Amistio

Developers

Integration guides

A quickstart and four short guides for building against the Amistio partner API. Every fact on this page is read from the code that answers your requests: the header names, the numbers, and the state names are the ones the service really uses.

Code, header names, JSON keys, state names, and the reference text quoted from the API stay in English so they match what your program sends and receives.

Quickstart

Five calls from nothing to a finished run. Run every sample on your server: the key travels only in the Authorization header and must never reach a browser or a mobile app.

  1. Create an API key

    In the Builder, open Settings → Developer keys, create a key, and give it only the scopes you need - agents:read, runs:start, and runs:read for this quickstart. The key is shown exactly once; Amistio keeps only a digest. Put it in the AMISTIO_KEY environment variable on your server.

  2. List your agents

    The answer holds each agent's id and whether a published version is live. Only a live agent can start a run through the API. When nextCursor is present, pass it back as cursor to read the next page.

    curl -H "Authorization: Bearer $AMISTIO_KEY" \
      https://www.amistio.com/api/v1/agents
  3. Read one agent's inputs

    form.fields lists the inputs the published version expects: the key, the kind, whether it is required, and the choices of a select field. Send your values under those keys.

    curl -H "Authorization: Bearer $AMISTIO_KEY" \
      https://www.amistio.com/api/v1/agents/<agentId>
  4. Start a run

    Send an Idempotency-Key header you choose. A retry with the same key inside 24 hours returns the same run with 200 instead of starting a second one with 201; the same key with a different input fails with 409. The input is validated exactly like the public run page.

    curl -X POST -H "Authorization: Bearer $AMISTIO_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: order-42" \
      -d '{"input":{"topic":"weekly report"}}' \
      https://www.amistio.com/api/v1/agents/<agentId>/runs
  5. Read the result - or let Amistio call you

    Poll the run until state has left running and awaiting-approval, then read outputs. Or register a result callback and be told the moment the run settles; the next guide shows how.

    curl -H "Authorization: Bearer $AMISTIO_KEY" \
      https://www.amistio.com/api/v1/runs/<runId>
    Go to result callbacks

Result callbacks

When a run of your agent settles - succeeded, failed, cancelled, or awaiting approval - Amistio POSTs one signed JSON message to the https address you registered. Your server checks the signature, records the event id, answers 2xx, and does its work afterwards.

Register a callback

One POST with a key that carries callbacks:manage. Name the settle states you want in events, or leave it out to receive all four. The response is the only time the raw secret is returned: it has the shape amcb_<registration>_<secret>, and Amistio keeps only its digest. Store it in your server's environment as AMISTIO_CALLBACK_SECRET. At most 10 callbacks per agent.

curl -X POST -H "Authorization: Bearer $AMISTIO_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://app.example.com/amistio/callback","events":["succeeded","failed"]}' \
  https://www.amistio.com/api/v1/agents/<agentId>/callbacks

What your server receives

A POST with Content-Type application/json and User-Agent Amistio-Callbacks/1, carrying the three headers below. Redirects are never followed, and one send waits at most 10 seconds for your answer.

X-Amistio-Event-Id
The stable id of this event. Retries resend the same id with a fresh timestamp and signature; deduplicate on it.
X-Amistio-Timestamp
When the message was signed, in Unix seconds. Refuse it when it is more than 300 seconds from your clock.
X-Amistio-Signature
sha256= followed by the hex HMAC-SHA256 of "<timestamp>.<raw body>", with your raw secret as the key.

The message body

A bounded JSON object: ids, the state, timestamps, and a redacted preview of the outputs for a succeeded run or the typed error for a failed one. Never inputs, steps, credentials, drafts, or another owner's data.

