API reference
Build on Reapdat from your own systems. One key, one base URL, and an integration per product area.
https://api.reapdat.com/api/v1Overview#
One REST API, one credential, JSON in and JSON out. Every endpoint is scoped to your account by the credential you send, which is why no request in this reference carries an account id — there is no way to ask for someone else's data.
https://api.reapdat.com/api/v1| Request body | application/json, except where a file is being uploaded |
| Timestamps | ISO 8601, UTC. A naive value on input is read as UTC |
| Errors | Always { "detail": "..." }, with a meaningful status code |
| Versioning | The /v1 prefix. Breaking changes ship under a new prefix |
Authentication#
Send an admin API key on every request. One header — no login call, no token to refresh, no expiry to handle.
X-API-Key: ua_admin_xxxxxxxxxxxxxxxxxxxxxxxxxxxxA key may also be sent as Authorization: Bearer ua_admin_... — keys are recognised by their ua_ prefix, so either header carries one.
Find your key under Portal → Integrations → API Keys. It is shown once, because only a hash is stored: a lost key is regenerated, never recovered. Revoking one takes effect immediately and leaves your other keys working.
A browser session (the portal's own sign-in) is accepted too, on every endpoint here.
Errors#
| Status | Means |
|---|---|
| 400 | The request was understood and refused. detail says why |
| 401 | No credential, or a malformed / expired browser session |
| 403 | The API key is unknown, revoked, expired, or is not an admin key |
| 404 | No such record on your account. Never confirms one exists elsewhere |
| 422 | The body did not validate. detail names the offending field |
| 429 | Rate limited. Back off and retry |
{
"detail": "Admin API key required"
}Chat Links API
livePurpose-built links — a shareable chat page per campaign, role or product, each with its own isolated knowledge base.
What a chat link is#
A chat link is a self-contained assistant at its own URL. It starts empty and only knows what you give it, so a link built for one role never answers from another's material — and it can inherit your main knowledge base on top when you want both.
- The URL is the credential. It carries a 192-bit token, and the page behind it needs no login.
- Revocable and expiring. Revoke a link and the URL 404s immediately, for everyone holding it.
- Isolated by default. Set
inherit_main_kbto also answer from your main knowledge base.
The Link object#
Returned by every management endpoint except DELETE.
{
"id": "e92c2046-c5c7-46e0-883b-13538feb6037",
"label": "Toronto campaign",
"tag": "q4",
"tags": ["q4", "toronto"],
"token": "bZY3eWQ80eOn-Pn3F6BPjLlq43q-yUbF",
"url": "https://agent.reapdat.com/acme/bZY3eWQ80eOn-...",
"agent_name": null,
"greeting": null,
"primary_color": null,
"language": null,
"channels": ["chat"],
"inherit_main_kb": false,
"created_via": "api",
"status": "active",
"is_active": true,
"is_expired": false,
"revoked_at": null,
"expires_at": "2026-12-31T23:59:59+00:00",
"use_count": 0,
"last_used_at": null,
"created_at": "2026-09-12T15:41:55.380471+00:00",
"kb_sources": {},
"kb_chunks": 0,
"kb_docs": 0
}| field | type | notes |
|---|---|---|
| id | string | UUID. Used in the path of every per-link endpoint. |
| label | string | Internal name. Not shown to visitors. |
| tag | string | null | Legacy single tag. Always equals tags[0], or null. |
| tags | string[] | Shown on Needs You items sourced from this link. |
| token | string | 32 chars. The credential in the public URL. |
| url | string | The shareable link, fully assembled. Do not build it yourself. |
| agent_name | string | null | null inherits the account's. |
| greeting | string | null | null inherits the account's welcome message. |
| primary_color | string | null | #RRGGBB. null inherits. |
| language | string | null | null inherits. |
| channels | string[] | null | Subset of chat, call, book. null inherits all enabled channels. Can only narrow them — a link cannot enable a channel the account has disabled. |
| created_via | string | api when created with an API key, portal when created in the browser. Never null. Set at creation and never changed — renaming an API-made link in the portal does not re-attribute it. |
| inherit_main_kb | boolean | false — answers only from this link's own knowledge. true — its own and the account's main one. See Knowledge scope. |
| status | string | active | expired | revoked. Derived; see Lifecycle. |
| is_active | boolean | status == "active" |
| is_expired | boolean | status == "expired" |
| revoked_at | string | null | Set when revoked. |
| expires_at | string | null | null = permanent. |
| use_count | integer | Times the public page has been opened. |
| last_used_at | string | null | |
| created_at | string | |
| kb_sources | object | {source: chunk_count} for this link's own documents. |
| kb_chunks | integer | Searchable passages in this link's own knowledge base. |
| kb_docs | integer | Documents in this link's own knowledge base. |
Create a link#
/chat-linksCreates an empty link. Returns the Link object, including the url to share.
Every field is optional — posting {} creates a working link that inherits the account's persona, colour, language and channels.
{
"label": "Careers",
"tags": ["careers", "hiring"],
"agent_name": "Alex",
"greeting": "Hi - ask me about the roles we have open.",
"primary_color": "#7C3AED",
"language": "en",
"channels": ["chat", "call"],
"inherit_main_kb": false,
"expires_at": "2026-12-31T23:59:59Z"
}| field | type | constraint |
|---|---|---|
| label | string | ≤120. Defaults to "Untitled link". |
| tags | string[] | ≤8 entries, ≤40 chars each. Trimmed, de-duplicated case-insensitively, then capped. |
| tag | string | ≤60. Legacy. Used only when tags is absent. |
| agent_name | string | ≤120 |
| greeting | string | ≤2000 |
| primary_color | string | ≤7, #RRGGBB |
| language | string | ≤8 |
| channels | string[] | Subset of chat, call, book. Omit to inherit. `[]` is rejected with a 400. |
| inherit_main_kb | boolean | Default false. |
| expires_at | string | ISO 8601. Omit or null for permanent. |
Create a link and its knowledge at once#
/chat-links/bundleCreates the link and loads its knowledge base in one call. multipart/form-data.
Three parts: link (the link itself, as JSON), knowledge (an array of entries, as JSON) and files (up to five documents). Only link is required.
POST /api/v1/chat-links/bundle
X-API-Key: ua_admin_...
Content-Type: multipart/form-data
link = {"label":"Lead iOS Developer",
"tags":["REQ-43674","ios"],
"channels":["chat"],
"inherit_main_kb":true}
knowledge = [{"content":"Contract, remote. Denver CO. 8+ years."},
{"question":"Do you sponsor visas?",
"answer":"Not for this role."}]
files = @role-spec.pdf{
"link": {
"id": "b444447b-8fab-4dc9-ad69-9dfa1d20d03b",
"label": "Lead iOS Developer",
"tags": ["REQ-43674", "ios"],
"token": "EDFIqR3asRlCSL0FY0kB7_cLMGxGF0_B",
"url": "https://agent.reapdat.com/your-slug/EDFIqR3asRlC...",
"channels": ["chat"],
"inherit_main_kb": true,
"status": "active",
"kb_docs": 0
},
"knowledge": {
"job_id": "105e015e-e8ab-4b7e-8fbb-ce313691692d",
"status": "running",
"queued": 3
}
}Watch the knowledge load#
/chat-links/bundle/{job_id}Progress of the background ingest. Scoped to your account.
{ "status": "running", "queued": 3, "ingested": 0, "failed": 0, "errors": [] }Add knowledge later#
Pass the link's id as link_id and the content is scoped to that link alone. Omit it and the content goes to your main knowledge base instead.
/knowledge/portal/ingestA question and answer, or a block of text.
POST /api/v1/knowledge/portal/ingest
X-API-Key: ua_admin_...
Content-Type: application/json
{
"content": "Interviews are two rounds: a screen then a panel.",
"link_id": "b444447b-8fab-4dc9-ad69-9dfa1d20d03b"
}{
"success": true,
"doc_id": "4bb31953-...-62aee3da4241",
"chunks": 1
}/knowledge/portal/uploadA document. PDF, DOCX, TXT, CSV, XLSX, JSON or an image. multipart/form-data.
curl -s -X POST "https://api.reapdat.com/api/v1/knowledge/portal/upload" \
-H "X-API-Key: ua_admin_..." \
-F file=@benefits.pdf \
-F "link_id=b444447b-8fab-4dc9-ad69-9dfa1d20d03b"What you can upload#
| formats | max size | |
|---|---|---|
| Documents | .pdf .docx .txt .csv .xlsx .json | 10 MB |
| Images | .jpg .jpeg .png .webp .gif | 5 MB |
Images are checked against their magic bytes, so a renamed extension is rejected, and are auto-captioned so the agent can answer about what is in them.
- CSV and XLSX need either
questionandanswercolumns, or acontentcolumn. - JSON must be an array of objects, or a single object.
- TXT is split per non-empty line; PDF and DOCX per paragraph.
- A scanned PDF with no extractable text is rejected — there is no OCR.
| code | cause |
|---|---|
| 400 | Unsupported extension, an empty file, a CSV/XLSX missing its columns, or an unreadable / image-only PDF |
| 402 | Plan document limit reached — counted across the whole account, not per link |
| 404 | link_id unknown, or it belongs to another account |
| 413 | Over the size cap. The message names the limit and the actual size |
| 503 | Embedding service not configured — server side, retry later |
Knowledge scope#
inherit_main_kb decides what a link can read. It is the one setting that changes what the agent is able to say.
| value | the link answers from |
|---|---|
| false *(default)* | only the documents loaded onto this link |
| true | this link's documents and the account's main knowledge base |
When true, both are searched and merged by relevance, with the link's own material winning an equal score — the specific answer outranks the general one. At most 5 passages reach the agent per turn, before and after merging.
Scoping applies on chat and on voice alike. On chat the page sends the link with every message; on a voice call the link is stamped onto the knowledge tool when the call is created, so the system prompt summary and every mid-call lookup stay scoped.
Either way the link still knows the account-level basics — your agent instructions, tone, and business facts such as hours, services and contact details. Those are not knowledge-base content and are never link-scoped.
List, update, revoke#
/chat-linksEvery link on the account, newest first, with its knowledge-base counts. Not paginated — all links are returned.
{
"links": [ /* Link, newest first */ ],
"count": 3,
"limit": 20 // -1 = unlimited
}/chat-links/{id}Applies only the keys you send. An explicit null clears a field.
/chat-links/{id}/revokeThe URL stops working at once. Sibling links are untouched.
/chat-links/{id}Deletes the link and its isolated knowledge base.
POST /api/v1/chat-links/b444447b-.../revoke
X-API-Key: ua_admin_...{
"label": "Lead iOS Developer",
"status": "revoked",
"is_active": false,
"revoked_at": "2026-09-17T02:52:32.122406+00:00"
}status is derived and is one of active, expired or revoked — expiry beats the active flag. Setting a future expires_at on an expired link revives it, unless it was revoked by hand.
Lifecycle#
create ──▶ active ─── expires_at passes ──▶ expired ──▶ future/null expires_at ──▶ active
│ (unless revoked by hand)
└── revoke, or is_active:false ──▶ revoked ──▶ is_active:true ──▶ active| expired | revoked | |
|---|---|---|
| caused by | the date passing | an API call |
| revoked_at | null | stamped |
| public URL | 404 | 404 |
| recovers on a new expires_at | yes | no — needs is_active: true |
status is computed on every read. When both apply, revoked wins — so a link that expired and was then revoked does not come back by extending the date.
The public side#
No authentication. The token in the path is the credential.
| endpoint | |
|---|---|
| GET agent.reapdat.com/{slug}/{token} | The link's chat page. This is the value of url. |
| GET /chat-page/l/{token} | The same page, legacy URL form. Still supported. |
| GET /chat-page/l/{token}/qr | QR code for a link. |
| GET /chat-page/{slug}/qr | QR code for the account's main chat page. |
| GET /chat-page/{slug}/info | { name, agent_name, enabled, slug }. Account-level. |
| QR param | values | default |
|---|---|---|
| format | png | svg | png |
| size | 100–1000 | 400 |
The QR encodes the url form, so codes printed before the URL style changed still resolve. An unknown, revoked, expired or inactive link returns the same 404 Chat page not found in every case — the response never distinguishes them.
Limits#
| Links per account | 20 by default. The effective value is limit on GET /chat-links; -1 is unlimited. |
| Documents | Your plan's cap, counted across the whole account |
| Files per bundle | 5. Send the rest to /knowledge/portal/upload with the link's id |
| Tags per link | 8, ≤40 chars each |
| Channels | At least one; [] is rejected, null inherits |
| code | cause |
|---|---|
| 400 | channels: [] — select at least one of chat, call or book |
| 403 | Chat link limit reached. Delete one, or ask an admin to raise the cap |
| 404 | Unknown link id, or it belongs to another account |
| 422 | Malformed JSON, or a field of the wrong type or over its length |
The whole flow, as a script#
BASE=https://api.reapdat.com/api/v1
AUTH="X-API-Key: ua_admin_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# create a link AND load its knowledge in one call
OUT=$(curl -s -X POST "$BASE/chat-links/bundle" -H "$AUTH" \
-F 'link={"label":"Careers","tags":["careers"],"channels":["chat","call"]}' \
-F 'knowledge=[{"content":"We hire across engineering and delivery."}]' \
-F 'files=@roles.pdf')
ID=$(echo "$OUT" | jq -r .link.id)
echo "$OUT" | jq -r .link.url # <- store this now
# watch the knowledge load
curl -s "$BASE/chat-links/bundle/$(echo "$OUT" | jq -r .knowledge.job_id)" -H "$AUTH"
# add to it later - same key, no login
curl -s -X POST "$BASE/knowledge/portal/upload" -H "$AUTH" \
-F file=@more-roles.pdf -F "link_id=$ID"
# revoke when the role closes
curl -s -X POST "$BASE/chat-links/$ID/revoke" -H "$AUTH"Something missing, or an endpoint behaving differently from this page? Tell us — the reference is written from the code, so a mismatch is a bug in one of them.