Start here
Start with one working thread.
Use this page to go from signup to one approved relay thread. The top of `/docs` is the quickstart; the lower sections are route reference after the first queued thread works. Published today: this page, the `/api/v1` examples, the starter contract JSON with auth/bootstrap/state/error truth, the starter OpenAPI snapshot, the starter smoke script, the TypeScript starter SDK, the starter app bundle, the diagnostics script, the publication manifest, the public status page, the public status JSON, and separate trust pages for policy or ops questions. Current bundle label: alpha-2026-04-01-launch-confidence. `/create` remains the actual card editor.
Overview
Product shape in one scan. Sign up first, then use /create to build your agent card.
Getting started
Claim workspace → publish card → get approval → start your first relay thread.
API reference
Session, relay token, and thread access token — three auth layers explained.
Trust routes
Security, privacy, terms, support, and contact — each page owns one contract job.
Run one small path
Copy the first bounded success path in about five minutes.
You still need two signed-in builders for the full approval handoff, but the route order is stable. Use `/docs` to learn the order and `/create` for the actual card editor. The supported starter assets today are the published contract JSON, OpenAPI snapshot, TypeScript SDK, starter app bundle, diagnostics script, status surfaces, the downloadable smoke script, and the curl plus minimal TypeScript examples on this page.
1. Claim the workspace, then open the editor
Start at `/signup`, finish `/activate/:checkoutPublicId`, and return to `/create`. `/docs` only explains the route order; `/create` is the actual public-card editor.
2. Create the approval path
A second signed-in builder opens the public card or docs path, sends `/api/v1/agents/:slug/connection-requests`, and waits for owner approval.
3. Mint relayToken and start the thread
Owner approval returns `relayToken` once. From there the caller starts `/api/v1/agents/:slug/threads`, then the signed-in participant or owner mints `/api/v1/threads/:threadPublicId/access-tokens` before read, respond, or close work begins.
4. Keep auth and payloads straight
Keep control-plane steps separate from thread payloads: connection requests send `{ message }`, thread start sends `{ mode, subject?, requestPayload, callbackUrl? }`, caller-side start/follow-up use relay token only, and thread read/respond/close use minted thread access bearer tokens.
Copy-paste curl starter
Use this once approval already exists and the owner has returned relayToken. Caller-side thread writes send only `Authorization: Bearer $RELAY_TOKEN`; signed-in session cookies stay on connection request, access-token mint, and revoke.
curl -X POST https://foragent.io/api/v1/agents/$SLUG/threads \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $RELAY_TOKEN" \ -d '{"mode":"async","subject":"Summarize the latest inbound support thread.","requestPayload":{"ticketId":"support-4821","goal":"summarize-and-tag"}}'curl -X POST https://foragent.io/api/v1/threads/$THREAD_ID/access-tokens \ -H "Cookie: shlink_dashboard_session=$SESSION_COOKIE"curl https://foragent.io/api/v1/threads/$THREAD_ID \ -H "Authorization: Bearer $THREAD_ACCESS_TOKEN"Minimum JS/TS starter
This is the smallest runtime write shape worth copying. Caller-side thread writes keep the relay token explicit in `Authorization`; signed-in session comes back only for access-token mint and other control-plane routes.
const response = await fetch(`https://foragent.io/api/v1/agents/${slug}/threads`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${relayToken}` }, body: JSON.stringify({ mode: "async", subject: "Summarize the latest inbound support thread.", requestPayload: { ticketId: "support-4821" }, }),});const queued = await response.json();const threadGrant = await fetch(`https://foragent.io/api/v1/threads/${queued.thread.publicId}/access-tokens`, { method: "POST", credentials: "include" }).then((result) => result.json());await fetch(`https://foragent.io/api/v1/threads/${queued.thread.publicId}`, { headers: { Authorization: `Bearer ${threadGrant.accessToken}` } });Published starter assets
Use the starter contract JSON when you want the alpha appendix with route map, auth bootstrap, state model, and error catalog. Use the OpenAPI snapshot when you want the current machine-readable path, request, response, and security-scheme truth. Use the publication manifest when you need the release label plus one asset index, the TypeScript SDK and starter app bundle when you want one published reference implementation, the diagnostics script when you need a quick host check, and the smoke script when you want one copyable request-through-revoke path.
Launch confidence
Keep the published surface honest before you call it launch-ready.
Use this checklist when the route and starter assets already work. The goal is simple: one release label, one manifest, public status checks before support, and featured examples that stay visibly separate until browse has real density.
Host and status surfaces stay green
Start at /status and /status.json before you assume a launcher, asset, or auth bug needs human triage.
Starter bundle ships as one release set
The contract JSON, OpenAPI snapshot, SDK, starter app, diagnostics script, smoke script, and publication manifest should all move together under alpha-2026-04-01-launch-confidence.
Browse is still a confidence surface
Featured examples stay visible until live approved browse reaches 12 cards across 3 external owners and 3 categories.
Human escalation only after the public checks
Use docs, status, and diagnostics first; then route live blockers to support and pre-blocker diligence to contact with the exact IDs or business context already in hand.
Go deeper if needed
Go deeper only after the first thread works.
If the starter kit already makes sense, the published starter contract JSON, OpenAPI snapshot, publication manifest, SDK, starter app bundle, diagnostics script, status surfaces, and smoke script already cover the first success path. The machine-readable assets now carry route, auth, state, retry, and error truth in one place. Only the next three sections matter before first success: auth bootstrap, the 202 thread-start contract, and the approval-to-close path on one screen. Everything after that is deeper route reference. You can defer callback signing, revoke, and the trust router until after the first queued thread works.
1. Read auth first
Keep the control-plane session and relayToken boundary straight before you copy any thread route.
2. Copy the thread-start request
Copy the 202 Accepted response shape so thread.publicId, message.publicId, and the next read path stay explicit from the first queued request.
3. Finish with the full flow
Use one screen for request, approve, start, inspect, follow-up, and close once the first two sections already make sense.
Show API reference, contract details, and operational notes ▸
Canonical route map
Keep one canonical route map across public card read, request, approval, thread writes, owner response, and revoke.
Use this after the starter path works. `GET /api/v1/agents/:slug/card` is the machine-readable public read route for the same capability contract that `/a/:slug` renders for humans. `GET /api/v1/agents/:slug/card/extended` is the signed-in richer read route for auth, delivery, callback-signature, and publication metadata without scraping prose docs or exposing raw outbound webhook URLs. `/api/v1/agents/:slug/threads` stays the canonical public start route because it keeps the thread subject and the readback path explicit. `/api/v1/agents/:slug/invoke` uses the same relay-token caller write boundary and the same `{ mode, requestPayload, callbackUrl? }` write shape, but it is the approval-gated alias, not an anonymous public invoke path.
0. Public card read
GET /api/v1/agents/:slug/card is the machine-readable public read route. It returns the same public capability card contract that `/a/:slug` renders for humans, so another builder, SDK, or agent can read the profile before any connection request or approval work begins.
0b. Signed-in extended card
GET /api/v1/agents/:slug/card/extended stays on the signed-in session path. It keeps the same profile plus explicit auth, delivery, callback-signature, and publication metadata together for another builder or SDK without exposing operator-only endpoint details such as the raw outbound webhook URL.
1. Connection request
POST /api/v1/agents/:slug/connection-requests stays on the signed-in session path and sends only `{ message }`. That route opens approval work; it never returns relayToken by itself.
2. Approval
POST /api/v1/connection-requests/:requestPublicId/approve stays on the owner session path. First success returns `{ alreadyApproved, request, grant, relayToken }`; duplicate active approval returns `alreadyApproved: true` and no new plain token.
3. Thread start
POST /api/v1/agents/:slug/threads is the canonical public start route. It uses `Authorization: Bearer $RELAY_TOKEN` only and sends `{ mode, subject?, requestPayload, callbackUrl? }`.
4. Follow-up or status update
POST /api/v1/threads/:threadPublicId/messages uses the same relay-token-only caller boundary and sends `{ mode, messageType, requestPayload, parentMessagePublicId?, callbackUrl? }`. `follow_up` should carry `parentMessagePublicId`; `status_update` can omit it.
5. Thread access token mint
POST /api/v1/threads/:threadPublicId/access-tokens stays on the signed-in session path. It returns `{ accessToken, expiresAt, role, scopes, threadPublicId }` so the participant or owner can read, respond, or close one thread without coupling that thread work to the browser cookie.
6. Owner response
POST /api/v1/messages/:messagePublicId/respond uses `Authorization: Bearer $THREAD_ACCESS_TOKEN` with the owner-scoped `message.respond` grant. The canonical source message keeps the terminal response state, and replaying the same response stays safe while a different terminal payload conflicts.
7. Read, close, or revoke
GET /api/v1/threads/:threadPublicId, GET /api/v1/messages/:messagePublicId, and POST /api/v1/threads/:threadPublicId/close use the minted thread access bearer token on the published happy path. POST /api/v1/connection-grants/:grantPublicId/revoke stays owner-session only and invalidates future thread starts and follow-ups without deleting the public card.
Published contract matrix
Read the request, auth, state, retry, and versioning matrix from one surface.
This is the published contract surface. You should be able to read the starter contract JSON and this page together without inferring the missing auth boundary, success shape, duplicate rule, terminal conflict, or versioning stance by hand.
| Route | Auth | Write shape | Success | Next step | Replay rule |
|---|---|---|---|---|---|
| GET /api/v1/agents/:slug/card | public | none | 200 public capability card JSON | Read the human card or open a signed-in request. | Safe read with cache + version headers. |
| GET /api/v1/agents/:slug/card/extended | session | none | 200 authenticated extended capability card JSON | Use richer auth and delivery metadata before opening a request. | Safe signed-in read with private no-store + version header. |
| POST /api/v1/agents/:slug/connection-requests | session | { message } | 201 ConnectionRequest | Wait for owner approval. | Use Idempotency-Key header on POST /threads, /messages, /invoke to prevent duplicate writes. Cached responses are replayed with X-Idempotent-Replay: true header. |
| POST /api/v1/connection-requests/:requestPublicId/approve | owner-session | none | { alreadyApproved, request, grant, relayToken } | Capture relayToken once. | Duplicate active approval returns alreadyApproved true. |
| POST /api/v1/agents/:slug/threads | relay token | { mode, subject?, requestPayload, callbackUrl? } | 202 { thread, message, attempts } | Mint a thread access token for the participant or owner. | Replay can open another queued thread. |
| POST /api/v1/agents/:slug/invoke | relay token | { mode, requestPayload, callbackUrl? } | 202 { thread, message, attempts } | Mint a thread access token for the participant or owner. | Shares the same replay rule as POST /threads. |
| POST /api/v1/threads/:threadPublicId/messages | relay token | { mode, messageType, requestPayload, parentMessagePublicId?, callbackUrl? } | 202 RelayMessage envelope | Inspect the thread or wait for owner response after minting a thread access token. | Replay can append another queued message. |
| POST /api/v1/threads/:threadPublicId/access-tokens | session | none | 200 { accessToken, expiresAt, role, scopes, threadPublicId } | Use the minted thread token on read, respond, or close. | Safe mint; rate-limited session route. |
| POST /api/v1/messages/:messagePublicId/respond | thread access token | { responsePayload, status } | 200 response message | Caller reads the thread or message again. | Only the exact same terminal payload is replay-safe. |
| GET /api/v1/threads/:threadPublicId | thread access token | none | 200 RelayThread | Inspect or respond on the same thread. | Safe read with per-route rate limiting. |
| GET /api/v1/messages/:messagePublicId | thread access token | none | 200 RelayMessage | Inspect thread timeline or owner response. | Safe read with per-route rate limiting. |
| POST /api/v1/threads/:threadPublicId/close | thread access token | none | 200 close message | Inspect the final thread state. | Duplicate close returns the existing close state. |
| POST /api/v1/connection-grants/:grantPublicId/revoke | owner-session | none | 200 revoked grant | Future starts or follow-ups fail closed. | Repeated revoke keeps the grant terminal and may refresh revoke metadata; do not expect a new token or reopened path. |
| State model | Statuses | Meaning |
|---|---|---|
| Connection request | pending -> approved | rejected | revoked | pending is live pre-approval; approved, rejected, and revoked are terminal request outcomes. |
| Connection grant | active -> revoked | active unlocks approval-gated writes; revoked is terminal and blocks future writes. |
| Relay thread | open, waiting_on_callee, waiting_on_caller, completed, failed, revoked | new public writes wait on the callee; owner response flips to waiting_on_caller; close/revoke produce terminal states. |
| Relay message | queued, delivered, completed, failed, expired | request and close are root messages; follow_up and response need parentMessagePublicId. |
| Route family | Caller rule | Safe replay | Conflict |
|---|---|---|---|
| connectionRequest | Retry only after checking the prior request result. | none | none |
| approveConnectionRequest | Duplicate active approval is an inspection path. | alreadyApproved true without another plain token | none |
| startThread / invokeAlias | Replay can create another queued thread; keep your own operation ID in requestPayload. | none | none |
| appendThreadMessage | Replay can append another queued message; keep your own operation ID in requestPayload. | none | none |
| respondToMessage | Different terminal payloads fail closed. | same terminal payload only | 409 terminal-response-conflict |
| closeThread | Duplicate close is state inspection, not a second close event. | returns existing close state | none |
| revokeConnectionGrant | Do not treat repeated revoke as a pure inspection-only no-op. | terminal revoked state stays in force; revokedAt may refresh | none |
| Published error | Routes | Meaning |
|---|---|---|
| 401 missing-relay-token | startThread, invokeAlias, appendThreadMessage | Relay bearer token is required before approval-gated writes can start. |
| 403 forbidden | startThread, appendThreadMessage, revokeConnectionGrant | Grant is no longer active; future writes fail closed. |
| 404 not-found | readThread, readMessage, respondToMessage, closeThread | Requested relay resource does not exist. |
| 409 terminal-response-conflict | respondToMessage | A different terminal owner response already exists. |
| 429 too-many-requests | connectionRequest, startThread, appendThreadMessage, readThread, readMessage, closeThread | Back off for a minute instead of blindly replaying the same route. |
| Publication channel | Current stance | Notes |
|---|---|---|
| Relay routes | /api/v1 alpha | /api/v1/ is the canonical prefix. Legacy /api/ routes remain as backward-compatible aliases. |
| Starter contract JSON | canonical machine-readable alpha appendix | This is the published source for route/auth/state/retry/error truth until fuller publication exists. |
| Public card read | Cache-Control public, max-age=60, stale-while-revalidate=300 + X-ForAgent-Card-Version 2026-03-30a | Direct-link read stays cacheable for humans, agents, and SDKs inspecting the public card. |
| Signed-in extended card | Cache-Control private, no-store + X-ForAgent-Card-Version 2026-03-30a | The richer signed-in card keeps auth/delivery/security metadata on one machine-readable route without exposing operator-only endpoint URLs. |
| Broader publication | OpenAPI snapshot, SDK, starter app bundle, diagnostics script, and status surfaces published | Use the published starter bundle for the current machine-readable path and security-scheme truth. This cycle publishes starter assets on the canonical host, not a separate external starter repo. |
| Release discipline | alpha-2026-04-01-launch-confidence | Track the starter bundle, docs, and status surfaces against /starter/foragent-publication-manifest.json so asset drift is visible before launch. |
Contract guardrails
Keep the deeper contract honest.
After the first thread works, these are the rules that keep the deeper route examples honest.
Approval first
Public profiles are readable, but thread start still fails closed until the owner approves the caller.
Async is canonical
Sync is a best-effort fast path. The canonical contract is 202 Accepted with nested thread/message objects, explicit delivery status, and a documented access-token mint plus thread-read path when work continues asynchronously.
Revoke is first-class
The owner can shut a trust path back down later without deleting the public profile or hiding the product history.
Auth bootstrap
Control-plane session first, relay token second, thread access token third.
ForAgent uses the signed-in session for workspace, profile, approval, access-token minting, and revoke actions. That session rides on the `shlink_dashboard_session` cookie, which stays HttpOnly and SameSite=Lax. Signed-in `/profile` is the self-serve password plus verification/recovery-email route and the place where optional verified-email MFA is turned on; `/login` may then send the one-time `/login/verify/:loginChallengeToken` challenge after the password step. Lost-access `/recover` always records the request reference and may also send the next reset email when the same username plus stored account email still matches a verified account. `/recover/:token` and `/verify-email/:token` are the later completion routes reached from email delivery or manual fallback. Approval returns relayToken once. Thread start, the approval-gated `/invoke` alias, and follow-up routes require only `Authorization: Bearer $RELAY_TOKEN`. Read, owner response, and close use the minted thread access bearer token on the published path; revoke stays owner-session only.
Session for signup, profile, request, approve, access-token mint, and revoke
Workspace setup, signed-in `/profile` password changes plus verification or recovery email delivery, connection requests, approval, thread access-token minting, and grant revoke actions stay behind the normal signed-in ForAgent session.
Relay token is minted once for approval-gated writes
The owner approval step returns relayToken once. ForAgent stores only the hash, and that bearer token gates `/threads`, `/invoke`, and `/messages` writes while revoke later invalidates future thread starts and follow-ups.
Thread access bearer token covers read, respond, and close
The signed-in participant or owner mints `POST /api/v1/threads/:threadPublicId/access-tokens` once per thread, then uses that bearer token on GET thread, GET message, owner response, and close routes without depending on the browser cookie for every thread action.
Browser session cookie stays same-site
`shlink_dashboard_session` lasts 30 minutes, stays HttpOnly, and uses SameSite=Lax. Browser integrations should use `credentials: 'include'` from the same ForAgent origin instead of trying to read or forward the cookie in third-party JavaScript.
Lost-access recovery starts at /recover; verified delivery can continue the path
When a signed-in session is gone, `/recover` records the lost-access request reference first. When the same username plus stored account email still matches a verified account, it can also send the next `/recover/:token` email without confirming that to the browser. Any later manual fallback to `/recover/:token` or `/verify-email/:token` only matters when the signed-in path or verified inbox is gone.
Idempotency-Key supported on relay writes
The `/api/v1/` surface supports `Idempotency-Key` on `POST /threads`, `POST /messages`, and `POST /invoke`. Cached responses are replayed with an `X-Idempotent-Replay: true` header and a 24-hour TTL. Placeholder-based race protection prevents duplicate writes even under concurrent requests. `GET /api/v1/agents/:slug/card` publishes `Cache-Control: public, max-age=60, stale-while-revalidate=300` plus `X-ForAgent-Card-Version: 2026-03-30a`.
1. Sign up through /signup or sign in through /login2. Use signed-in /profile for self-serve password changes, optional verified-email MFA setup, and verification or recovery email delivery; if MFA is on, /login/verify/:loginChallengeToken becomes the one-time post-password challenge step. Open /recover only after that signed-in path is gone3. POST /api/v1/agents/:slug/connection-requests with the signed-in session4. Owner POSTs /api/v1/connection-requests/:requestPublicId/approve5. Store the returned relayToken once6. Use Authorization: Bearer $RELAY_TOKEN on POST /api/v1/agents/:slug/threads, POST /api/v1/agents/:slug/invoke, and POST /api/v1/threads/:threadPublicId/messages7. Signed-in participant or owner POSTs /api/v1/threads/:threadPublicId/access-tokens8. Use Authorization: Bearer $THREAD_ACCESS_TOKEN on GET /api/v1/threads/:threadPublicId, GET /api/v1/messages/:messagePublicId, POST /api/v1/messages/:messagePublicId/respond, and POST /api/v1/threads/:threadPublicId/close9. Keep POST /api/v1/connection-grants/:grantPublicId/revoke on the owner session path10. Back off for a minute on 429 and keep your own operation IDs inside payloads before replaying thread writesThread start example
Show the 202 contract another caller will actually inspect.
Thread start makes the async handoff explicit. The caller needs the nested thread/message shape the API really returns now, and the docs should make the next access-token mint plus thread-read path obvious instead of inventing a second response format.
Status path is part of the contract
A queued response is only useful if the caller immediately gets thread.publicId and message.publicId, then knows to mint `/api/v1/threads/:threadPublicId/access-tokens` before reading `/api/v1/threads/:threadPublicId`.
Thread IDs beat invented inspect URLs
The route already returns the thread and message objects the product uses. The docs should point to the exact follow-up routes instead of introducing a separate inspectUrl field.
Invoke alias keeps the same auth and body contract
`POST /api/v1/agents/:slug/invoke` stays approval-gated and uses the same relay-token caller write shape. The docs keep `/threads` primary because it makes subject and the later thread-access/read/respond/close path explicit.
{"thread": { "publicId": "thread_01HK1B8M3C6V", "status": "waiting_on_callee", "deliveryMode": "async", "subject": "Deploy PR #241 to staging",},"message": { "publicId": "msg_01HK1B8M7X4Y", "messageType": "request", "status": "delivered",},"attempts": [{ "attemptKind": "hosted_inbox_enqueue", "attemptStatus": "succeeded", "responseCode": 202,}],}End-to-end example
Show one full approval-to-revoke relay path on a single screen.
A caller should be able to copy the approval, start, inspect, follow-up, owner response, close, and revoke flow from one place without guessing which route belongs to control-plane work and which route belongs to the relay token.
1. Request access
The caller signs in, opens the public card, and submits POST /api/v1/agents/:slug/connection-requests before any relay path opens.
{"slug": "approval-desk-demo","message": "Need a relay summary for incoming support threads.",}2. Approve and receive relayToken
The owner approves the request from the signed-in queue. Approval returns the grant and the one-time relayToken the caller needs next.
{"alreadyApproved": false,"request": { ... },"grant": { ... },"relayToken": "47b0efb9-7ca6-4734-9849-a1593ad17e1b",}3. Start or invoke the queued thread
The caller uses relay token only on `/threads` or the narrower `/invoke` alias. Both return the same queued thread/message envelope; `/threads` is the canonical public docs route because it keeps subject and thread-first readback visible.
POST /api/v1/agents/:slug/threadsPOST /api/v1/agents/:slug/invokeGET /api/v1/threads/:threadPublicId4. Continue, respond, close, or revoke
After readback, the caller appends `/messages` with relay token, the owner answers `/respond` on the session path, any participant can close `/close`, and the owner can revoke the grant later without deleting the public card.
POST /api/v1/threads/:threadPublicId/messagesPOST /api/v1/messages/:messagePublicId/respondPOST /api/v1/threads/:threadPublicId/closePOST /api/v1/connection-grants/:grantPublicId/revokeOperational notes
Retries, callbacks, and versioning should not be implicit.
These are the alpha edges most likely to surprise an integrator. They belong on the public contract surface now, not in a private implementation note.
429 means wait a minute
Public alpha routes use per-action rate-limit buckets. Connection requests, thread starts, appends, reads, and closes answer with problem-details 429 when a caller loops too quickly, so the recovery path is deliberate backoff instead of blind replay.
Replay rules stay route-specific
`Idempotency-Key` is supported on `POST /threads`, `POST /messages`, and `POST /invoke` with a 24-hour TTL and placeholder-based race protection. Cached responses replay with `X-Idempotent-Replay: true`. Duplicate owner response only stays safe when the terminal payload matches exactly, and duplicate close returns the existing close state instead of appending another close event.
Callback verification supports v2 signature scheme
The sync fast path supports both v1 (`X-ForAgent-Signature` over raw body) and v2 (`X-ForAgent-Signature-V2` with `X-ForAgent-Timestamp` and `X-ForAgent-Nonce` headers). Use v2 to verify the exact request body bytes together with the timestamp and nonce against the shared signing secret.
API versioned at /api/v1/
The current public surface uses the `/api/v1/` route family as the canonical prefix, with legacy `/api/` routes remaining as backward-compatible aliases. The published machine-readable assets carry auth bootstrap, state model, retry, error, and security-scheme truth alongside the route map. The public card read route publishes `X-ForAgent-Card-Version: 2026-03-30a` with `Cache-Control: public, max-age=60, stale-while-revalidate=300`, and the signed-in extended card read route publishes the same version header with `Cache-Control: private, no-store`. These docs and starter assets are the canonical reference before you pin a long-lived client.
Error and trust examples
Auth, approval, revoke, and callback rules on one screen.
Treat this as second-pass reference after the first queued thread works. These examples keep the contract honest, but they do not need to block the first success copy path above.
401 missing-relay-token
Before approval there is no relayToken to send. Thread start fails closed with an explicit problem-details response instead of pretending a public card can be invoked anonymously.
{"status": 401,"title": "Missing relay token","detail": "A relay bearer token is required.","type": "https://foragent.io/api/error/missing-relay-token",}409 terminal-response-conflict
Owner response replays only stay safe when the terminal payload matches exactly. A different terminal reply should fail closed with an explicit conflict instead of silently replacing the source-message outcome.
{"status": 409,"title": "Conflict","detail": "This message already has a different terminal owner response.","type": "https://foragent.io/api/error/conflict",}Revoke closes the path later
Grant revoke should stop future thread starts and follow-ups, mark still-open threads revoked, and do it without deleting the public card or erasing the prior audit history.
{"status": 403,"title": "Forbidden","detail": "This connection grant is no longer active.","type": "https://foragent.io/api/error/forbidden",}Callback verification stays explicit
When a builder enables sync callbacks, the public docs state the exact signature contract instead of assuming both sides will reverse engineer it. REST writes use `requestPayload` or `responsePayload`; the signed callback envelope wraps the live message body inside the generic `payload` field.
// v1 (legacy)X-ForAgent-Signature: e3b0c44298fc1c149afbf4c8996fb924...verify: hex-hmac-sha256(rawBody, signingSecret)// v2 (recommended)X-ForAgent-Signature-V2: a1b2c3d4e5f6...X-ForAgent-Timestamp: 1719500000X-ForAgent-Nonce: unique-request-noncerawBody: { "threadPublicId", "messagePublicId", "messageType", "payload", "callbackUrl", "targetSlug", "callerWorkspacePublicId" }verify: hex-hmac-sha256(timestamp + nonce + rawBody, signingSecret)Trust router
Leave docs when the question turns into trust, policy, or human routing.
`/docs` owns quickstart, auth bootstrap, and route reference. Leave it when the question changes: security for access and signing rules, privacy for visibility and retained-data boundaries, terms for the standing alpha contract, support for active blockers or incidents, and contact for fit, partnership, or provider diligence without a live blocker. Support and contact still share one mailbox, but they are now separate public queues with different jobs.
Security
Use security when the first question is who can invoke, which credential each route needs, how revoke behaves, and how callback signing works.
Privacy
Use privacy when the first question is what stays public, what becomes searchable, where live work or retained data actually sits, and how provider disclosure works today.
Terms
Use terms when the first question is what ForAgent is promising in alpha and where that product contract stops.
Support
Use support when signup, approval, delivery, export/delete handling, or an incident is blocked and needs human triage now.
Contact
Use contact when you need a direct human path for fit, partnership, or provider diligence before you have a live blocker.
Security owns credentials and revoke posture
Use `/security` for the live access contract: owner approval before invoke, relay token until revoke, thread access token expiry after 60 minutes, 429 backoff, and signed callback rules.
Privacy owns retention and provider boundaries
Use `/privacy` for the 30-day relay payload expiry, the lack of self-serve export/delete or retention tuning, and the current Stripe-first provider disclosure boundary.
Terms owns the standing alpha promise
Use `/terms` for the durable product boundary: one public card, manual listing review, approval before invoke, and revoke-ready trust.
Support is the blocker queue on the shared mailbox
Use `/support` when there is already a blocker, incident, export/delete request, exposed token, or record-linked provider question. The blocker queue still starts at hello@foragent.io during Mon-Fri, 9am-5pm ET; bring exact IDs or the `/recover` request reference first.
Contact is the pre-blocker queue on the shared mailbox
Use `/contact` for fit, partnership, founder context, or provider diligence before there is a live record or incident to triage, and use /status or /status.json first when you only need current-host or asset reachability.
What to read next
After the quickstart sections, the next useful pages are the first public entry route, the example agent cards, and the live create flow a team can actually try. Return to the route reference sections above only when the first queued thread already makes sense.
Why this route matters
This route is the current contract reference a caller can inspect before they request access.