idstring
The event id - the same value as the X-Amistio-Event-Id header.
type"run.succeeded" | "run.failed" | "run.cancelled" | "run.awaiting-approval"
run.<state>: the word run, a dot, and the settle state this message is about.
occurredAtstring (ISO 8601)
When the run reached this state.
run.runIdstring
The run id. Read the whole run with the get-run operation.
run.definitionIdstring
The agent id - agentId in the rest of the API.
run.versionIdstring, optional
The published version that ran, when known.
run.state"succeeded" | "failed" | "cancelled" | "awaiting-approval"
The settle state on its own, without the run. prefix.
run.startedAtstring (ISO 8601)
When the run started.
run.settledAtstring (ISO 8601)
When the run reached this state - the same instant as occurredAt.
run.outputPreviewstring, optional
Only for succeeded: one bounded, redacted JSON string of the outputs. Generated images and audio are reduced to descriptors.
run.error{ code, message }, optional
Only for failed: the run's typed error code and a redacted message.
attemptnumber
1 for the first send, then 2, 3, and so on for each retry.

Verify the signature

Five checks, in this order, before you trust a message. The samples below are complete verifiers in eight languages; the curl tab is a manual openssl check for one captured message.

  1. Read the body exactly as received, as raw text. Never re-serialize parsed JSON: a reordered key or a changed space breaks the signature.
  2. Read X-Amistio-Timestamp and refuse it when it is more than 300 seconds from your clock, in either direction.
  3. Compute HMAC-SHA256 over the timestamp, a dot, and the raw body, with your raw secret as the key; hex-encode it and prefix sha256=.
  4. Compare it with X-Amistio-Signature in constant time. Reject any difference, including a different length.
  5. Record X-Amistio-Event-Id and ignore a message whose id you already handled. Answer 2xx within a few seconds and do the work afterwards; a slow answer is retried as a timeout.
# Manual check from a shell: recompute the signature of one callback you captured.
# Headers: X-Amistio-Event-Id, X-Amistio-Timestamp (Unix seconds), X-Amistio-Signature (sha256=<hex>).
# Save the body byte for byte as body.json and keep the secret the register call returned once in $AMISTIO_CALLBACK_SECRET.
TIMESTAMP='<X-Amistio-Timestamp value>'
printf '%s.' "$TIMESTAMP" | cat - body.json \
  | openssl dgst -sha256 -hmac "$AMISTIO_CALLBACK_SECRET" -hex \
  | sed 's/^.* //; s/^/sha256=/'
# The printed value must equal X-Amistio-Signature. Refuse the message when
# the difference between $(date +%s) and $TIMESTAMP is more than 300 seconds.
# A real receiver compares in constant time, deduplicates on X-Amistio-Event-Id, and answers 2xx right away.

Retries

A 2xx answer ends the delivery. Timeouts, network failures, and the statuses 408, 429, 5xx are retried: at most 5 attempts, with 30 s, 2 min, 10 min, 30 min between them - about 42.5 minutes end to end. Every other 4xx is a final rejection with no retry.

Pause and resume

Set enabled to false to pause: nothing new is sent, and a message already queued for that callback is skipped. Set it back to true to resume. The secret and the events do not change.

curl -X PATCH -H "Authorization: Bearer $AMISTIO_KEY" \
  -H "Content-Type: application/json" \
  -d '{"enabled":false}' \
  https://www.amistio.com/api/v1/agents/<agentId>/callbacks/<callbackId>

Rotate the secret

Rotation mints a fresh secret and raises secretVersion. Messages sent after the call are signed with the new secret only - there is no overlap window - so update your server right after. The response is the only time the new secret is returned.

curl -X POST -H "Authorization: Bearer $AMISTIO_KEY" \
  https://www.amistio.com/api/v1/agents/<agentId>/callbacks/<callbackId>/rotate-secret

Delivery evidence

Every delivery is recorded on its run: the outcome, a closed reason for a failed or skipped one, and for each attempt its time, the HTTP status or failure class, and a bounded excerpt of your response. Read it with a key that carries runs:read. Never the secret, and never the registered address beyond its host.

