> ## Documentation Index
> Fetch the complete documentation index at: https://docs.traversal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Knowledge Files API

> Upload runbooks, reference documents, and agent skills to Traversal with the V1 Knowledge Files API.

The Knowledge Files API lets you manage the customer-authored files that Traversal agents use during investigations. You can upload runbooks and reference documents, define reusable agent skills, replace those files, or delete them.

All endpoints require authentication — see [Authentication](/api/authentication) to create an API key. For the base URL and error envelope, see the [API overview](/api/overview).

## Endpoints

See the **Endpoints** section in the sidebar for the full API reference, generated from the OpenAPI spec.

API keys inherit the role of the user who created them. Every Knowledge Files endpoint requires the `member` role or higher. Requests use the organization bound to the API key — you cannot read or modify another organization's files.

## File organization

Every path must start with one of these roots:

| Root              | Use it for                                                                                            |
| ----------------- | ----------------------------------------------------------------------------------------------------- |
| `user_generated/` | Runbooks, architecture notes, service catalogs, and other reference material.                         |
| `skills/`         | Agent skills. Each skill uses a directory containing a `SKILL.md` file and optional supporting files. |

The API does not expose Traversal-generated knowledge or agent memories.

Paths are case-sensitive and must:

* Use `/` separators
* Be relative, not absolute
* Exclude `.` and `..` segments
* Exclude control characters
* Use at most 900 UTF-8 bytes in total and 255 bytes per segment

### Supported file formats

Each upload request can contain at most **10 MiB (10,485,760 bytes)**. The file extension and `Content-Type` must describe the same format.

| Extensions         | Accepted `Content-Type`                               |
| ------------------ | ----------------------------------------------------- |
| `.md`, `.markdown` | `text/markdown`                                       |
| `.txt`             | `text/plain`                                          |
| `.csv`             | `text/csv`                                            |
| `.tsv`             | `text/tab-separated-values`, `text/tsv`               |
| `.json`            | `application/json`                                    |
| `.yaml`, `.yml`    | `application/yaml`, `application/x-yaml`, `text/yaml` |
| `.pdf`             | `application/pdf`                                     |

You can send `application/octet-stream` to infer the stored content type from the extension.

## Upload a file

Send the file bytes directly as the `PUT` request body. Do not wrap the content in JSON or multipart form data.

```bash theme={null}
curl -X PUT https://api.traversal.com/v1/knowledge/files/user_generated/runbooks/checkout.md \
  -H "Authorization: Bearer $TRAVERSAL_API_KEY" \
  -H "Content-Type: text/markdown" \
  -H "If-None-Match: *" \
  --data-binary @checkout.md
```

`If-None-Match: *` prevents an existing file from being overwritten. A successful create returns `201 Created` with file metadata, `ETag`, and `Location`.

```json theme={null}
{
  "path": "user_generated/runbooks/checkout.md",
  "content_type": "text/markdown",
  "size_bytes": 2841,
  "sha256": "e89873d4d7f2b6d71e414f184425ca95410cb3dfd41a497b3d9f9e12a891c617",
  "etag": "\"d50c991679d03cc0b4b29afec405e581c89fca80d3a7c31ab2e8ab7d34f3d78d\"",
  "created_at": "2026-08-25T15:30:00Z",
  "updated_at": "2026-08-25T15:30:00Z",
  "created": true
}
```

<Warning>
  Without a conditional header, `PUT` creates a missing file or completely replaces an existing file. An unconditional replacement returns `200 OK`.
</Warning>

## Write files safely

### Create only if the path is unused

Use `If-None-Match: *`, as shown in the upload example, whenever you intend to create a new file. Traversal returns `412 Precondition Failed` if the path already exists or another request creates it concurrently.

### Replace only the version you retrieved

First retrieve the current metadata and save its `etag` value:

```bash theme={null}
ETAG=$(curl --silent --show-error \
  "https://api.traversal.com/v1/knowledge/files/user_generated/runbooks/checkout.md" \
  -H "Authorization: Bearer $TRAVERSAL_API_KEY" \
  | jq -r '.etag')
```

Then send the saved value in `If-Match`:

```bash theme={null}
curl -X PUT https://api.traversal.com/v1/knowledge/files/user_generated/runbooks/checkout.md \
  -H "Authorization: Bearer $TRAVERSAL_API_KEY" \
  -H "Content-Type: text/markdown" \
  -H "If-Match: $ETAG" \
  --data-binary @checkout.md
```

If the file changed or moved after you retrieved it, Traversal returns `412 Precondition Failed`. Retrieve the latest metadata before deciding whether to retry.

You can also send `If-Match: *` to replace the file only if the path already exists.

An unconditional `PUT` retries one concurrent replacement, then returns `409 Conflict` if the file still changed underneath the request. Retry the write after fetching the latest metadata.

## Delete a file

```bash theme={null}
curl -X DELETE https://api.traversal.com/v1/knowledge/files/user_generated/runbooks/checkout.md \
  -H "Authorization: Bearer $TRAVERSAL_API_KEY"
```

