Project read API

Read project runs, usage, threads and scores from a backend with an API key.

The project read API answers project wide observability and conversation reads for a backend service. Send an API key to https://backend.assistant-api.com with the Aui-User-Id and Aui-Workspace-Id headers like every API key request; the workspace header is required but does not scope these reads, which cover the whole project. Every route on this page is read only. The cloud never returns system runs such as thread title runs. See Conventions for the shared hosts and credentials, and Errors for error shapes.

The project run object

FieldTypeMeaning
idstringThe run identifier.
thread_id, message_idstring or nullThe thread and message associated with the run.
created_bystring or nullThe identity that created the run.
status, outcome_type, error_codestored valuesThe run status, outcome classification and error code.
provider, model_idstring or nullThe provider and model used by the run.
environment, releasestring or nullThe environment and release recorded for the run.
tagsarrayTags recorded on the run. The tag query filters for a matching tag.
metadata, attributesobjectThe same object under both names. metadata remains for SDK backward compatibility.
root_span_idstring or nullThe root span identifier.
service_name, agent_namestring or nullService and agent names recorded for the run.
usage_details, cost_detailsobject or nullDetailed usage and cost values recorded for the run.
input_tokens, cached_input_tokens, output_tokens, reasoning_tokensnumber or nullToken counts recorded for the run.
duration_ms, first_token_msnumber or nullRun duration and time to the first token in milliseconds.
cost_usddecimal string or nullThe run cost in US dollars.
total_stepsnumber or nullThe total number of recorded steps.
assistant_idstring or nullThe assistant associated with the run.
created_atISO 8601When the run was created.

The project span object

FieldTypeMeaning
id, run_id, parent_span_idstring or nullSpan, run and parent span identifiers.
created_at, started_at, ended_atISO 8601 or nullSpan creation, start and end times.
trace_id, span_idstring or nullW3C trace and span identifiers when exported from your server.
name, operationstring or nullSpan name and operation.
provider, tool_call_idstring or nullProvider and tool call associated with the span.
status_code, error_typestored valuesSpan status code and error type.
attributesobject or nullAttributes recorded for the span.
span_indexnumberThe span index used to order spans with the same start time.
type, step_typestored valuestype is generation or tool_call; step_type is initial, tool-result, continue or sampling.
model_id, tool_name, tool_sourcestring or nullModel and tool details recorded for the span.
input_tokens, cached_input_tokens, output_tokens, reasoning_tokensnumber or nullToken counts recorded for the span.
start_ms, end_msnumber or nullStart and end times relative to the run.
finish_reason, status, errorstored valuesCompletion and error values recorded for the span.
cost_usddecimal string or nullThe span cost in US dollars.
usage_details, cost_detailsobject or nullDetailed usage and cost values recorded for the span.
input, outputstored valuesThe span input and output.

List project runs

GET /v1/projects/runs
QueryTypeDefaultRules
statusenumOne of the API run status values.
outcome_typeenumOne of the API run outcome values.
model_idstring1 to 255 characters.
providerstring1 to 255 characters.
created_bystring1 to 255 characters.
thread_idstring1 to 48 characters.
environmentstring1 to 64 characters.
releasestring1 to 255 characters.
tagstring1 to 64 characters. Matches a tag on the run.
fromISO 8601 datetimeInclude runs from this time.
toISO 8601 datetimeInclude runs through this time.
limitinteger501 to 100.
cursorstringAn opaque cursor, 1 to 512 characters.
Request
GET /v1/projects/runs?limit=50&from=2026-09-01T00%3A00%3A00.000Z&to=2026-09-17T00%3A00%3A00.000Z
curl
curl "https://backend.assistant-api.com/v1/projects/runs?limit=50&from=2026-09-01T00%3A00%3A00.000Z&to=2026-09-17T00%3A00%3A00.000Z" \
  -H "Authorization: Bearer $ASSISTANT_API_KEY" \
  -H "Aui-User-Id: user_123" \
  -H "Aui-Workspace-Id: workspace_123"
Response
{
  "runs": [],
  "next_cursor": null
}