curl -H "Authorization: Bearer $AMISTIO_KEY" \
  https://www.amistio.com/api/v1/runs/<runId>/deliveries

Outcomes

pending
Attempts remain.
delivered
Your server answered 2xx.
failed
Amistio gave up; see the reason.
skipped
Nothing was sent; see the reason.

Reasons

queue-unavailable
This deployment has no delivery queue.
enqueue-failed
The delivery could not be queued.
registration-missing
The callback was removed before the send.
registration-disabled
The callback was paused.
run-missing
The run no longer exists.
secret-unavailable
The signing secret could not be read.
egress-denied
At send time the address resolved to a private or internal network.
rejected
Your server answered a 4xx other than 408 or 429.
attempts-exhausted
Every attempt failed.

Failure classes of one attempt

  • timeout
  • network
  • egress-denied
  • rejected
  • server-error
  • rate-limited

Which addresses are accepted

Public https only, with no embedded credentials and no fragment. Private, loopback, link-local, and cloud metadata ranges are refused with 400 at registration, and the same check runs again - DNS included - right before every send; a denial at send time is final.

Embed a run page

Show the public run page of a live agent inside your own site. Your server mints a short-lived embed session, your page loads its embedUrl in an iframe, and the visitor runs the agent without leaving you.

  1. Allow your origin

    In the Builder, open Settings → Embedding origins and add the exact origin of the page that will hold the frame: scheme, host, and port, no path. Only a listed origin can mint or load a frame.

    Open Embedding origins
  2. Mint a session on your server

    POST with a key that carries embed:mint, naming a live agent and the allowed origin. The answer holds a token shown once, the embedUrl to load, and expiresAtMillis. 409 means the origin is not on your list or nothing is live; 501 means this deployment has no AMISTIO_EMBED_TOKEN_KEY configured.

    curl -X POST -H "Authorization: Bearer $AMISTIO_KEY" \
      -H "Content-Type: application/json" \
      -d '{"agentId":"<agentId>","origin":"https://app.example.com"}' \
      https://www.amistio.com/api/v1/embed-sessions
  3. Load the frame

    Put embedUrl in an iframe on that origin and load it right away: the first load must happen within 10 minutes of minting, and the token is bound to one origin and one load, so mint a fresh session for every page view. After the first load the frame's own calls stay valid for 60 minutes. Do not add a sandbox attribute - the frame must keep its own origin to talk to Amistio - and no allow permissions are needed.

    <!-- Mint a fresh embedUrl on your server for every page view, then render it: -->
    <iframe
      src="<embedUrl from the mint response>"
      title="<your agent's name>"
      width="100%"
      height="640"
      style="border: 0"
    ></iframe>

What can and cannot be framed

Only the public run page of a live agent - surface run-page. The Builder is not embeddable. Every Amistio page answers with frame-ancestors 'none', except /embed/<token> with a valid token, which allows exactly the one origin the token was minted for. A failed check renders the standard not-found page, never partial content.

Every load also checks that the minting key is still active, that the origin is still on your list, and that the agent is still live. When the browser tells Amistio which page is embedding, it must be the bound origin, so keep the default referrer policy.

The life of a run

A run starts in running and ends in exactly one settled state. The state names below are the ones the API returns in state.

States

running
Working. Poll again or wait for the callback.
awaiting-approval
Paused before a step that needs the owner's decision: an approval block, or a tool that is not pinned read-only. Approved, the run continues in running; rejected, it takes its rejected branch or the step fails.
succeeded
Finished. outputs holds the redacted result.
failed
Stopped with error.code and error.message.
cancelled
Stopped by the owner or by its deadline; see cancellation.reason.

Polling or callbacks

Read the run until state is one of succeeded, failed, cancelled; each read counts toward the 60 requests per minute per key. A result callback tells you the moment a run settles and is the better fit for anything that waits longer than a few seconds. Both return the same redacted view.

Approvals

