# Project read API
URL: /docs/cloud/api/project

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

> For AI agents: a documentation index is available at [llms.txt](/llms.txt). Use `.md` for canonical markdown pages; `.mdx` is kept as a backwards-compatible alias on supported URL paths.

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](/docs/cloud/api) for the shared hosts and credentials, and [Errors](/docs/cloud/api#errors) for error shapes.

## The project run object

| Field                                                                      | Type                   | Meaning                                                                              |
| -------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------ |
| `id`                                                                       | string                 | The run identifier.                                                                  |
| `thread_id`, `message_id`                                                  | string or null         | The thread and message associated with the run.                                      |
| `created_by`                                                               | string or null         | The identity that created the run.                                                   |
| `status`, `outcome_type`, `error_code`                                     | stored values          | The run status, outcome classification and error code.                               |
| `provider`, `model_id`                                                     | string or null         | The provider and model used by the run.                                              |
| `environment`, `release`                                                   | string or null         | The environment and release recorded for the run.                                    |
| `tags`                                                                     | array                  | Tags recorded on the run. The `tag` query filters for a matching tag.                |
| `metadata`, `attributes`                                                   | object                 | The same object under both names. `metadata` remains for SDK backward compatibility. |
| `root_span_id`                                                             | string or null         | The root span identifier.                                                            |
| `service_name`, `agent_name`                                               | string or null         | Service and agent names recorded for the run.                                        |
| `usage_details`, `cost_details`                                            | object or null         | Detailed usage and cost values recorded for the run.                                 |
| `input_tokens`, `cached_input_tokens`, `output_tokens`, `reasoning_tokens` | number or null         | Token counts recorded for the run.                                                   |
| `duration_ms`, `first_token_ms`                                            | number or null         | Run duration and time to the first token in milliseconds.                            |
| `cost_usd`                                                                 | decimal string or null | The run cost in US dollars.                                                          |
| `total_steps`                                                              | number or null         | The total number of recorded steps.                                                  |
| `assistant_id`                                                             | string or null         | The assistant associated with the run.                                               |
| `created_at`                                                               | ISO 8601               | When the run was created.                                                            |

## The project span object

| Field                                                                      | Type                   | Meaning                                                                                                   |
| -------------------------------------------------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------- |
| `id`, `run_id`, `parent_span_id`                                           | string or null         | Span, run and parent span identifiers.                                                                    |
| `created_at`, `started_at`, `ended_at`                                     | ISO 8601 or null       | Span creation, start and end times.                                                                       |
| `trace_id`, `span_id`                                                      | string or null         | W3C trace and span identifiers when exported from your server.                                            |
| `name`, `operation`                                                        | string or null         | Span name and operation.                                                                                  |
| `provider`, `tool_call_id`                                                 | string or null         | Provider and tool call associated with the span.                                                          |
| `status_code`, `error_type`                                                | stored values          | Span status code and error type.                                                                          |
| `attributes`                                                               | object or null         | Attributes recorded for the span.                                                                         |
| `span_index`                                                               | number                 | The span index used to order spans with the same start time.                                              |
| `type`, `step_type`                                                        | stored values          | `type` is `generation` or `tool_call`; `step_type` is `initial`, `tool-result`, `continue` or `sampling`. |
| `model_id`, `tool_name`, `tool_source`                                     | string or null         | Model and tool details recorded for the span.                                                             |
| `input_tokens`, `cached_input_tokens`, `output_tokens`, `reasoning_tokens` | number or null         | Token counts recorded for the span.                                                                       |
| `start_ms`, `end_ms`                                                       | number or null         | Start and end times relative to the run.                                                                  |
| `finish_reason`, `status`, `error`                                         | stored values          | Completion and error values recorded for the span.                                                        |
| `cost_usd`                                                                 | decimal string or null | The span cost in US dollars.                                                                              |
| `usage_details`, `cost_details`                                            | object or null         | Detailed usage and cost values recorded for the span.                                                     |
| `input`, `output`                                                          | stored values          | The span input and output.                                                                                |

## List project runs

```
GET /v1/projects/runs
```

| Query          | Type              | Default | Rules                                         |
| -------------- | ----------------- | ------- | --------------------------------------------- |
| `status`       | enum              |         | One of the API run status values.             |
| `outcome_type` | enum              |         | One of the API run outcome values.            |
| `model_id`     | string            |         | 1 to 255 characters.                          |
| `provider`     | string            |         | 1 to 255 characters.                          |
| `created_by`   | string            |         | 1 to 255 characters.                          |
| `thread_id`    | string            |         | 1 to 48 characters.                           |
| `environment`  | string            |         | 1 to 64 characters.                           |
| `release`      | string            |         | 1 to 255 characters.                          |
| `tag`          | string            |         | 1 to 64 characters. Matches a tag on the run. |
| `from`         | ISO 8601 datetime |         | Include runs from this time.                  |
| `to`           | ISO 8601 datetime |         | Include runs through this time.               |
| `limit`        | integer           | 50      | 1 to 100.                                     |
| `cursor`       | string            |         | An opaque cursor, 1 to 512 characters.        |

```
GET /v1/projects/runs?limit=50&from=2026-09-01T00%3A00%3A00.000Z&to=2026-09-17T00%3A00%3A00.000Z
```

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

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

| Status | Body                                               | When                                                                    |
| ------ | -------------------------------------------------- | ----------------------------------------------------------------------- |
| `200`  | A runs array and a `next_cursor` value.            | The read completed. The array is empty when no runs match.              |
| `400`  | A schema validation error object.                  | A query value failed validation.                                        |
| `400`  | `{ "error": "Invalid run cursor" }`                | `cursor` is not a valid project run cursor.                             |
| `401`  | An 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}
```

| Parameter | Type   | Required | Rules               |
| --------- | ------ | -------- | ------------------- |
| `runId`   | string | yes      | 1 to 48 characters. |

```
GET /v1/projects/runs/run_000000000000000000000000
```

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

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

| Status | Body                                               | When                                                                    |
| ------ | -------------------------------------------------- | ----------------------------------------------------------------------- |
| `200`  | The run object with `spans`.                       | The run is visible in the authenticated project.                        |
| `400`  | A schema validation error object.                  | `runId` failed validation, including a value longer than 48 characters. |
| `401`  | An 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
```