The cursor is a strict base64url encoding of JSON shaped like { "created_at": ISO, "id": string }. It is opaque, so pass next_cursor unchanged as the next cursor value. Results are ordered by created_at DESC, id DESC, and the response contains limit rows at most.

StatusBodyWhen
200A runs array and a next_cursor value.The read completed. The array is empty when no runs match.
400A schema validation error object.A query value failed validation.
400{ "error": "Invalid run cursor" }cursor is not a valid project run cursor.
401An authentication error object.The authorization header is missing, malformed or expired.
403{ "error": "This endpoint requires an API key" }The credential is not an API key, or the API key cannot use this route.

Get a project run

GET /v1/projects/runs/{run_id}
ParameterTypeRequiredRules
runIdstringyes1 to 48 characters.
Request
GET /v1/projects/runs/run_000000000000000000000000
curl
curl https://backend.assistant-api.com/v1/projects/runs/run_000000000000000000000000 \
  -H "Authorization: Bearer $ASSISTANT_API_KEY" \
  -H "Aui-User-Id: user_123" \
  -H "Aui-Workspace-Id: workspace_123"
Response
{
  "id": "run_000000000000000000000000",
  "thread_id": "thread_0qzof3jPoDwr7K3agyJN3D4U",
  "message_id": null,
  "created_by": null,
  "status": null,
  "outcome_type": null,
  "error_code": null,
  "provider": null,
  "model_id": null,
  "environment": null,
  "release": null,
  "tags": [],
  "metadata": {},
  "attributes": {},
  "root_span_id": null,
  "service_name": null,
  "agent_name": null,
  "usage_details": null,
  "cost_details": null,
  "input_tokens": 0,
  "cached_input_tokens": 0,
  "output_tokens": 0,
  "reasoning_tokens": 0,
  "duration_ms": 0,
  "first_token_ms": null,
  "cost_usd": "0",
  "total_steps": 0,
  "assistant_id": null,
  "created_at": "2026-09-17T00:00:00.000Z",
  "spans": []
}

The response is the project run object plus spans. Spans are ordered by start_ms ASC, span_index ASC. Only spans belonging to a non system run are returned.

StatusBodyWhen
200The run object with spans.The run is visible in the authenticated project.
400A schema validation error object.runId failed validation, including a value longer than 48 characters.
401An authentication error object.The authorization header is missing, malformed or expired.
403{ "error": "This endpoint requires an API key" }The request does not use an API key.
404{ "error": "Run not found" }No matching non system run is visible in the project.

The span order is part of the response contract, so clients can render a stable sequence without sorting the returned array.

Get daily project usage

GET /v1/projects/usage/daily
QueryTypeDefaultRules
fromYYYY-MM-DD30 day windowA real UTC calendar day. If omitted, it is 29 days before to.
toYYYY-MM-DDToday in UTCA real UTC calendar day.
group_byenummodel, which groups rows by day, provider and model.

The default range ends today in UTC and starts 29 days earlier. A requested range cannot have a negative span or a span greater than 366 days. Invalid calendar dates are also rejected. Without group_by, every day in the range appears, including days with zero values. With group_by=model, the response contains one row for each stored day, provider and model combination, with no zero fill; provider and model_id can be null.

Request
GET /v1/projects/usage/daily?from=2026-09-01&to=2026-09-17&group_by=model
curl
curl "https://backend.assistant-api.com/v1/projects/usage/daily?from=2026-09-01&to=2026-09-17&group_by=model" \
  -H "Authorization: Bearer $ASSISTANT_API_KEY" \
  -H "Aui-User-Id: user_123" \
  -H "Aui-Workspace-Id: workspace_123"
Response
{
  "days": [
    {
      "day": "2026-09-17",
      "provider": null,
      "model_id": null,
      "runs": 0,
      "errors": 0,
      "input_tokens": 0,
      "output_tokens": 0,
      "total_tokens": 0,
      "cost_usd": 0
    }
  ]
}

runs counts rows whose kind is run. errors counts rows whose kind is run and whose status is error. Token and cost sums also include rows whose kind is sampling.