While a run waits, pendingApproval shows how many approvers are required and how many have decided. Decisions are made in Amistio by the owner; the API cannot approve or reject. approvals counts the decisions recorded so far.

Where a run came from

origin.kind names the door a run entered through. Runs your code starts are api; runs from an embedded frame are embed. The other values are the Builder's own doors.

  • manual
  • draft-test
  • webhook
  • schedule
  • share-link
  • provider-event
  • room-message
  • conversation
  • api
  • embed

What the view holds

input is what was sent. outputs is the redacted result: generated images and audio are reduced to descriptors, and secrets are scrubbed. steps lists up to 100 recorded steps with a bounded preview each; stepsTruncated is true when more happened. usage counts modelCalls, toolCalls, connectorCalls, retries for the whole run.

Cancellation

user-requested · deadline

cancellation.reason is user-requested when the owner stopped the run and deadline when it ran past its time limit; requestedAtMillis is present when someone asked.

Connect your MCP server

Amistio is an MCP client: it connects to remote MCP servers so agents can use their tools, read their resources, and import their prompts. It does not expose an MCP server of its own. This guide is for the server you run.

Transport

Streamable HTTP over public https: Amistio POSTs JSON-RPC to your server URL and accepts application/json or text/event-stream answers. Protocol versions 2025-11-25, 2025-06-18, 2025-03-26. Redirects are refused, one response is read up to 1 MiB, and the address must pass the same egress guard as every connector call - private and internal networks are refused.

Authentication

OAuth 2.1, discovered from your server: the protected-resource metadata names the authorization server, whose metadata names the endpoints. Amistio identifies itself with a Client ID Metadata Document - its client_id is the URL https://www.amistio.com/.well-known/amistio-oauth-client.json - when your authorization server advertises client_id_metadata_document_supported, and falls back to dynamic registration otherwise. Authorization code grant only, PKCE with S256 and the resource indicator on every request, no client secret, redirect to /builder/oauth/callback.

The document this deployment publishes:

{
  "client_id": "https://www.amistio.com/.well-known/amistio-oauth-client.json",
  "client_name": "Amistio",
  "client_uri": "https://www.amistio.com",
  "redirect_uris": [
    "https://www.amistio.com/builder/oauth/callback"
  ],
  "grant_types": [
    "authorization_code",
    "refresh_token"
  ],
  "response_types": [
    "code"
  ],
  "token_endpoint_auth_method": "none"
}

Or a server token: the owner pastes a bearer token once under Integrations. It is stored encrypted in the owner's vault, never shown again, rotated by pasting a new one, and sent as the Authorization header on every call. A server that needs no credential connects without one.

Tools and approvals

Annotate every tool honestly with readOnlyHint. Read-only tools and resource reads run on their own; every other tool waits for the owner's approval in Amistio before it is called. Amistio advertises no client capabilities, so sampling/createMessage, elicitation/create, roots/list are answered with method not found and never acted on.

Resources

An agent step can read a pinned resource. Only text and JSON MIME types are accepted, one read is bounded at 256 KiB and fails closed instead of truncating, and the content enters the run as untrusted data to parse - never as instructions.

Prompts

A pinned prompt can be imported into a Skill draft with its arguments mapped to Skill inputs. The owner reviews and releases it as an immutable Skill; prompts are never fetched during a run.

Pins

When the owner connects your server, its tool, resource, and prompt lists are frozen. A changed name, schema, URI, or argument list fails closed at call time with a typed error until the owner removes and re-adds the connection - so ship changes as new names.

Closed on purpose

Sampling (server-driven model calls), elicitation (questions to the person mid-run), stdio servers, and MCP Apps UI are not offered. Nothing your server sends can drive the model or question the person.

Where owners connect it

Builder → Integrations: paste the server URL, choose OAuth, a server token, or no sign-in, and accept the pinned catalog.

Open Integrations

Essential cookies keep Amistio working. Nothing is tracked until you allow analytics.