API Reference
Reference for the implemented public REST API, hosted widget stream transport, and the dashboard routes that often look
API Reference
Reference for the implemented public REST API, hosted widget stream transport, and the dashboard routes that often look similar but are not the same API surface.
Start with the public API domain
Use https://api.kravos.ai/v1 for server-to-server REST integrations. Routes on your Kravos.ai web app
domain are either hosted widget transports or dashboard session routes unless this page says otherwise.
Quickstart
- Create an agent-scoped API key from API Keys.
- Give the key the smallest permission set for the workflow:
chat,retrieve,ingest, ortrace_read. - Call the public API base URL with either
Authorization: Bearer ...orx-api-key.
curl -X POST https://api.kravos.ai/v1/chat \
-H "Authorization: Bearer spk_..." \
-H "Content-Type: application/json" \
-d '{"message":"What are your opening hours?"}'
Successful JSON responses use this shape:
{
"data": {},
"meta": {
"requestId": "req_abc123"
}
}
Base URLs
| Surface | Base URL | Use for |
|---|---|---|
| Public REST API | https://api.kravos.ai/v1 | Server-to-server chat, retrieval, sources, conversations |
| Hosted web app / widget | https://your-domain.com/api | Widget SDK routes, dashboard routes, hosted stream transport |
| Deprecated dashboard shim | https://your-domain.com/api/v1 | Only POST /chat and POST /retrieve shims that return 410 |
Authentication
Public API requests authenticate with either header format:
Authorization: Bearer spk_...
or:
x-api-key: spk_...
Agent keys created in the dashboard are scoped to one agent and currently use the spk_ raw-key prefix. Org-scoped keys
exist for organization automation and MCP-style platform access, but /v1 and /api/sdk routes reject org keys because
those routes must resolve a single agent.
| Permission | Used for |
|---|---|
chat | POST /v1/chat, POST /v1/chat/stream, conversation reads |
retrieve | POST /v1/retrieve |
ingest | Source reads/writes, sync jobs, ingestion-job reads |
ingest.read | Fine-grained route check; base ingest keys satisfy it |
ingest.write | Fine-grained route check; base ingest keys satisfy it |
usage.read | Fine-grained route check; base public keys satisfy it |
trace_read | GET /v1/agent-runs/:runId |
Permission compatibility
Permissions are checked with directional compatibility so keys created through either vocabulary keep working:
- Alias groups (bidirectional): holding any member of a group satisfies a requirement for any other member.
chat↔chat:write;retrieve↔sources:read↔retrieval:read;ingest↔ingest.read↔ingest.write↔sources:write;skill_fetch↔skills:read;usage.read↔usage:read. - Base permission fallback: a key holding
ingestsatisfies fine-grainedingest.read/ingest.writechecks. - One-directional legacy grant: keys created with
chat,retrieve, oringesthistorically receivedusage.read; that grant is preserved, so those keys satisfyGET /v1/usage. The reverse never holds —usage.readdoes not grant chat access. - A literal
*permission satisfies every check.
Allowed origins
The API service accepts CORS requests, then the key allowlist decides whether the request is allowed:
- empty
allowedOriginsmeans any origin is accepted - a non-empty list requires an
Originheader - the
Originvalue must exactly match one configured URL - blocked origins return
403; revoked, inactive, or expired keys return401
This lets browser-based integrations use public endpoints without turning allowed origins into a hard product cap.
Request IDs
Send x-request-id when you have one. If you omit it, the API creates one. JSON responses include meta.requestId, and
the x-request-id response header is exposed for browser clients.
Errors
Public /v1 JSON errors use this envelope:
{
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid or expired API key",
"details": {}
},
"meta": {
"requestId": "req_abc123"
}
}
details is present only for validation failures or other structured errors.
Runtime failures follow a safe rule: 4xx and 504 responses from the agent runtime are propagated with their
machine-readable codes, while any other runtime failure returns 500 TURN_FAILED without internal details. The
Platform MCP chat tools follow the same rule and surface runtime 5xx failures as a generic tool error that references
the request ID instead of leaking internals.
| Code | Typical status | Meaning |
|---|---|---|
UNAUTHORIZED | 401 | Missing, invalid, expired, inactive, or revoked API key |
FORBIDDEN | 403 | Origin blocked, missing permission, or route needs agent key |
PLAN_RESTRICTED | 403 | Current plan does not include public API access |
VALIDATION_ERROR | 400 | Request body or query string did not match the route schema |
NOT_FOUND | 404 | Resource does not exist for the key's agent |
LIMIT_EXCEEDED | 429 | Source or monthly ingestion-job entitlement limit reached |
TURN_LIMIT_EXCEEDED | 429 | Chat turn quota reached |
CONVERSATION_NOT_FOUND | 404 | conversationId does not exist for the key agent |
CONVERSATION_NOT_ACTIVE | 409 | Conversation exists but is not in AI_ACTIVE state |
END_USER_NOT_FOUND | 404 | endUserId does not exist in the tenant |
TURN_FAILED | 500 | Chat runtime failed |
RUNTIME_TIMEOUT | 504 | Runtime timed out |
GONE | 410 | Dashboard-domain shim moved to api.kravos.ai |
UNSUPPORTED_ATTACHMENT_TYPE | 400 | Hosted stream attachment not supported by selected model |
UNSUPPORTED_TYPE | 400 | Hosted stream file MIME type is not allowed |
FILE_TOO_LARGE | 400 | Hosted stream file exceeds the implemented per-type limit |
INTERNAL_ERROR | 500 | Unexpected server failure |
Pagination And Filtering
List endpoints use cursor pagination on the public API.
| Endpoint | Query params | Notes |
|---|---|---|
GET /v1/sources | limit, cursor, status, type | limit defaults to 20 and is capped at 100 |
GET /v1/conversations | limit, cursor, endUserId | limit defaults to 20 and is capped at 100 |
GET /v1/conversations/:id/messages | limit, cursor | limit defaults to 50; there is no history cap |
GET /v1/agent-runs/:id | limit, cursor | Sequence cursor; each page defaults to 20 and is capped at 100 |
nextCursor is null when there are no more results. Use enum values for status and type; invalid filters are not
applied by the current implementation. Agent-run cursors are the last event sequence. Continue requesting pages until
hasMore is false; there is no total trace retrieval cap.
Choose The Right Endpoint
- use
POST /v1/chatwhen you want one complete reply - use
POST /v1/chat/streamwhen the client should render tokens as they arrive - use
POST /v1/retrievewhen you only want source chunks, not AI generation - use
/v1/sources*when you are managing ingestion programmatically - use
/v1/conversations*when you need API-created transcript history - use
GET /v1/agent-runs/:runIdwhen you need a redacted execution trace for one completed or running turn - use
GET /v1/usagewhen you need current-period usage counters
Usage Semantics
GET /v1/usage is scoped to the key agent: data.turns.used counts platform turns recorded for that agent,
data.ingestionJobs.used counts jobs created on that agent's sources, data.sources.active counts that agent's
sources, and data.tokens reports the additive { input, output } token aggregate from AI messages of that agent's
conversations — all within the current period. Period bounds come from the tenant subscription, falling back to the
current calendar month. Plan limit values are tenant entitlements and are reported as limits, never as used amounts.
The route only admits agent-scoped keys and fails closed with 500 INTERNAL_ERROR if agent context is missing. This is
the REST plan-counter surface; tenant cost aggregates are available through the Platform MCP get_usage tool instead.
Chat Turn Request And Response
Both POST /v1/chat and POST /v1/chat/stream share one request contract.
Identity: exactly one of three
Provide exactly one identity target. Supplying more than one returns 400 VALIDATION_ERROR.
| Field | Meaning |
|---|---|
conversationId | Continue an existing conversation. 404 CONVERSATION_NOT_FOUND if it does not exist for the key agent; 409 CONVERSATION_NOT_ACTIVE if it is not in AI_ACTIVE state. |
endUserId | Internal end user ID for a new conversation. 404 END_USER_NOT_FOUND if the user does not exist in the tenant. |
externalEndUserId | Stable caller-defined identity. Resolved idempotently to the same internal end user; concurrent first requests never create duplicates. Trimmed before validation and bounded to 1–200 characters on both the public REST routes and the MCP create_end_user tool; whitespace-only values are rejected. |
Omit all three to let the API create a fresh anonymous identity for the turn.
Response shape
data.reply is always a string — the complete assistant reply text. data.citations is an array of source
citations used to ground the reply, and data.media is an array of media attachments shared in the turn. Both are
always present and may be empty.
data.endUserId is the resolved internal end user ID. It is present whenever an end user identity was resolved for
the turn — anonymous persistent turns, endUserId, and externalEndUserId — and omitted for conversation-target and
ephemeral (persist: false) turns.
{
"data": {
"reply": "We are open 9am-6pm Monday to Friday.",
"conversationId": "clx...",
"endUserId": "clx...",
"citations": [],
"media": []
},
"meta": {
"requestId": "req_abc123"
}
}
Realtime token behavior
POST /v1/chat returns realtimeToken and realtimeUrl by default whenever a conversation exists, so realtime
capable clients can upgrade without an extra request. Pass includeRealtimeToken: false to omit both fields
entirely, for example when the client never uses the hosted realtime transport.
Streaming headers
POST /v1/chat/stream returns the SSE stream with content-type: text/event-stream and the x-request-id header.
When an end user identity was resolved for the turn, the stream also sets x-end-user-id to the internal end user
ID. Both x-request-id and x-end-user-id are CORS-exposed, so browser clients can read them.
Endpoints
Chat & Retrieval
Send messages, stream responses, and search the knowledge base on the public /v1 API.
/v1/chatRun one non-streaming chat turn through the agent attached to the API key. Provide exactly one of conversationId, endUserId, or externalEndUserId; data.reply is always a string, citations/media are always present, and data.endUserId is present when an end user identity was resolved. Realtime fields are included by default; pass includeRealtimeToken: false to omit them.
curl -X POST https://api.kravos.ai/v1/chat \
-H "Authorization: Bearer spk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"message":"What are your opening hours?"}'Request body
{
"message": "What are your opening hours?",
"conversationId": "optional",
"endUserId": "optional",
"externalEndUserId": "optional-stable-caller-id",
"persist": true,
"includeRealtimeToken": true
}Response
{
"data": {
"reply": "We are open 9am-6pm Monday to Friday.",
"conversationId": "conv_abc123",
"endUserId": "end_user_abc123",
"citations": [],
"media": [],
"realtimeToken": "rt_abc123",
"realtimeUrl": "wss://realtime.kravos.ai/v1"
},
"meta": {
"requestId": "req_abc123"
}
}/v1/chat/streamStream one chat turn using Server-Sent Events. Provide exactly one of conversationId, endUserId, or externalEndUserId: conversationId returns 404 CONVERSATION_NOT_FOUND when outside the key agent scope and 409 CONVERSATION_NOT_ACTIVE when not in AI_ACTIVE state; endUserId returns 404 END_USER_NOT_FOUND when unknown; externalEndUserId is a stable caller-defined identity resolved idempotently. Supplying more than one identity returns 400 VALIDATION_ERROR. The stream sets x-end-user-id (CORS-exposed alongside x-request-id) when an end user identity was resolved.
curl -X POST https://api.kravos.ai/v1/chat/stream \
-H "Authorization: Bearer spk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"message":"Tell me about your products","externalEndUserId":"customer-42"}' \
--no-bufferRequest body
{
"message": "Tell me about your products",
"conversationId": "optional",
"endUserId": "optional",
"externalEndUserId": "optional-stable-caller-id",
"persist": true
}Response
HTTP/1.1 200 OK
content-type: text/event-stream
x-request-id: req_abc123
event: ...
data: .../v1/retrieveSearch source chunks without running AI generation. topK is 1-20 and minScore is 0-1.
curl -X POST https://api.kravos.ai/v1/retrieve \
-H "Authorization: Bearer spk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"query":"pricing plans","topK":5,"minScore":0.7}'Request body
{
"query": "pricing plans",
"topK": 5,
"minScore": 0.7
}Response
{
"data": {
"chunks": [
{
"id": "chunk_abc123",
"documentId": "doc_abc123",
"sourceName": "Pricing",
"text": "Our plans start at $29/mo...",
"score": 0.94,
"url": "https://example.com/pricing"
}
]
},
"meta": {
"requestId": "req_abc123"
}
}Sources
Manage knowledge sources programmatically. Requires an API key with the ingest permission.
/v1/sourcesList sources with cursor-based pagination. Filter by status or type.
curl 'https://api.kravos.ai/v1/sources?limit=20&status=READY' \
-H "Authorization: Bearer spk_your_api_key"Request body
// Query params: ?limit=20&status=READY&type=URL&cursor=clx...Response
{
"data": {
"sources": [
{
"id": "clx...",
"agentId": "agent_abc123",
"type": "URL",
"name": "Help center",
"status": "READY",
"config": {
"url": "https://help.example.com"
},
"lastSyncedAt": "2026-07-06T12:00:00.000Z",
"lastError": null,
"latestJob": null
}
],
"nextCursor": "clx..."
},
"meta": {
"requestId": "req_abc123"
}
}/v1/sourcesCreate a source. autoSync defaults to true and queues an ingestion job.
curl -X POST https://api.kravos.ai/v1/sources \
-H "Authorization: Bearer spk_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: my-unique-key" \
-d '{"type":"URL","name":"Help center","config":{"url":"https://help.example.com"}}'Request body
{
"type": "URL",
"name": "Help center",
"config": {
"url": "https://help.example.com"
},
"autoSync": true
}Response
{
"data": {
"source": {
"id": "clx...",
"agentId": "agent_abc123",
"type": "URL",
"name": "Help center",
"status": "PENDING",
"config": {
"url": "https://help.example.com"
}
},
"ingestionJob": {
"id": "job_abc123",
"status": "QUEUED"
}
},
"meta": {
"requestId": "req_abc123"
}
}/v1/sources/:idFetch one source and its latest ingestion job.
curl https://api.kravos.ai/v1/sources/SOURCE_ID -H "Authorization: Bearer spk_your_api_key"Request body
// No request bodyResponse
{
"data": {
"id": "clx...",
"agentId": "agent_abc123",
"type": "URL",
"name": "Help center",
"status": "READY",
"config": {
"url": "https://help.example.com"
},
"error": null,
"latestJob": {
"id": "job_abc123",
"type": "INGEST",
"status": "SUCCEEDED",
"progress": 100,
"chunksCreated": 42,
"error": null
}
},
"meta": {
"requestId": "req_abc123"
}
}/v1/sources/:idUpdate a source name, config, or both. At least one field is required.
curl -X PATCH https://api.kravos.ai/v1/sources/SOURCE_ID \
-H "Authorization: Bearer spk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"name":"Updated help center"}'Request body
{
"name": "Updated help center",
"config": {
"url": "https://help.example.com"
}
}Response
{
"data": {
"id": "clx...",
"agentId": "agent_abc123",
"name": "Updated help center",
"type": "URL",
"status": "READY"
},
"meta": {
"requestId": "req_abc123"
}
}/v1/sources/:idDelete a source and its associated documents and chunks.
curl -X DELETE https://api.kravos.ai/v1/sources/SOURCE_ID -H "Authorization: Bearer spk_your_api_key"Request body
// No request bodyResponse
{
"data": {
"id": "clx...",
"deleted": true
},
"meta": {
"requestId": "req_abc123"
}
}/v1/sources/:id/syncTrigger ingestion or a full re-index for a source. Returns 202 when the job is queued.
curl -X POST https://api.kravos.ai/v1/sources/SOURCE_ID/sync \
-H "Authorization: Bearer spk_your_api_key" \
-H "Content-Type: application/json" \
-d '{"force":false}'Request body
{
"force": false
}Response
{
"data": {
"jobId": "job_abc123",
"status": "QUEUED",
"sourceId": "clx..."
},
"meta": {
"requestId": "req_abc123"
}
}/v1/ingestion-jobs/:idCheck ingestion job status and progress.
curl https://api.kravos.ai/v1/ingestion-jobs/JOB_ID -H "Authorization: Bearer spk_your_api_key"Request body
// No request bodyResponse
{
"data": {
"id": "job_abc123",
"sourceId": "clx...",
"status": "QUEUED",
"progress": 0,
"chunksCreated": null,
"error": null,
"startedAt": null,
"completedAt": null
},
"meta": {
"requestId": "req_abc123"
}
}Conversations & Usage
Read API conversations and current-period usage for the API key agent.
/v1/conversationsList API conversations for the key agent with cursor-based pagination.
curl https://api.kravos.ai/v1/conversations -H "Authorization: Bearer spk_your_api_key"Request body
// Query params: ?limit=20&endUserId=end_user_id&cursor=clx...Response
{
"data": {
"conversations": [
{
"id": "conv_abc123",
"agentId": "agent_abc123",
"endUserId": "end_user_abc123",
"messageCount": 4,
"createdAt": "2026-07-06T12:00:00.000Z",
"updatedAt": "2026-07-06T12:05:00.000Z"
}
],
"nextCursor": null
},
"meta": {
"requestId": "req_abc123"
}
}/v1/conversations/:idFetch one API conversation and its messages for the key agent.
curl https://api.kravos.ai/v1/conversations/CONVERSATION_ID -H "Authorization: Bearer spk_your_api_key"Request body
// No request bodyResponse
{
"data": {
"id": "conv_abc123",
"agentId": "agent_abc123",
"endUserId": "end_user_abc123",
"messages": [
{
"id": "msg_1",
"role": "user",
"content": "Hello",
"createdAt": "2026-07-06T12:00:00.000Z"
},
{
"id": "msg_2",
"role": "assistant",
"content": "Hi!",
"createdAt": "2026-07-06T12:00:01.000Z"
}
],
"createdAt": "2026-07-06T12:00:00.000Z"
},
"meta": {
"requestId": "req_abc123"
}
}/v1/conversations/:id/messagesList every message in a conversation with stable database-key keyset pagination; legacy rows are never silently omitted.
curl 'https://api.kravos.ai/v1/conversations/CONVERSATION_ID/messages?limit=50' \
-H "Authorization: Bearer spk_your_api_key"Request body
// Query params: ?limit=50&cursor=opaque_cursor
// limit defaults to 50 and must be a positive safe integer.
// Ordering: conversation-scoped database pagination key ascending (PostgreSQL BIGINT). Follow nextCursor until it is null; there is no history cap.
// Cursors are HMAC-authenticated with the server REALTIME_TOKEN_SECRET.
// During legacy-key backfill, the endpoint returns 503 PAGINATION_NOT_READY.Response
{
"data": {
"messages": [
{
"id": "msg_1",
"role": "user",
"content": "Hello",
"createdAt": "2026-07-06T12:00:00.000Z"
},
{
"id": "msg_2",
"role": "assistant",
"content": "Hi!",
"createdAt": "2026-07-06T12:00:01.000Z"
}
],
"nextCursor": "opaque_cursor_or_null",
"hasMore": true
},
"meta": {
"requestId": "req_abc123"
}
}/v1/usageRead current-period usage scoped to the key agent: agent turn, ingestion-job, active-source, and input/output token counters, with tenant plan limits reported as limits.
curl https://api.kravos.ai/v1/usage -H "Authorization: Bearer spk_your_api_key"Request body
// No request bodyResponse
{
"data": {
"period": {
"start": "2026-07-01T00:00:00.000Z",
"end": "2026-08-01T00:00:00.000Z"
},
"turns": {
"used": 100,
"limit": 5000,
"overageEnabled": false
},
"ingestionJobs": {
"used": 4,
"limit": 100
},
"sources": {
"active": 5,
"limit": 50
},
"tokens": {
"input": 1200000,
"output": 400000
}
},
"meta": {
"requestId": "req_abc123"
}
}Public Endpoint Index
| Endpoint | Permission | Purpose |
|---|---|---|
POST /v1/chat | chat | Run a synchronous chat turn |
POST /v1/chat/stream | chat | Stream a chat turn as Server-Sent Events |
POST /v1/retrieve | retrieve | Search source chunks without AI generation |
GET /v1/sources | ingest | List sources |
POST /v1/sources | ingest | Create a source and optionally queue ingest |
GET /v1/sources/:id | ingest | Fetch one source and latest job |
PATCH /v1/sources/:id | ingest | Update source name or config |
DELETE /v1/sources/:id | ingest | Delete a source |
POST /v1/sources/:id/sync | ingest | Queue ingest or re-index |
GET /v1/ingestion-jobs/:id | ingest | Check ingestion job status |
GET /v1/conversations | chat | List conversations for the key's agent |
GET /v1/conversations/:id | chat | Fetch one conversation and its messages |
GET /v1/agent-runs/:runId | trace_read | Read one redacted execution trace page |
GET /v1/usage | any public key | Read current-period plan usage |
Chat And Retrieval Notes
POST /v1/chat and POST /v1/chat/stream accept JSON only on the public API domain:
{
"message": "What are your opening hours?",
"conversationId": "optional",
"endUserId": "optional",
"persist": true
}
If neither conversationId nor endUserId is sent, the API creates an end user for the turn. persist defaults to true.
The streaming route returns text/event-stream and forwards runtime SSE events.
Agent Run Traces
GET /v1/agent-runs/:runId returns one run only when it belongs to the authenticated key's tenant and agent. It requires
trace_read; runs outside that scope return 404. Every successful trace read is durably audited before the response is
sent.
GET /v1/agent-runs/run_123?limit=20&cursor=40
Authorization: Bearer spk_...
{
"data": {
"id": "run_123",
"status": "complete",
"conversationId": "conv_123",
"messageId": "msg_123",
"startedAt": "2026-07-30T12:00:00.000Z",
"completedAt": "2026-07-30T12:00:03.000Z",
"events": [
{
"id": "run_123:41",
"runId": "run_123",
"sequence": 41,
"type": "tool.completed",
"status": "complete",
"label": "Completed a tool call",
"timestamp": "2026-07-30T12:00:02.000Z"
}
],
"hasMore": true,
"nextCursor": "41"
},
"meta": { "requestId": "req_abc123" }
}
Pass nextCursor as cursor for the next page. limit and cursor must be positive integers; invalid values return
400 VALIDATION_ERROR. Trace event payloads contain developer-visible tool and source context with credentials redacted before storage. Encrypted
payloads are never exposed. Because payloads can include customer and tool data, grant trace_read only to trusted
server-side integrations.
Hosted stream clients with both chat and trace_read receive operator-visible data-agent-run-event parts. A key with
only chat receives end-user-redacted event parts. The chat request authenticates the key once; trace visibility is derived
from that authenticated permission set.
data: {"type":"data-agent-run-event","data":{"id":"run_123:41","runId":"run_123","sequence":41,"type":"tool.completed","status":"complete","label":"Completed a tool call","timestamp":"2026-07-30T12:00:02.000Z"}}
POST /v1/retrieve accepts:
{
"query": "pricing plans",
"topK": 5,
"minScore": 0.7
}
The implemented validation accepts query up to 1,000 characters, topK from 1 to 20, and minScore from 0 to 1.
Source Types
When creating sources via POST /v1/sources, use one of these documented public configurations:
| Type | Required config fields | Notes |
|---|---|---|
URL | config.url | Optional: maxDepth, includePatterns, excludePatterns |
SITEMAP | config.sitemapUrl or config.url | Optional: includePatterns, excludePatterns |
FILE | config.fileUrl | Optional: fileName |
IMAGE | config.fileUrl | Optional: fileName |
BULK_UPLOAD | no route-level required fields | Accepted by the source enum; ZIP extraction is dashboard-managed |
The public create route accepts BULK_UPLOAD because it accepts the source enum. The ZIP extraction workflow itself is an
authenticated dashboard route (/api/sources/bulk-upload), not a separate public /v1 REST endpoint.
autoSync defaults to true. Set autoSync: false when you want to create the source record first and queue ingestion
later with POST /v1/sources/:id/sync.
Idempotent source creation
Pass an Idempotency-Key header on POST /v1/sources to safely retry create requests. Matching
keys for the same API key return the original 201 response for 24 hours.
Web App And SDK Routes
These routes were inspected because they overlap with public API concepts, but they are not the same as the public REST API:
/api/sdk/*powers the embedded widget and suite. It accepts an agent API key or a dashboard session fallback. Routes that read existing end-user state requiresessionId; init and some voice-start paths can create a session when omitted./api/sources*,/api/conversations*, and/api/usageare dashboard session routes. They usenext-auth/RBAC, not public API-key-only auth.POST /api/v1/chatandPOST /api/v1/retrieveon the web app domain return410 GONE; call the public API domain instead.POST /api/v1/chat/streamon the web app domain is the hosted widget stream endpoint. It can accept JSON ormultipart/form-datawith attachments.
File Attachments On Hosted Stream
The hosted web stream endpoint can accept images and PDFs alongside a chat message. Send POST /api/v1/chat/stream on
your Kravos.ai web app domain as multipart/form-data:
curl -X POST https://your-domain.com/api/v1/chat/stream \
-H "Authorization: Bearer spk_..." \
-F "message=What is in this image?" \
-F "files=@photo.png"
Supported MIME types are JPEG, PNG, WebP, GIF, and PDF. The implemented hosted route accepts up to 10 files per request, with 10 MB per image and 25 MB per PDF. Model-specific validation may still reject an attachment type.
OpenAPI And Spec Alignment
The compact OpenAPI spec exposed through platform/MCP docs is generated from
packages/platform/src/read/docs/reference.ts. It lists the implemented public /v1 paths for chat, streaming chat,
retrieval, sources, source detail/update/delete/sync, ingestion-job detail, conversations, agent-run traces, and usage.
The generated spec includes path parameters, success status codes, permissions, source paths, and update timestamps. It is still compact: request and response schemas are documented on this page and should be treated as the human-readable contract.
Related Docs
API Keys
Create and manage keys for chat, retrieve, and ingest use cases.
Sources
Add and monitor the content your agent retrieves from.
Conversations
Review transcript history and takeover flows.