StatusBodyWhen
200A days array.The usage range was read.
400A schema validation error object.A query value failed validation.
400{ "error": "Usage day range must span at most 366 days" }The range is negative, greater than 366 days or contains a date that is not a real UTC day.
401An authentication error object.The authorization header is missing, malformed or expired.
403{ "error": "This endpoint requires an API key" }The request does not use an API key.

Get the current usage period

GET /v1/projects/usage/period

This route has no query parameters and no request body. The period is the UTC calendar month from the first day at 00:00 through the first day of the next month at 00:00.

Request
GET /v1/projects/usage/period
curl
curl https://backend.assistant-api.com/v1/projects/usage/period \
  -H "Authorization: Bearer $ASSISTANT_API_KEY" \
  -H "Aui-User-Id: user_123" \
  -H "Aui-Workspace-Id: workspace_123"
Response
{
  "plan": "free",
  "period_start": "2026-09-01T00:00:00.000Z",
  "period_end": "2026-10-01T00:00:00.000Z",
  "active_users": 0,
  "included": 200,
  "cap": 200,
  "capped_at": null,
  "thread_titles": {
    "used": 0,
    "completed": 0,
    "included": 500,
    "cap": 500,
    "capped_at": null
  }
}
FieldMeaning
planThe plan identifier for the project.
period_start, period_endThe UTC start and exclusive end of the current calendar month.
active_usersThe active user count for the period. It is the greater of the stored period count and the sum of period users by day.
included, capThe active user allowance and cap resolved for the project.
capped_atThe time the active user cap was reached, or null.
thread_titlesThe title usage counters, allowance, cap and cap time for the period.
StatusBodyWhen
200The full usage period object.The project exists and the period was read.
401An authentication error object.The authorization header is missing, malformed or expired.
403{ "error": "This endpoint requires an API key" }The request does not use an API key.
404{ "error": "Project not found" }The project row does not exist.

The response reports the current UTC period and does not reserve or consume allowance.

List project threads

GET /v1/projects/threads
QueryTypeDefaultRules
is_archivedtrue or falsefalseList archived or unarchived threads. A response does not mix both.
external_idstringReturn only threads with this external id.
limitinteger201 to 100.
afterthread idContinue after this thread in the list order.

The query is the same as the thread list. The route searches every workspace in the project. It returns raw thread table rows, which carry more fields than the thread object.

Request
GET /v1/projects/threads?is_archived=false&limit=20
curl
curl "https://backend.assistant-api.com/v1/projects/threads?is_archived=false&limit=20" \
  -H "Authorization: Bearer $ASSISTANT_API_KEY" \
  -H "Aui-User-Id: user_123" \
  -H "Aui-Workspace-Id: workspace_123"
Response
{
  "threads": [
    {
      "id": "thread_0qzof3jPoDwr7K3agyJN3D4U",
      "project_id": "proj_0ltyjcuaxpv1",
      "workspace_id": "workspace_0ouwwTGaKr9lBxsYD5mPSWga",
      "created_by": "usr_anon_0ouwwTGaKr9lBxsYD5mPSWga",
      "created_at": "2026-09-17T02:40:30.497Z",
      "updated_by": "usr_anon_0ouwwTGaKr9lBxsYD5mPSWga",
      "updated_at": "2026-09-17T02:40:32.478Z",
      "title": "Capital of Portugal and a pastry",
      "last_message_at": "2026-09-17T02:40:31.932Z",
      "is_archived": false,
      "external_id": null,
      "metadata": null,
      "run_count": 1,
      "copied": false,
      "stopped": false,
      "regenerated": false,
      "edited": false,
      "cost_usd": "0.0012",
      "total_tokens": 42,
      "thumbs_up": false,
      "thumbs_down": false,
      "feedback_positive": 1,
      "feedback_negative": 0,
      "topic": null,
      "task": null,
      "review": null,
      "review_note": null
    }
  ]
}

