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

  1. Create an agent-scoped API key from API Keys.
  2. Give the key the smallest permission set for the workflow: chat, retrieve, ingest, or trace_read.
  3. Call the public API base URL with either Authorization: Bearer ... or x-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

SurfaceBase URLUse for
Public REST APIhttps://api.kravos.ai/v1Server-to-server chat, retrieval, sources, conversations
Hosted web app / widgethttps://your-domain.com/apiWidget SDK routes, dashboard routes, hosted stream transport
Deprecated dashboard shimhttps://your-domain.com/api/v1Only 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.

PermissionUsed for
chatPOST /v1/chat, POST /v1/chat/stream, conversation reads
retrievePOST /v1/retrieve
ingestSource reads/writes, sync jobs, ingestion-job reads
ingest.readFine-grained route check; base ingest keys satisfy it
ingest.writeFine-grained route check; base ingest keys satisfy it
usage.readFine-grained route check; base public keys satisfy it
trace_readGET /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 ingest satisfies fine-grained ingest.read / ingest.write checks.
  • One-directional legacy grant: keys created with chat, retrieve, or ingest historically received usage.read; that grant is preserved, so those keys satisfy GET /v1/usage. The reverse never holds — usage.read does 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 allowedOrigins means any origin is accepted
  • a non-empty list requires an Origin header
  • the Origin value must exactly match one configured URL
  • blocked origins return 403; revoked, inactive, or expired keys return 401

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.

CodeTypical statusMeaning
UNAUTHORIZED401Missing, invalid, expired, inactive, or revoked API key
FORBIDDEN403Origin blocked, missing permission, or route needs agent key
PLAN_RESTRICTED403Current plan does not include public API access
VALIDATION_ERROR400Request body or query string did not match the route schema
NOT_FOUND404Resource does not exist for the key's agent
LIMIT_EXCEEDED429Source or monthly ingestion-job entitlement limit reached
TURN_LIMIT_EXCEEDED429Chat turn quota reached
CONVERSATION_NOT_FOUND404conversationId does not exist for the key agent
CONVERSATION_NOT_ACTIVE409Conversation exists but is not in AI_ACTIVE state
END_USER_NOT_FOUND404endUserId does not exist in the tenant
RUNTIME_DATA_UNAVAILABLE503Optional runtime-data forwarding is temporarily unavailable
TURN_FAILED500Chat runtime failed
RUNTIME_TIMEOUT504Runtime timed out
GONE410Dashboard-domain shim moved to api.kravos.ai
UNSUPPORTED_ATTACHMENT_TYPE400Hosted stream attachment not supported by selected model
UNSUPPORTED_TYPE400Hosted stream file MIME type is not allowed
FILE_TOO_LARGE400Hosted stream file exceeds the implemented per-type limit
INTERNAL_ERROR500Unexpected server failure

Pagination And Filtering

List endpoints use cursor pagination on the public API.

EndpointQuery paramsNotes
GET /v1/sourceslimit, cursor, status, typelimit defaults to 20 and is capped at 100
GET /v1/conversationslimit, cursor, endUserIdlimit defaults to 20 and is capped at 100
GET /v1/conversations/:id/messageslimit, cursorlimit defaults to 50; there is no history cap
GET /v1/agent-runs/:idlimit, cursorSequence 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/chat when you want one complete reply
  • use POST /v1/chat/stream when the client should render tokens as they arrive
  • use POST /v1/retrieve when 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/:runId when you need a redacted execution trace for one completed or running turn
  • use GET /v1/usage when 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: at most one of three

Provide at most one identity target. Supplying more than one returns 400 VALIDATION_ERROR; all three may be omitted.

FieldMeaning
conversationIdContinue 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.
endUserIdInternal end user ID for a new conversation. 404 END_USER_NOT_FOUND if the user does not exist in the tenant.
externalEndUserIdStable 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.

Persistence

persist defaults to true. Set it to false for a turn that must not append messages or an execution trace:

  • with conversationId, the turn can read that conversation's existing context but does not append to it
  • with endUserId or externalEndUserId, the identity is resolved for correlation but the turn creates no conversation
  • with no identity target, the turn is fully anonymous and creates neither an end user nor a conversation

Request-scoped runtime data

Both POST /v1/chat and POST /v1/chat/stream accept the same three optional runtime fields. They are additive and do not change identity validation, response shapes, streaming headers, or realtime-token behavior.

FieldModelModel providerConfigured Kravos toolsDurable runtime-input storage
contextYesYesNot automaticallyNo
toolContextNoNoExplicit selected bindingsNo
toolCredentialsNeverNeverExplicit selected auth slotsNever

These public endpoints run text turns; the provider column states the data-visibility contract when the same context field is used by widget voice. Never put secrets in context. Use toolContext for hidden non-secret binding values and toolCredentials for short-lived credentials selected by configured tools.

