Skip to main content
A pod is a hosted copy of your agent that co-signs and locks trades for one of your ads. These endpoints take a pod from request to live: you request it, follow it through approval, choose the ad it serves, register its key, file its retirements, and go live. See Agents for what an agent may do.
A pod request needs a ProofBridge operator’s approval before anything runs. Only makers may request a pod: you need at least one ad that is ACTIVE, PAUSED or EXHAUSTED, created from your wallet or a linked account. Each maker may have up to 2 pods by default, counting every pod that is not rejected or deleted.

Endpoints

Every pod route acts only on your own pods. Another user’s pod answers 403 not-owner, an unknown id 404 not-found. Pod refusals are coded: branch on error, never on message. Some carry an extra field such as limit, field, fields, chains, chainIds or status. Times in pod bodies are unix seconds unless a field says ISO 8601.

Status and next step

A pod has a status, where it is in its life, and a nextStep, what you should do now. Drive the screen from nextStep; use status for badges and filters. A live pod can also show set-policy when its policy expires, or agent-revoked once you revoke it. Offer Delete on every pod except a deleted one.

The state machine in brief

rejected and deleted are final; their label is free for a new request with a new id. A pod that is identity-ready or failed and unused for 3 days is reclaimed. A live pod is never reclaimed. retirements-filed is computed on read from identity-ready.

The pod object

GET /v1/pods/:id and every pod write except create answer with this body.
  • gas.*.level is ok, top-up-soon, low, missing (a Stellar account not yet on chain) or unknown (the read failed; balance is null). Balances are in whole coins and cached for 30 seconds; add ?fresh=1 right after a top-up. Send an EVM top-up on gas.evm.chainId.
  • coverage is null before the pod reports and while the check cannot run. scanned: false means it has not run yet.

Request a pod

POST /v1/pods Sends a request for review. Nothing is deployed until an operator approves it. If the pod server is full, the request is waitlisted instead, with no error. This route has a strict per-IP rate limit.
  • rpcOwnership: "manifest" keeps the endpoints with the pod’s configuration, so it restores itself after a host loss. "tenant-held" leaves them with you, and the pod stays down after a host loss until you enter them again.
  • Under "manifest", both evmUrl and stellarUrl must be on record before the pod can start; nextStep: "set-rpc" tells you when.
  • Every URL must be a public https provider endpoint with a real hostname: no IP literals, localhost, .internal or .local. The endpoints are stored encrypted and never returned.
  • One open pod per label. A rejected or deleted pod’s label is free again.
Try again. For a failed pod that was approved, send POST /v1/pods again with the same label. The pod is redeployed with the same id and no new review, and the answer is status: "provisioning". You may change rpc and rpcOwnership; contact, plan and registryChainIds must stay as approved.

List and capacity

GET /v1/pods lists your pods, newest first. Deleted pods are left out; rejected requests stay listed. The list has no nextStep; open the pod for that.
GET /v1/pods/capacity tells you whether to show the request form. Read it first, so you never show a form the relayer would refuse.
The request route still checks both, so map maker-required and requests-paused on submit too. If the pod host cannot be reached, this answers 502 plane-unavailable or 503 plane-busy.

One pod

GET /v1/pods/:id returns the pod object. Poll it to follow the pod; render your call to action from nextStep.

Choose the ad

GET /v1/pods/:id/eligible-ads lists your ads the pod can serve: ACTIVE, PAUSED or EXHAUSTED, oldest first.
POST /v1/pods/:id/ad chooses one. Choosing another ad later drops the pod’s binding to the previous one. You can choose before the pod has started.

Register the pod’s key

When nextStep is register-key, the pod has derived the key it will co-sign with for the ad’s settlement identity (its agent key) and signed one proof of possession per registry. The secret key never leaves the pod. You sign once and post it. GET /v1/pods/:id/registration
  1. Check env, rebuild the RegisterKey message locally and compare it with signPayload.
  2. The signed-in wallet that is account signs signPayload once.
  3. Post { ...submit, ownerAuth: { scheme, sig } } to POST /v1/keys/register. The pod’s nextStep turns to awaiting-binding, and the relayer binds the pod to the ad once the key is live on every registry.
If the post answers a stale nonce or an expired deadline, call this route again for a fresh payload.

File the pod’s retirements

When nextStep is file-retirements, file a pre-signed retirement for the pod’s agent key with POST /v1/keys/retire/prepare and POST /v1/keys/retire/presign. Use account = agentKey.settlementIdentity, keyCommitment = agentKey.keyCommitment, validUntil: "1", and the pod’s label as label. coverage turns green within 30 seconds.

Rotate

POST /v1/pods/:id/registration/rotate takes no body and asks the pod for its next agent key. Once a key has landed, the relayer refuses any other key from the pod until you call this. The pod posts the new key within a minute; then register it as above. The old key stays registered until you retire it through the keys routes. Answers the pod object with nextStep: "register-key".

Update the endpoints

PATCH /v1/pods/:id/rpc replaces the whole set of endpoints ({} clears them). A running pod redeploys with the new ones without a new review; expect one heartbeat gap. A waitlisted request just stores them. This route has a strict per-user rate limit.

Go live

POST /v1/pods/:id/go-live takes no body. The relayer checks that the pod has started, both endpoints are on record (under "manifest"), both operational accounts hold at least their gas minimum, and every registry slot has a pre-signed retirement on file. Then the pod leaves dry run. Calling it on a live pod returns the current status. heartbeat.dryRun turns false within a minute. This route has a strict per-user rate limit.

Delete

DELETE /v1/pods/:id takes no body. The pod is stopped and removed, and its binding to the ad is dropped. A waitlisted or under-review request is withdrawn. Deleting a declined request clears it from your list. Deleting an already deleted pod answers the same body, with status: "deleted". Confirm with the user first.

Logs

GET /v1/pods/:id/logs streams the pod’s logs as server-sent events (Content-Type: text/event-stream).
  • Each data: event is one line, { t, line }, where t is unix seconds.
  • An event: error frame means one read of the logs failed; the stream continues.
  • The browser EventSource cannot send an Authorization header, so use a fetch-based SSE reader with the bearer token.
  • A : keep-alive comment arrives every 25 seconds while idle. If the stream closes, reconnect after at least 5 seconds. On reconnect the whole current buffer (up to 1000 lines) is sent again, so clear the viewer or skip lines you already show.
  • At most 3 open streams per owner. Close the stream when the viewer is hidden.