OpenAPI spec https://api.korey.ai/api/v1-alpha0/openapi.json
Overview
The Korey API is a REST API that lets you build integrations on top of Korey threads and messages. All requests are made to:
https://api.korey-staging.ai/api/v1-alpha0
All endpoints require a Bearer token and return JSON. Get started by creating a personal access token at Settings → API Tokens.
Authentication
All endpoints require a Bearer token passed in the Authorization header:
Authorization: Bearer kt_pat_<token>
Create a personal access token at Settings → API Tokens. Each token
carries a set of scopes that gate access to specific endpoints. If a token
lacks the required scope for an endpoint, the response is 403 Forbidden
with a required_scope field indicating what is needed.
Pagination
List endpoints use keyset pagination based on item IDs. Each response
includes first_id, last_id, and has_more.
To fetch the next page, pass ?after=<last_id> from the previous response.
When has_more is false there are no further pages.
The sort order used on the first request must be kept consistent across
all subsequent page requests. Changing sort_by or sort_dir mid-pagination
will produce unexpected results.
Filters are also not carried between pages. If you paginate with filters
(e.g. ?owned=true), pass the same filters on every request.
Cursors do not expire — you can resume pagination at any time.
Thread State Lifecycle
Every thread has a state field that indicates whether it is ready to accept
new messages or is currently processing a response.
| State | Meaning |
|---|---|
ready |
The thread is idle and accepts new messages via POST /threads/:id/messages |
waiting |
A message has been queued and is awaiting the AI processor |
active |
The LLM is actively generating a response |
error |
Processing failed; inspect the response for details |
interrupted |
Generation was interrupted (e.g. by a conflicting message) |
You may only send a new message when the thread is in ready state.
Sending while waiting or active returns 409 Conflict.
The simplest way to know when a thread is ready again after sending a message
is to open the SSE stream (GET /threads/:id/messages/:msg-id/response/stream)
and wait for the done event — it is only emitted after the thread returns to
ready. Alternatively, poll GET /threads/:id and check the state field.
Feedback
You can rate any AI response with a thumbs up or down via
POST /threads/:id/messages/:msg-id/feedback. Optionally include a short
free-text comment. One rating per user per message — submitting again
updates the previous rating.
Conversational Continuity
Threads are persistent conversations. For multi-turn interactions, reuse the
same thread by sending subsequent messages to
POST /threads/:thread-id/messages rather than creating a new thread each
time. Store the thread_id from the initial response and pass it to all
follow-up messages.
Every thread and message includes an app_url field — a direct link to that
item in the Korey web app, useful for surfacing context to users.
Set a meaningful name when creating a thread. It appears in the Korey UI
and helps users identify the conversation later.
Error State Recovery
A thread in error state is still readable — you can fetch its messages via
GET /threads/:id/messages — but it cannot receive new messages. There is
currently no way to resume or reset a thread in error state.
To continue work after an error, create a new thread and seed it with a summary of the previous conversation. A useful pattern:
- Fetch the messages from the errored thread.
- Summarize the relevant context (e.g. what was being worked on, what was decided, where it left off).
- Create a new thread with that summary as the initial message.
Versioning
This API is currently at v1-alpha0. Breaking changes may occur without
notice while in alpha. The path prefix will change to /api/v1 when the
API graduates from alpha.
Identity
/me Get current user
Returns the authenticated user's identity and organization.
Responses
| Status | Description |
|---|---|
200 | Success |
401 | Unauthorized — Bearer token required or invalid |
403 | Forbidden — token does not have required scope |
{
/** JWT subject — stable string ID for the user. */
sub: string;
/** Display name of the user. */
name: string | null;
/** Email address of the user. */
email: string | null;
/** Internal numeric user ID. */
korey_user_id: integer;
/** Internal numeric organization ID. */
korey_organization_id: integer;
/** URL-safe organization slug. */
korey_organization_slug: string;
/** User role within the organization. */
role: string;
} Threads
/threads/{thread-id}/messages List messages
Lists active messages in a thread, ordered by created_at ASC. Pass last_id as ?after= to paginate.
Path parameters
| Name | Type | Description |
|---|---|---|
thread-id required | string | The thread ID. |
Query parameters
| Name | Type | Description |
|---|---|---|
limit | integer | Maximum number of messages to return. Default: 50, max 200. |
after | string | Return messages after this message ID (for pagination). |
Responses
| Status | Description |
|---|---|
200 | Success |
401 | Unauthorized — Bearer token required or invalid |
403 | Forbidden — token does not have required scope |
404 | Not found |
{
/** Array of message objects. */
data: [
{
/** Unique message ID. */
id: string;
/** ID of the thread this message belongs to. */
thread_id: string;
/** Who sent the message. */
role: "user" | "assistant" | "summary";
/** Ordered list of content blocks that make up the message. */
contents: [
{
type: "text";
text: string;
}
| {
type: "image";
attachment_id: string;
}
| {
type: "document";
attachment_id: string;
}
| {
type: "user_get_choice";
tool_use_id: string;
question: string;
choices: string[];
}
];
/** ISO 8601 timestamp when the message was created. */
created_at: string;
/** Direct link to this message in the Korey web app. */
app_url: string;
}
];
/** ID of the first message in the page. */
first_id: string | null;
/** ID of the last message in the page. Pass as ?after= to fetch the next page. */
last_id: string | null;
/** True when there are more messages beyond this page. */
has_more: boolean;
/** The limit applied to this request. */
limit: integer;
} /threads/{thread-id}/messages Send message
Sends a user text message to a thread and triggers AI processing.
The thread must be in ready state — the only state in which new messages are
accepted. Returns 409 if the thread is not ready.
After sending, the thread transitions:
waiting— message is queued for the AI processoractive— the LLM is generating a responseready— response is complete; the thread accepts new messages again
Use the SSE stream endpoint or poll the thread state field to know when
the thread returns to ready.
Path parameters
| Name | Type | Description |
|---|---|---|
thread-id required | string | The thread ID. |
Request body
{
/** The user message text to send. */
text: string;
} Responses
| Status | Description |
|---|---|
201 | Created |
401 | Unauthorized — Bearer token required or invalid |
403 | Forbidden |
404 | Not found |
409 | Conflict |
{
/** ID of the newly created message. Use this to poll or stream the AI response. */
message_id: string;
} /threads/{thread-id}/messages/{message-id}/feedback Submit feedback
Submits a thumbs-up or thumbs-down rating for an AI message. One feedback entry per user per message — submitting again updates the previous rating.
Path parameters
| Name | Type | Description |
|---|---|---|
thread-id required | string | The thread ID. |
message-id required | string | The message ID. |
Request body
{
/** True for thumbs up, false for thumbs down. */
positive: boolean;
/** Optional free-text comment accompanying the rating. */
feedback_text?: string;
} Responses
| Status | Description |
|---|---|
204 | Response |
401 | Unauthorized — Bearer token required or invalid |
403 | Forbidden — token does not have required scope |
/threads/{thread-id}/messages/{message-id}/response/stream Stream AI response
SSE stream for the AI response to a user message.
Events are sent in SSE format:
event: <type>
data: <json>
| Event | Data | Description |
|---|---|---|
token |
{"token": "..."} |
A text chunk of the AI response |
error |
{"message": "..."} |
An error occurred |
done |
{} |
Response is fully complete — thread is back in ready state |
done is only emitted after the LLM has finished generating and the thread
has returned to ready. Once you receive done the thread accepts new messages.
Thinking and status events are filtered. The stream closes after done or error.
Path parameters
| Name | Type | Description |
|---|---|---|
thread-id required | string | The thread ID. |
message-id required | string | The message ID. |
Responses
| Status | Description |
|---|---|
401 | Unauthorized — Bearer token required or invalid |
403 | Forbidden — token does not have required scope |
/threads List threads
Lists visible threads for the authenticated user. Pass last_id as ?after= to paginate.
Query parameters
| Name | Type | Description |
|---|---|---|
owned | boolean | true = only threads you own; false = threads owned by others; omit = all visible threads. |
private | boolean | true = private threads only; false = public threads only; omit = both. |
archived | boolean | true to include archived threads. Default: false. |
q | string | Full-text search query against thread names and messages. |
sort_by | "updated_at" | "created_at" | Field to sort by. Default: updated_at. |
sort_dir | "asc" | "desc" | Sort direction. Default: desc. |
updated_since | string | ISO 8601 timestamp — return only threads updated after this time. |
limit | integer | Maximum number of threads to return. Default: 50, max 200. |
after | string | Return threads after this thread ID (for pagination). |
Responses
| Status | Description |
|---|---|
200 | Success |
401 | Unauthorized — Bearer token required or invalid |
403 | Forbidden — token does not have required scope |
{
/** Array of thread objects. */
data: [
{
/** Unique thread ID. */
id: string;
/** Optional display name for the thread. */
name: string | null;
/** Current processing state of the thread. */
state: "ready" | "waiting" | "active" | "error" | "interrupted";
/** When true the thread is only visible to its owner. */
is_private: boolean;
/** Whether the thread has been archived. */
archived: boolean;
/** The user who created the thread. */
owner: {
/** Owner user ID. */
id: string;
/** Owner display name. */
name: string | null;
};
/** ISO 8601 timestamp when the thread was created. */
created_at: string;
/** ISO 8601 timestamp when the thread was last updated. */
updated_at: string;
/** Direct link to this thread in the Korey web app. */
app_url: string;
}
];
/** ID of the first item in the page. null when the page is empty. */
first_id: string | null;
/** ID of the last item in the page. Pass as ?after= to fetch the next page. */
last_id: string | null;
/** True when there are more results beyond this page. */
has_more: boolean;
/** The limit applied to this request. */
limit: integer;
} /threads Create thread
Creates a new thread with an initial message. Returns 409 on conflict.
Request body
{
/** Initial message text to start the thread. */
text: string;
/** Optional display name for the thread. */
name?: string | null;
/** When true the thread is only visible to its owner. Defaults to false. */
is_private?: boolean;
} Responses
| Status | Description |
|---|---|
201 | Created |
401 | Unauthorized — Bearer token required or invalid |
403 | Forbidden — token does not have required scope |
409 | Conflict |
{
/** ID of the newly created thread. */
thread_id: string;
/** ID of the initial message. */
message_id: string;
} /threads/{thread-id} Get thread
Returns a single thread by ID. 404 for private threads not owned by the caller.
Path parameters
| Name | Type | Description |
|---|---|---|
thread-id required | string | The thread ID. |
Responses
| Status | Description |
|---|---|
200 | Success |
401 | Unauthorized — Bearer token required or invalid |
403 | Forbidden — token does not have required scope |
404 | Not found |
{
/** Unique thread ID. */
id: string;
/** Optional display name for the thread. */
name: string | null;
/** Current processing state of the thread. */
state: "ready" | "waiting" | "active" | "error" | "interrupted";
/** When true the thread is only visible to its owner. */
is_private: boolean;
/** Whether the thread has been archived. */
archived: boolean;
/** The user who created the thread. */
owner: {
/** Owner user ID. */
id: string;
/** Owner display name. */
name: string | null;
};
/** ISO 8601 timestamp when the thread was created. */
created_at: string;
/** ISO 8601 timestamp when the thread was last updated. */
updated_at: string;
/** Direct link to this thread in the Korey web app. */
app_url: string;
} /threads/{thread-id}/messages/{message-id}/response Poll for AI response
Polls for the AI response to a user message.
Returns 202 Accepted while the thread is in waiting or active state.
Returns 200 OK once the thread returns to ready and the full response is
available. Returns 404 if no response exists for the message.
Prefer the SSE stream endpoint for lower latency.
Path parameters
| Name | Type | Description |
|---|---|---|
thread-id required | string | The thread ID. |
message-id required | string | The message ID. |
Responses
| Status | Description |
|---|---|
200 | Success |
202 | Accepted |
401 | Unauthorized — Bearer token required or invalid |
403 | Forbidden — token does not have required scope |
404 | Not found |
{
status: "complete";
messages: [
{
/** Unique message ID. */
id: string;
/** ID of the thread this message belongs to. */
thread_id: string;
/** Who sent the message. */
role: "user" | "assistant" | "summary";
/** Ordered list of content blocks that make up the message. */
contents: [
{
type: "text";
text: string;
}
| {
type: "image";
attachment_id: string;
}
| {
type: "document";
attachment_id: string;
}
| {
type: "user_get_choice";
tool_use_id: string;
question: string;
choices: string[];
}
];
/** ISO 8601 timestamp when the message was created. */
created_at: string;
/** Direct link to this message in the Korey web app. */
app_url: string;
}
];
} {
status: "processing";
}