Published apps
#What a published app is
A published app is a single agent exposed as a small, branded web app (its own template, logo, and colours) that an end user fills in directly — no API key, no console access. Under the hood it still runs an agent in your workspace and charges your workspace's credits; the app just adds its own access gate and per-day usage caps on top.
The Drop & extract template is the first one: a branded header, a drop zone (accepted file types come from the underlying agent's inputSpec), any extra input fields the preset defines, and a result page showing the structured output as a table plus a JSON/CSV download.
#Creating and managing an app
Management routes require scope apps:manage:
curl -X POST "https://api.agentflowbind.com/v1/apps" \
-H "Authorization: Bearer afb_live_xxxxxxxxxxxx" -H "Content-Type: application/json" \
-d '{
"agentId": "3c1a9e40-...-77bd",
"template": "drop-and-extract",
"access": { "mode": "password", "password": "correct-horse-battery-staple" }
}'{
"id": "d4a1e9c0-...",
"workspaceId": "1a2b3c4d-...",
"profileId": "3c1a9e40-...-77bd",
"template": "drop-and-extract",
"slug": "drop-and-extract-9f2c1e4b",
"theme": {},
"access": { "mode": "password", "requireBotCheck": false },
"domain": null,
"customDomain": null,
"domainVerification": null,
"caps": {},
"endUserAuth": null,
"status": "draft",
"createdAt": "2026-03-14T09:00:00.000Z",
"updatedAt": "2026-03-14T09:00:00.000Z"
}Omit slug to get one generated from template; supply your own (3–63 lowercase letters/digits/hyphens) and a taken one returns 409 slug_taken. A password's hash is never returned (nor is a link mode's linkToken regenerated unless you pass access.regenerateLinkToken: true on a PATCH).
curl "https://api.agentflowbind.com/v1/apps" -H "Authorization: Bearer afb_live_xxxxxxxxxxxx"
curl -X PATCH "https://api.agentflowbind.com/v1/apps/d4a1e9c0-..." -H "Authorization: Bearer afb_live_xxxxxxxxxxxx" -H "Content-Type: application/json" -d '{"caps": {"dailyRuns": 50, "dailyCredits": 2000, "maxFileMb": 10}}'
curl -X DELETE "https://api.agentflowbind.com/v1/apps/d4a1e9c0-..." -H "Authorization: Bearer afb_live_xxxxxxxxxxxx"
# Go live / take offline — a draft app's public routes 404 until published
curl -X POST "https://api.agentflowbind.com/v1/apps/d4a1e9c0-.../publish" -H "Authorization: Bearer afb_live_xxxxxxxxxxxx"
curl -X POST "https://api.agentflowbind.com/v1/apps/d4a1e9c0-.../pause" -H "Authorization: Bearer afb_live_xxxxxxxxxxxx"#Access modes
| Mode | POST .../session body |
Notes |
|---|---|---|
public |
{} |
No gate at all |
link |
{ "linkToken": "..." } |
Must match the app's own access.linkToken |
password |
{ "password": "..." } |
Checked against the sealed hash set at creation/update |
magic_link |
{ "email": "..." } |
Sends a sign-in email; redeem with .../session/verify |
#Custom domains
curl -X POST "https://api.agentflowbind.com/v1/apps/d4a1e9c0-.../domain" \
-H "Authorization: Bearer afb_live_xxxxxxxxxxxx" -H "Content-Type: application/json" \
-d '{ "domain": "invoices.acme.example" }'{ "domain": "invoices.acme.example", "txtRecordName": "_afb-verify.invoices.acme.example", "txtRecordValue": "9f2c1e4b7a...", "app": { "...": "..." } }Create the TXT record at txtRecordName with txtRecordValue, then:
curl -X POST "https://api.agentflowbind.com/v1/apps/d4a1e9c0-.../domain/verify" -H "Authorization: Bearer afb_live_xxxxxxxxxxxx"which does a live DNS lookup and returns 422 domain_verification_failed if the record isn't found yet — retry once DNS has propagated.
#Usage
curl "https://api.agentflowbind.com/v1/apps/d4a1e9c0-.../usage" -H "Authorization: Bearer afb_live_xxxxxxxxxxxx"{
"day": "2026-03-14",
"today": { "runs": 12, "credits": 340 },
"caps": { "dailyRuns": 50, "dailyCredits": 2000, "maxFileMb": 10 },
"retryAfterSeconds": 51840,
"days": [ { "day": "2026-03-13", "runs": 41, "credits": 1180 } ]
}retryAfterSeconds is how long until the daily cap resets (UTC midnight) — the same value a capped end user's 429 would carry.
#The public runtime (what an end user's browser calls)
No API key, no session cookie — identified entirely by :slug (or a verified custom domain), then an afb_app_session cookie for everything past the access gate. Every route is rate-limited per client IP (60 req/min), separately from the key-based limits in Authentication & scopes.
# 1. Read the app's public config
curl "https://api.agentflowbind.com/v1/public/apps/drop-and-extract-9f2c1e4b"
# -> { "slug", "template", "theme", "access": { "mode", "requireBotCheck" }, "locale", "inputSpec" }
# 2. Open a session (here: password mode) — sets afb_app_session
curl -c cookies.txt -X POST "https://api.agentflowbind.com/v1/public/apps/drop-and-extract-9f2c1e4b/session" \
-H "Content-Type: application/json" -d '{ "password": "correct-horse-battery-staple" }'
# 3. Upload, then create a run — same shapes as the authenticated /v1/files and /v1/runs
curl -b cookies.txt -X POST "https://api.agentflowbind.com/v1/public/apps/drop-and-extract-9f2c1e4b/files" \
-H "Content-Type: application/pdf" -H "X-Filename: invoice.pdf" --data-binary @invoice.pdf
curl -b cookies.txt -X POST "https://api.agentflowbind.com/v1/public/apps/drop-and-extract-9f2c1e4b/runs" \
-H "Content-Type: application/json" -d '{ "input": { "fileIds": ["..."] } }'
# -> 202 { "id", "status": "queued" }
# 4. Read it back — only through the same session that created it
curl -b cookies.txt "https://api.agentflowbind.com/v1/public/apps/drop-and-extract-9f2c1e4b/runs/{id}"
curl -b cookies.txt "https://api.agentflowbind.com/v1/public/apps/drop-and-extract-9f2c1e4b/runs/{id}/output"GET .../runs/:id/events streams the same SSE shape as the authenticated run events endpoint, capped at 20 concurrent streams per app. A run created this way lands in the owner workspace with input.fields.sourceApp set to the app's slug — it shows up in the owner's own GET /v1/runs list like any other run, and (per row) is only ever readable back through the exact app session that created it, never through a different session or slug.
#Errors specific to published apps
| HTTP | code |
When |
|---|---|---|
| 401 | app_session_required |
Called files/runs/run-read without a valid afb_app_session cookie |
| 401 | invalid_app_credentials |
Wrong password, link token, or magic-link token |
| 400 | bot_check_failed |
access.requireBotCheck is set and the bot-check token was missing/invalid |
| 429 | app_cap_reached |
The app's caps.dailyRuns/dailyCredits for today is exhausted — Retry-After set to seconds until UTC midnight |
| 503 | app_unavailable |
The underlying agent has no published version, or the owner workspace's credit balance can't cover the run (never disclosed to the end user as a billing error) |
| 422 | upload_rejected |
The upload scanner flagged the file |
| 404 | not_found |
Unknown slug, or an app that's draft/paused (indistinguishable from "never existed") |
#Data handling
A published app processes the end user's upload the same way any other run does: through your workspace's chosen model providers, billed to your workspace. The app's own /about-data page explains this to the end user in plain terms — see Data & providers for the underlying provider-transparency model that page draws from.