A successful delete returns `204 No Content`. A missing or hidden path returns `404 Not Found`.

To delete only the version you retrieved, send its `etag` in `If-Match`:

```bash theme={null}
curl -X DELETE https://api.traversal.com/v1/knowledge/files/user_generated/runbooks/checkout.md \
  -H "Authorization: Bearer $TRAVERSAL_API_KEY" \
  -H "If-Match: $ETAG"
```

A stale `If-Match` returns `412 Precondition Failed`. An unconditional `DELETE` retries one concurrent delete, then returns `409 Conflict` if another request still wins the race.

## Create an agent skill

An agent skill is a directory under `skills/` with a `SKILL.md` file. The file starts with YAML frontmatter containing a unique `name` and a `description`, followed by the instructions:

```markdown theme={null}
---
name: checkout-triage
description: Investigate failures and latency in the checkout service.
---

1. Read `references/services.md` for service ownership.
2. Check checkout request errors and latency.
3. Compare the first failing deployment with the incident start time.
```

Upload the main skill file:

```bash theme={null}
curl -X PUT https://api.traversal.com/v1/knowledge/files/skills/checkout-triage/SKILL.md \
  -H "Authorization: Bearer $TRAVERSAL_API_KEY" \
  -H "Content-Type: text/markdown" \
  -H "If-None-Match: *" \
  --data-binary @SKILL.md
```

Upload supporting files to the same directory with separate requests:

```bash theme={null}
curl -X PUT https://api.traversal.com/v1/knowledge/files/skills/checkout-triage/references/services.md \
  -H "Authorization: Bearer $TRAVERSAL_API_KEY" \
  -H "Content-Type: text/markdown" \
  -H "If-None-Match: *" \
  --data-binary @references/services.md
```

Traversal validates `SKILL.md` when you upload it. Invalid frontmatter or a duplicate skill name returns `400 Bad Request`.

## List files

List files under both customer-managed roots:

```bash theme={null}
curl \
  "https://api.traversal.com/v1/knowledge/files?limit=100&page=1" \
  -H "Authorization: Bearer $TRAVERSAL_API_KEY"
```

Use `prefix` to list one subtree:

```bash theme={null}
curl \
  --get \
  "https://api.traversal.com/v1/knowledge/files" \
  -H "Authorization: Bearer $TRAVERSAL_API_KEY" \
  --data-urlencode "prefix=skills/checkout-triage" \
  --data-urlencode "limit=100" \
  --data-urlencode "page=1"
```

Files are ordered by path. If you omit pagination parameters, `page` defaults to `1` and `limit` defaults to `50`; the maximum limit is `100`. The response includes `count`, `total`, `prev`, and `next`.

## Download a file

```bash theme={null}
curl \
  "https://api.traversal.com/v1/knowledge/files/user_generated/runbooks/checkout.md/content" \
  -H "Authorization: Bearer $TRAVERSAL_API_KEY" \
  --output checkout.md
```

Downloads stream the original bytes and return the stored `Content-Type`, `Content-Length`, `Content-Disposition`, and `ETag`. Responses also include `Cache-Control: private, no-cache, must-revalidate` and `X-Content-Type-Options: nosniff`.

To avoid downloading an unchanged file, send its previous `ETag` in `If-None-Match`:

```bash theme={null}
curl \
  "https://api.traversal.com/v1/knowledge/files/user_generated/runbooks/checkout.md/content" \
  -H "Authorization: Bearer $TRAVERSAL_API_KEY" \
  -H 'If-None-Match: "d50c991679d03cc0b4b29afec405e581c89fca80d3a7c31ab2e8ab7d34f3d78d"' \
  --write-out "HTTP %{http_code}\n" \
  --output checkout.md.download
```

An unchanged file returns `304 Not Modified` with no body and includes the current `ETag` and `Cache-Control` headers. Replace your local copy only when the response is `200 OK`.

## Knowledge-specific errors

In addition to the [generic status codes](/api/overview#status-codes), these responses are common:

| Status code | Error code               | When it occurs                                                                                                                              |
| ----------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`       | `invalid_argument`       | The path is invalid or outside the API-visible roots, or a `SKILL.md` file has invalid frontmatter or a duplicate skill name.               |
| `404`       | `not_found`              | The path does not exist or is hidden.                                                                                                       |
| `409`       | `conflict`               | A concurrent write or delete still conflicts after one retry, a skill name is already taken, or stored metadata is inconsistent.            |
| `412`       | `precondition_failed`    | `If-Match` is stale, the file moved, `If-Match` was sent for a missing path, or an `If-None-Match` create-only condition failed.            |
| `413`       | `payload_too_large`      | The request body exceeds 10 MiB (10,485,760 bytes).                                                                                         |
| `415`       | `unsupported_media_type` | The extension or `Content-Type` is unsupported, they do not match, or an unsupported non-identity `Content-Encoding` such as `gzip` is set. |