Each raw row includes created_by, updated_by, copied, stopped, regenerated, edited, thumbs_up and thumbs_down, as well as run_count as an integer counting the conversation's runs, cost_usd as a decimal string summing those runs' cost, and total_tokens as an integer for the input plus output tokens of those runs. feedback_positive and feedback_negative are integers counting thumbs up and down ratings on the thread's messages. topic and task are the names the Intelligence analysis gave the thread, or null; a junk classification has null for both. review is good, needs_work, unclear or null, the latest review a person on the team recorded on the conversation. review_note is that review's note or null. Titles generated by the cloud are not counted in run_count, cost_usd or total_tokens.

Threads use the thread list ordering and after cursor rules. A successful response always has threads, including an empty array when nothing matches.

StatusBodyWhen
200A threads array.The project wide read completed.
400A schema validation error object.A query value failed validation.
401An authentication error object.The authorization header is missing, malformed or expired.
403{ "error": "Project-wide thread listing requires API key authentication" }The credential is not an API key, or API key access failed.

List messages in a project thread

GET /v1/projects/threads/{thread_id}/messages
Parameter or queryTypeRequiredDefaultRules
threadIdstringyes1 to 255 characters.
formatstringnoStored formatRequests a supported conversion of message content.
limitintegerno2001 to 200.
aftermessage idnoContinue after this message.

The route applies the same format conversion as the user message list. Stored aui/v0 rows can convert to ai-sdk/v5 or ai-sdk/v6, and stored ai-sdk/v5 rows can convert to ai-sdk/v6. Other formats are returned as stored. A conversion that cannot parse a row skips that row rather than failing the whole list. When attachment storage is configured, attachment URLs in the content are replaced with signed reads.

Request
GET /v1/projects/threads/thread_0qzof3jPoDwr7K3agyJN3D4U/messages?format=ai-sdk%2Fv6&limit=200
curl
curl "https://backend.assistant-api.com/v1/projects/threads/thread_0qzof3jPoDwr7K3agyJN3D4U/messages?format=ai-sdk%2Fv6&limit=200" \
  -H "Authorization: Bearer $ASSISTANT_API_KEY" \
  -H "Aui-User-Id: user_123" \
  -H "Aui-Workspace-Id: workspace_123"
Response
{
  "messages": [
    {
      "id": "msg_000000000000000000000000",
      "parent_id": null,
      "created_at": "2026-09-17T02:40:31.000Z",
      "updated_at": "2026-09-17T02:40:31.000Z",
      "format": "ai-sdk/v6",
      "content": {
        "role": "user",
        "parts": [{ "type": "text", "text": "Where is order 1042?" }]
      },
      "height": 0
    }
  ]
}

The response contains raw message rows with the requested format and converted content.

StatusBodyWhen
200A messages array.The project wide message read completed.
400A schema validation error object.A parameter or query value failed validation.
401An authentication error object.The authorization header is missing, malformed or expired.
403{ "error": "Project-wide message listing requires API key authentication" }The request does not use an API key, or API key access failed.

Format conversion and attachment signing affect the returned representation only; they do not rewrite stored messages.

List project scores

GET /v1/projects/scores
QueryTypeDefaultRules
namestring1 to 64 characters.
thread_idstring1 to 48 characters.
run_idstring1 to 48 characters.
sinceISO 8601 datetimeInclude scores from this time.
limitinteger501 to 200.
afterscore id1 to 48 characters. Resolves the score cursor.

There is no source query filter on this route. Scores are ordered by created_at DESC, id DESC. after resolves the referenced score and continues after its (created_at, id) position in that same order.

Request
GET /v1/projects/scores?name=quality&limit=50
curl
curl "https://backend.assistant-api.com/v1/projects/scores?name=quality&limit=50" \
  -H "Authorization: Bearer $ASSISTANT_API_KEY" \
  -H "Aui-User-Id: user_123" \
  -H "Aui-Workspace-Id: workspace_123"
Response
{
  "scores": [
    {
      "id": "score_000000000000000000000000",
      "thread_id": "thread_0qzof3jPoDwr7K3agyJN3D4U",
      "message_id": null,
      "run_id": null,
      "name": "quality",
      "data_type": "numeric",
      "value": 0,
      "string_value": null,
      "source": "api",
      "author_id": null,
      "comment": null,
      "created_at": "2026-09-17T00:00:00.000Z",
      "updated_at": "2026-09-17T00:00:00.000Z"
    }
  ]
}

