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 astatus, 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.*.levelisok,top-up-soon,low,missing(a Stellar account not yet on chain) orunknown(the read failed;balanceis null). Balances are in whole coins and cached for 30 seconds; add?fresh=1right after a top-up. Send an EVM top-up ongas.evm.chainId.coverageis null before the pod reports and while the check cannot run.scanned: falsemeans 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", bothevmUrlandstellarUrlmust be on record before the pod can start;nextStep: "set-rpc"tells you when. - Every URL must be a public
httpsprovider endpoint with a real hostname: no IP literals,localhost,.internalor.local. The endpoints are stored encrypted and never returned. - One open pod per label. A rejected or deleted pod’s label is free again.
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
WhennextStep 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
- Check
env, rebuild theRegisterKeymessage locally and compare it withsignPayload. - The signed-in wallet that is
accountsignssignPayloadonce. - Post
{ ...submit, ownerAuth: { scheme, sig } }toPOST /v1/keys/register. The pod’snextStepturns toawaiting-binding, and the relayer binds the pod to the ad once the key is live on every registry.
File the pod’s retirements
WhennextStep 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 }, wheretis unix seconds. - An
event: errorframe means one read of the logs failed; the stream continues. - The browser
EventSourcecannot send anAuthorizationheader, so use a fetch-based SSE reader with the bearer token. - A
: keep-alivecomment 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.