context and toolContext must be JSON objects. Their values may recursively contain strings, finite numbers, booleans, null, arrays, and objects. toolCredentials must be an object mapping slot names to strings.

Synchronous example: explain the selected invoice

Run this from your backend with an agent-scoped chat key supplied through your established secret store as KRAVOS_API_KEY. Derive the customer identity and permitted context from the authenticated application session; do not trust arbitrary browser-submitted customer IDs. All customer values below are synthetic.

curl --fail-with-body -X POST https://api.kravos.ai/v1/chat \
  -H "Authorization: Bearer ${KRAVOS_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Explain the invoice I am viewing",
    "externalEndUserId": "customer_456",
    "persist": true,
    "context": {
      "user": { "locale": "en" },
      "page": { "name": "Invoice details", "selectedInvoiceId": "inv_123" }
    }
  }'

Read the successful JSON envelope's data.reply and retain data.conversationId for continuation. externalEndUserId selects identity; it does not automatically give the model page or account facts.

Continue with updated context

Send this body to either chat endpoint using the same authentication headers. Replace the placeholder with the actual conversation ID returned by the previous persistent turn. Do not also send externalEndUserId or endUserId.

{
  "message": "What changed on this invoice?",
  "conversationId": "<conversation-id-from-previous-response>",
  "context": {
    "user": { "locale": "en" },
    "page": { "name": "Invoice details", "selectedInvoiceId": "inv_789" }
  }
}

Supply current runtime data on every relevant request. Continuing a conversation does not restore the preceding request's raw context, toolContext, or toolCredentials, even if that turn used persist: true.

Streaming example: summarize the selected invoice

This standalone backend request uses an agent-scoped chat key from your secret store in KRAVOS_API_KEY. Derive identity and context from your authenticated host session; the sample values are synthetic.

curl --no-buffer --include --fail-with-body -X POST https://api.kravos.ai/v1/chat/stream \
  -H "Authorization: Bearer ${KRAVOS_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Summarize my selected invoice",
    "externalEndUserId": "customer_456",
    "persist": true,
    "context": {
      "page": { "name": "Invoice details", "selectedInvoiceId": "inv_123" }
    }
  }'

The command displays headers and the stream without buffering. In application code, check HTTP status and Content-Type before consuming the body as SSE: an early validation or capability failure returns a JSON error instead. Do not parse the entire successful stream as a JSON response. Use the existing runtime SSE event contract shown in the streaming endpoint reference below, handling terminal errors and disconnects as well as completion.

The stream carries AI SDK UI-message parts in data: frames: text-delta.delta supplies incremental text, data-done.data.result supplies the completed result (including reply and conversationId), and an error part carries errorText. Other parts include text boundaries, citations, tools, media, and trace events; [DONE] terminates the stream. A model finish part alone does not establish that Kravos has settled and persisted the turn—wait for data-done for the completed result. Retain its conversation ID for continuation, and treat a disconnect before that result as an uncertain outcome, not permission to replay the turn.

Optional authorized private-data lookup

Either endpoint also accepts the following body when the tool has stored bindings and a configured billing slot:

{
  "message": "Show the selected invoice",
  "externalEndUserId": "user_123",
  "persist": true,
  "context": {
    "page": { "name": "Invoice details", "selectedInvoiceId": "inv_456" }
  },
  "toolContext": {
    "accountId": "acct_123"
  },
  "toolCredentials": {
    "billing": "<short-lived-user-token>"
  }
}

Runtime data is request-scoped and discarded after the turn regardless of persist. persist: true still controls the normal conversation and message records; it never stores the raw runtime input. Responses and SSE events never echo toolCredentials. The server caller is responsible for supplying a current short-lived value when a configured tool requires one.

Follow the HTTP tool bindings and external MCP server bindings guidance for supported configuration and transports. The stored auth name widget-session also supports runtime slots from public REST callers; it is not limited to widgets. Unbound data is not automatically sent downstream. Runtime fields do not replace API authentication, tenant/agent scope, or downstream authorization. Never log request bodies containing these fields or paste credentials into AI conversations or customer-visible errors.

Model-visible context can be repeated in an assistant reply, and that reply may be persisted. Request scope does not promise secrecy or erasure of those facts from conversation history.

If a configured tool reports SESSION_CREDENTIAL_REQUIRED, this is a safe tool failure naming the missing slot, not necessarily a top-level HTTP response code. Issue or refresh the named slot and retry according to the tool's normal idempotency policy. Do not log the value, return it to a browser, or move it into context. A request using any runtime field may return 503 RUNTIME_DATA_UNAVAILABLE while runtime-data forwarding is unavailable; context-free requests remain compatible.