| Query      | Type         | Default       | Rules                                                           |
| ---------- | ------------ | ------------- | --------------------------------------------------------------- |
| `from`     | `YYYY-MM-DD` | 30 day window | A real UTC calendar day. If omitted, it is 29 days before `to`. |
| `to`       | `YYYY-MM-DD` | Today in UTC  | A real UTC calendar day.                                        |
| `group_by` | enum         |               | `model`, 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`.

```
GET /v1/projects/usage/daily?from=2026-09-01&to=2026-09-17&group_by=model
```

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

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

| Status | Body                                                        | When                                                                                        |
| ------ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `200`  | A `days` array.                                             | The usage range was read.                                                                   |
| `400`  | A 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. |
| `401`  | An 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.

```
GET /v1/projects/usage/period
```

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

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

| Field                        | Meaning                                                                                                                |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `plan`                       | The plan identifier for the project.                                                                                   |
| `period_start`, `period_end` | The UTC start and exclusive end of the current calendar month.                                                         |
| `active_users`               | The active user count for the period. It is the greater of the stored period count and the sum of period users by day. |
| `included`, `cap`            | The active user allowance and cap resolved for the project.                                                            |
| `capped_at`                  | The time the active user cap was reached, or `null`.                                                                   |
| `thread_titles`              | The title usage counters, allowance, cap and cap time for the period.                                                  |

| Status | Body                                               | When                                                       |
| ------ | -------------------------------------------------- | ---------------------------------------------------------- |
| `200`  | The full usage period object.                      | The project exists and the period was read.                |
| `401`  | An 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
```

| Query         | Type              | Default | Rules                                                              |
| ------------- | ----------------- | ------- | ------------------------------------------------------------------ |
| `is_archived` | `true` or `false` | `false` | List archived or unarchived threads. A response does not mix both. |
| `external_id` | string            |         | Return only threads with this external id.                         |
| `limit`       | integer           | 20      | 1 to 100.                                                          |
| `after`       | thread id         |         | Continue after this thread in the list order.                      |

The query is the same as [the thread list](/docs/cloud/api/threads#list-threads). The route searches every workspace in the project. It returns raw thread table rows, which carry more fields than [the thread object](/docs/cloud/api/threads#the-thread-object).

```
GET /v1/projects/threads?is_archived=false&limit=20
```

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

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

| Status | Body                                                                         | When                                                        |
| ------ | ---------------------------------------------------------------------------- | ----------------------------------------------------------- |
| `200`  | A `threads` array.                                                           | The project wide read completed.                            |
| `400`  | A schema validation error object.                                            | A query value failed validation.                            |
| `401`  | An 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 query | Type       | Required | Default       | Rules                                               |
| ------------------ | ---------- | -------- | ------------- | --------------------------------------------------- |
| `threadId`         | string     | yes      |               | 1 to 255 characters.                                |
| `format`           | string     | no       | Stored format | Requests a supported conversion of message content. |
| `limit`            | integer    | no       | 200           | 1 to 200.                                           |
| `after`            | message id | no       |               | Continue 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.

```
GET /v1/projects/threads/thread_0qzof3jPoDwr7K3agyJN3D4U/messages?format=ai-sdk%2Fv6&limit=200
```

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

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

| Status | Body                                                                          | When                                                           |
| ------ | ----------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `200`  | A `messages` array.                                                           | The project wide message read completed.                       |
| `400`  | A schema validation error object.                                             | A parameter or query value failed validation.                  |
| `401`  | An 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
```

| Query       | Type              | Default | Rules                                          |
| ----------- | ----------------- | ------- | ---------------------------------------------- |
| `name`      | string            |         | 1 to 64 characters.                            |
| `thread_id` | string            |         | 1 to 48 characters.                            |
| `run_id`    | string            |         | 1 to 48 characters.                            |
| `since`     | ISO 8601 datetime |         | Include scores from this time.                 |
| `limit`     | integer           | 50      | 1 to 200.                                      |
| `after`     | score id          |         | 1 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.

```
GET /v1/projects/scores?name=quality&limit=50
```

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

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

| Status | Body                                                                        | When                                                           |
| ------ | --------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `200`  | A `scores` array.                                                           | The score read completed. There is no `next_cursor` field.     |
| `400`  | A schema validation error object.                                           | A query value failed validation.                               |
| `401`  | An 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.

```
GET /v1/projects/skills
```

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

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

```
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;
  },
};
```

| Status | Body                                                           | When                                                       |
| ------ | -------------------------------------------------------------- | ---------------------------------------------------------- |
| `200`  | `{ "skills": [...] }`                                          | The skills were read in name order.                        |
| `401`  | An 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.                 |