Orchestrate the public API surfaces
Orbit ships a family of unauthenticated endpoints under/api/v1/public/* that exist for one reason: your customer’s browser (or your customer’s customer) can drive a self-service flow — check a callback, sign a consent form, answer a survey, search your help center — without you proxying every click through your backend.
This guide is the orchestration map. It covers the queue-callback lifecycle end to end, then runs the same pattern across consent, surveys/CSAT, help-center search, and the .well-known discovery documents external agents resolve against. The last two sections explain the signed-token model these endpoints share, and which org-scoped endpoints must never leave your server.
Every path below is relative to https://api.orbit.devotel.io/api/v1.
Public endpoints are rate-limited per IP and intentionally unauthenticated (
security: [] in the OpenAPI spec). They carry no API key, no session, and no tenant plugin. Never send a live secret key (dv_live_sk_…) from a browser to “protect” one — you leak the key.When public surfaces matter
The public surface exists wherever your end user (a caller, a recipient, a visitor, an external agent) needs to act directly:
Pick the token-scoped surface when the grant is a single resource (one callback, one survey link, one conversation rating). Pick the tenant-scoped surface when the configuration itself is published (consent config, queue booking window).
Public-callback flow (worked sequence)
This is the canonical orchestration pattern: your server creates the resource, the public token carries the grant, the customer’s browser drives the lifecycle.- Create the callback server-side. Your backend (with a real API key) requests a callback — either via the queue endpoint
POST /api/v1/voice/callbacks(or the disposition-callback rule on the queue), or the virtual-holdcallback-in-queueflow. The creation response returns the callback id. - Sign a public token and hand it to the customer. The platform signs a compact HMAC token that embeds the callback id and the tenant it belongs to (the tenant schema travels inside the signed payload — it is not a query param). In the default flow the dispatch notification itself texts the caller a link like
https://api.orbit.devotel.io/api/v1/public/callbacks/<token>. If you build your own “call me back” UI, generate the link the same way and hand the token to the browser. - The customer reads status. From the browser:
Returns the current state (
pending,dialing, terminal). Poll it to keep the “we’ll call you back at …” card up to date. - The customer cancels or reschedules. Same token, same no-auth model:
Both mutations are guarded: a callback already dialing or in a terminal state is never altered, and a re-tap of cancel returns “already canceled” instead of an error. A 404 (
CALLBACK_NOT_FOUND) means the row is gone or the token doesn’t validate — render a friendly expired-link page, not a retry loop. - Render the landing page if you don’t want to build UI.
GET /public/callbacks/{token}(without a suffix) returns a self-contained HTML status page with cancel/reschedule buttons — suitable as the direct tap target of the SMS link.
Consent capture
The consent gate has two calls and one rule: opt-in needs proof of ownership; opt-out never does.- Fetch the tenant’s published form config so your page renders the tenant’s own branding, copy, and allowed channels:
Unknown or disabled tenants return
enabled: falsewith empty branding — the endpoint cannot be used to enumerate which tenant ids exist. Treatenabled: falseas “render your own fallback form,” not as an error to surface. - Post the consent decision:
granted: false(opt-out) is one-click and needs no proof token.granted: true(opt-in) is only recorded asopted_inwhen the signed proof token delivered to the recipient’s own address (email or phone) validates — this is what stops someone from opting in a number they don’t control. The response lists one consent-record id per channel written.
Surveys and CSAT
Both are token-scoped: the link itself is the grant. Survey (NPS/CSAT) links — the score row (0–10) plus optional comment, idempotent per link:data.ok: true. An invalid or expired token returns 403 on submit (404 HTML on the page GET).
Conversation rating (CSAT 1–5 / NPS 0–10) — same token model against one conversation:
data.already_submitted: true instead of overwriting.
Administrative setup (scorecards, auto-survey rules) still happens in the dashboard or the org-scoped API — see Post-call surveys. The public endpoints here are only the recipient-facing capture step.
Help-center search
Point your help widget’s search box at the public search endpoint instead of standing up your own index:slug, title, summary, updated_at, and view_count. The org query parameter selects the published help center — it scopes to public, published articles only, so no unpublished draft leaks. From the article page you can route a “was this helpful?” vote to POST /public/help/feedback (visitor IP is hashed, never stored raw) and escalate to POST /public/help/tickets when self-serve fails. Set up the help center itself in Brand and launch your hosted help center.
Discovery surface: .well-known for external agents
External-agent handoffs resolve against public, edge-cached discovery documents — no shared secret, no onboarding:
GET /public/.well-known/jwks.json— the EdDSA (Ed25519) JWK Set that verifies Orbit’s A2A agent identity tokens and signed AgentCards. Public key material only; an emptykeysarray means signing isn’t provisioned yet.GET /public/.well-known/ans/registry/lookup— Agent Trust Registry lookup: pass?org=<slug>plus one ofansURI,agentId, or E.164phoneto get the tenant’s brand-identity verification status, STIR/SHAKEN attestation tiers, and the agent’s scoped capability surface. Unknown orgs and disabled lookups collapse to the same 404.GET /public/mcp/.well-known/mcp.json— the hosted MCP server manifest (Streamable-HTTP transport at/api/v1/mcp) so MCP-aware agent hosts auto-configure without a hand-shared URL.GET /public/.well-known/http-message-signatures-directory— the public-key directory for verifying HTTP Message Signatures on Orbit-signed outbound requests.
Token model and scoping guarantees
All token-scoped public endpoints share one construction, and it is worth holding deliberately:- The token is the whole grant. It is
base64url(payload).base64url(HMAC-SHA256(secret, payload)). The payload carries the resource id and the tenant it belongs to, so a browser cannot reach across tenants by editing the URL — any tamper fails a constant-time HMAC compare. There is no session, no cookie, no tenant plugin. - Hold the token in the URL, nowhere else. Deliver it in the SMS/email link (or in the DOM of your served page) and let the customer carry it back. Do not log it, do not mirror it into your analytics, and do not persist it in your own database — you already have the callback id server-side. If the link leaks, the blast radius is one resource’s lifecycle (cancel/reschedule), not org data.
- Expired or forged token → 404/403, never a data leak. A bad token on the callback surface returns
CALLBACK_NOT_FOUND(404); survey submit returns403. Generate the link, hand it over, then forget it. - Scoping is per-link. One token maps to one callback, one survey invitation, one conversation rating. A customer holding a valid token can act on that resource only — never on the org’s list of callbacks, other customers, or queue configuration.
status, surface already_submitted / already_canceled responses as success, and re-issue the grant server-side when you need a longer window — never extend it client-side.
What NOT to call from the browser
The public surface above is the complete list of endpoints your customer’s browser may call. Everything else on/api/v1 is org-scoped and expects an X-API-Key header:
- Never put a
dv_live_sk_*key in browser code. Anyone with devtools can read it and drain your wallet. Server-only. - Org-scoped siblings of the same resource stay server-side.
GET/DELETE /voice/callbacks/{id},PATCH /voice/callbacks/{id}, queue configuration, survey definitions, consent-management settings — these leak or mutate org data. The public token endpoints exist precisely so the browser does not need the org-scoped route; don’t “simplify” by exposing the keyed one. - Don’t proxy-re-log the public endpoint. If you must mediate (e.g. embedding the status check in a logged widget), call the public endpoint from the browser directly — bouncing it through your backend adds latency and gains nothing, because the token is already the ceiling of access.
/public/* list, file it as a gap — don’t work around it by shipping a key.
FAQ
Are public endpoints rate-limited? Yes — every public endpoint is IP-rate-limited (the sameRATE_LIMIT_PUBLIC_UNAUTH class as the other public landing pages). Cache .well-known documents, poll callback status on a slow cadence (once per second at most), and treat a 429 as a signal to back off, not to retry harder.
What if a customer loses the link? Re-issue server-side: your backend still knows the callback id and can mint a new token the same way the dispatch notification did.
Can I brand the HTML landing pages? The callback landing page renders the queue/tenant branding you configured for the public-booking surface; consent config carries its own branding block. Anything beyond that is your own hosted page hitting the JSON endpoints.