For 503 RUNTIME_DATA_UNAVAILABLE, confirm forwarding capability availability with the operator. Do not silently retry without runtime data or move context into message. Even an explicitly supplied empty object is a runtime field; omit all three fields only when context-free behavior is intended. Invalid runtime input returns 400 VALIDATION_ERROR: fix the input rather than retrying unchanged. After a timeout or disconnect, do not automatically repeat a potentially side-effecting turn—a downstream operation may already have occurred.

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, including non-persistent identity-targeted turns — and omitted for conversation-target and anonymous non-persistent 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.

POST
/v1/chat

Run 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. With persist: false, existing conversation targets provide read-only context, while end-user targets are resolved without creating a conversation. 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"
  }
}
POST
/v1/chat/stream

Stream 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. With persist: false, existing conversation targets provide read-only context, while end-user targets are resolved without creating a conversation. 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-buffer

Request 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: ...
POST
/v1/retrieve

Search 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.

GET
/v1/sources

List 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"
  }
}
POST
/v1/sources

Create 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"
  }
}
GET
/v1/sources/:id

Fetch 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 body

Response

{
  "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"
  }
}
PATCH
/v1/sources/:id

Update 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"
  }
}
DELETE
/v1/sources/:id

Start durable storage cleanup, then delete the source and its associated documents and chunks. Returns 409 while the artifact is preserved or 503 when cleanup cannot start.

curl -X DELETE https://api.kravos.ai/v1/sources/SOURCE_ID -H "Authorization: Bearer spk_your_api_key"

Request body

// No request body

Response

{
  "data": {
    "id": "clx...",
    "deleted": true
  },
  "meta": {
    "requestId": "req_abc123"
  }
}
POST
/v1/sources/:id/sync

Trigger 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"
  }
}
GET
/v1/ingestion-jobs/:id

Check 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 body

Response

{
  "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.

GET
/v1/conversations

List 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"
  }
}
GET
/v1/conversations/:id

Fetch 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 body

Response

{
  "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"
  }
}
GET
/v1/conversations/:id/messages

List 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"
  }
}
GET
/v1/usage

Read 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 body

Response

{
  "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

EndpointPermissionPurpose
POST /v1/chatchatRun a synchronous chat turn
POST /v1/chat/streamchatStream a chat turn as Server-Sent Events
POST /v1/retrieveretrieveSearch source chunks without AI generation
GET /v1/sourcesingestList sources
POST /v1/sourcesingestCreate a source and optionally queue ingest
GET /v1/sources/:idingestFetch one source and latest job
PATCH /v1/sources/:idingestUpdate source name or config
DELETE /v1/sources/:idingestDelete a source
POST /v1/sources/:id/syncingestQueue ingest or re-index
GET /v1/ingestion-jobs/:idingestCheck ingestion job status
GET /v1/conversationschatList conversations for the key's agent
GET /v1/conversations/:idchatFetch one conversation and its messages
GET /v1/agent-runs/:runIdtrace_readRead one redacted execution trace page
GET /v1/usageany public keyRead current-period plan usage

DELETE /v1/sources/:id starts durable storage cleanup before deleting an artifact-backed Source row. It returns 409 ARTIFACT_DELETE_CONFLICT while another lifecycle operation preserves the artifact, or 503 ARTIFACT_DELETE_UNAVAILABLE when cleanup cannot be started safely. Retry only after resolving or waiting for the reported condition.

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?",
  "externalEndUserId": "customer_456",
  "persist": true,
  "context": { "locale": "en" },
  "toolContext": { "accountId": "acct_123" },
  "toolCredentials": { "billing": "<short-lived-user-token>" }
}

If no identity field is sent, the API creates an end user for a persistent turn and runs a non-persistent turn anonymously. persist defaults to true. The three runtime fields are optional; omit all of them for the existing context-free behavior. The streaming route returns text/event-stream and forwards runtime SSE events without echoing hidden runtime values.

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:

TypeRequired config fieldsNotes
URLconfig.urlOptional: maxDepth, includePatterns, excludePatterns
SITEMAPconfig.sitemapUrl or config.urlOptional: includePatterns, excludePatterns
FILEconfig.fileUrlOptional: fileName
IMAGEconfig.fileUrlOptional: fileName
BULK_UPLOADno route-level required fieldsAccepted 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 require sessionId; init and some voice-start paths can create a session when omitted.
  • /api/sources*, /api/conversations*, and /api/usage are dashboard session routes. They use next-auth/RBAC, not public API-key-only auth.
  • POST /api/v1/chat and POST /api/v1/retrieve on the web app domain return 410 GONE; call the public API domain instead.
  • POST /api/v1/chat/stream on the web app domain is the hosted widget stream endpoint. It can accept JSON or multipart/form-data with 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.

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.

Last updated: September 2026