Each score contains id, thread_id, message_id, run_id, name, data_type, value, string_value, source, author_id, comment, created_at and updated_at. value is returned as a number or null.

StatusBodyWhen
200A scores array.The score read completed. There is no next_cursor field.
400A schema validation error object.A query value failed validation.
401An authentication error object.The authorization header is missing, malformed or expired.
403{ "error": "Project-wide score listing requires API key authentication" }The request does not use an API key, or API key access failed.

List project skills

GET /v1/projects/skills

This API key only route has no parameters or pagination. It returns only published skills in name order.

Request
GET /v1/projects/skills
curl
curl https://backend.assistant-api.com/v1/projects/skills \
  -H "Authorization: Bearer $ASSISTANT_API_KEY" \
  -H "Aui-User-Id: user_123" \
  -H "Aui-Workspace-Id: workspace_123"
Response
{
  "skills": [
    {
      "id": "task_…",
      "name": "…",
      "description": null,
      "instructions": "…",
      "task": {
        "id": "task_…",
        "name": "…"
      },
      "published_at": "2026-09-17T00:00:00.000Z",
      "updated_at": "2026-09-17T00:00:00.000Z"
    }
  ]
}

Each item has an id, name, description that is a string or null, instructions, a task object with string id and name, and ISO timestamps in published_at and updated_at.

Publish a skill from the task page in the dashboard: Intelligence, open a task, Skill, then Publish. Update saves later edits and moves updated_at. Unpublish withdraws the skill. Publishing needs no LLM provider or assistant. A newer draft from the analysis never replaces the published text.

Using skills from a backend

Fetch the list at startup or on a timer, and cache it by each skill's updated_at. You can append every skill's instructions under its name to the system prompt. Alternatively, include only each skill's name and description in the prompt, then let the model load one skill's instructions with a tool when it applies.

This example uses the second pattern. The model sees the skill list from systemPrompt(), built for each request so a refresh shows up; loadSkill returns instructions only after it selects a skill.

Load a published skill
type Skill = {
  id: string;
  name: string;
  description: string | null;
  instructions: string;
  task: { id: string; name: string };
  published_at: string;
  updated_at: string;
};

let skills: Skill[] = [];
const updatedAtById = new Map<string, string>();

async function refreshSkills() {
  const response = await fetch("https://backend.assistant-api.com/v1/projects/skills", {
    headers: {
      Authorization: `Bearer ${process.env.ASSISTANT_API_KEY}`,
      "Aui-User-Id": "user_123",
      "Aui-Workspace-Id": "workspace_123",
    },
  });

  if (!response.ok) throw new Error("Could not load project skills");

  const { skills: fetchedSkills } = (await response.json()) as { skills: Skill[] };
  const isCurrent =
    fetchedSkills.length === skills.length &&
    fetchedSkills.every((skill) => updatedAtById.get(skill.id) === skill.updated_at);

  if (isCurrent) return;

  skills = fetchedSkills;
  updatedAtById.clear();
  for (const skill of skills) updatedAtById.set(skill.id, skill.updated_at);
}

await refreshSkills();
setInterval(() => {
  refreshSkills().catch((error) => console.error("skill refresh failed", error));
}, 5 * 60_000);

function systemPrompt() {
  return [
    "Use a skill when it applies.",
    "Available skills:",
    ...skills.map((skill) => `- ${skill.name}: ${skill.description ?? "No description."}`),
  ].join("\n");
}

const loadSkill = {
  name: "load_skill",
  description: "Return the published instructions for a listed skill.",
  async execute({ name }: { name: string }) {
    const skill = skills.find((candidate) => candidate.name === name);
    if (!skill) throw new Error(`No published skill named ${name}`);
    return skill.instructions;
  },
};
StatusBodyWhen
200{ "skills": [...] }The skills were read in name order.
401An authentication error object.The authorization header is missing, malformed or expired.
403{ "error": "Project skills require API key authentication" }A JWT caller uses this API key only route.