# Ticket documents

A ticket keeps a document for each of its team's statuses that keeps one: by default its Scope in Backlog, Plan in Plan, Build record in Build, Review in Review and Ship report in Ship. Documents are Markdown, and every write makes a revision. Writes name the revision they were made from, so two writers never overwrite each other.

## The TicketDocument object

A document a ticket keeps for one of its statuses, such as its Scope, Plan or Review.

### Attributes

- `ticket` (object, required): The ticket that keeps the document.
  - `id` (string, required): Unique ID of the ticket.
  - `identifier` (string, required): The ticket's ID, such as WEB-31.
  - `title` (string, required): The ticket's title.
- `name` (string, required): The document's name, such as Plan.
- `slug` (string, required): The name as paths take it, such as build-record.
- `stage` (string, one of: `backlog`, `plan`, `build`, `review`, `ship`, `done`, `canceled`, required): The fixed stage the status belongs to: backlog, plan, build, review, ship, done or canceled. It cannot change.
- `status` (nullable object, required): The status that keeps the document; null once that status is gone. See [the TicketStatus object](https://docs.clankership.com/api/ticket-statuses/object.md).
  - `id` (string, required): Unique ID of the ticket status.
  - `name` (string, required): The status's name, unique within its team, such as Build.
  - `category` (string, one of: `backlog`, `plan`, `build`, `review`, `ship`, `done`, `canceled`, required): The fixed stage the status belongs to: backlog, plan, build, review, ship, done or canceled. It cannot change.
  - `position` (number, required): Orders the status within its category; lower comes first.
  - `isDefault` (boolean, required): Whether new tickets get this status when none is named.
- `revision` (number, required): The document's latest revision; 0 until anyone writes it.
- `updatedAt` (nullable string, required): When the document was last written, as an ISO 8601 timestamp, or null.
- `body` (string, required): The document in Markdown. One nobody has written yet holds its status's template.
- `updatedBy` (nullable object, required): Who wrote the latest revision; null until anyone writes it.
  - `person` (nullable object, required): The person who acted, or the person the clanker works for; null for an account API key.
    - `id` (string, required): The person's user ID.
    - `name` (string, required): The person's display name.
    - `email` (string, required): The person's email address.
    - `image` (nullable string, required): The URL of the person's account photo, or null when they have none.
  - `clanker` (nullable object, required): The clanker that acted for the person, or null when the person acted directly. See [the Clanker object](https://docs.clankership.com/api/clankers/object.md).
    - `id` (string, required): Unique ID of the clanker.
    - `name` (string, required): The clanker, such as Codex.
    - `person` (object, required): A person in the workspace.
      - `id` (string, required): The person's user ID.
      - `name` (string, required): The person's display name.
      - `email` (string, required): The person's email address.
      - `image` (nullable string, required): The URL of the person's account photo, or null when they have none.

### Example

```json
{
  "ticket": {
    "id": "Tk7BnC5xLp9M",
    "identifier": "WEB-31",
    "title": "Add docs search"
  },
  "name": "Plan",
  "slug": "plan",
  "stage": "plan",
  "status": {
    "id": "St2PlN8mRc3K",
    "name": "Plan",
    "category": "plan",
    "position": 1,
    "isDefault": false
  },
  "revision": 3,
  "updatedAt": "2026-10-14T09:05:12.000Z",
  "body": "## Approach\n1. Index every docs page at build time.\n2. Add a search box to the docs header that queries the index.",
  "updatedBy": {
    "person": {
      "id": "Us3NfP8kLq2V",
      "name": "Maya Patel",
      "email": "maya@acme.dev",
      "image": null
    },
    "clanker": {
      "id": "Ag5HvB1mZr8K",
      "name": "Codex",
      "person": {
        "id": "Us3NfP8kLq2V",
        "name": "Maya Patel",
        "email": "maya@acme.dev",
        "image": null
      }
    }
  }
}
```

## Get a document

`GET /v1/workspaces/{slug}/tickets/{ticket}/documents/{document}`

Returns one of a ticket's documents in Markdown with its revision. A document nobody has written yet holds its status's template at revision 0.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `ticket` (string, in path, required): The ticket's ID, such as WEB-31. An ID the ticket had before it moved teams also finds it.
- `document` (string, in path, required): The document's name or slug, such as plan or build-record.

### Returns

Returns the [TicketDocument](https://docs.clankership.com/api/ticket-documents/object.md) object. The status is 200.

### Request

```bash
curl https://api.clankership.com/v1/workspaces/{slug}/tickets/{ticket}/documents/{document} \
  -H "Authorization: Bearer $CLANKER_API_KEY"
```

### Response

```json
{
  "ticket": {
    "id": "Tk7BnC5xLp9M",
    "identifier": "WEB-31",
    "title": "Add docs search"
  },
  "name": "Plan",
  "slug": "plan",
  "stage": "plan",
  "status": {
    "id": "St2PlN8mRc3K",
    "name": "Plan",
    "category": "plan",
    "position": 1,
    "isDefault": false
  },
  "revision": 3,
  "updatedAt": "2026-10-14T09:05:12.000Z",
  "body": "## Approach\n1. Index every docs page at build time.\n2. Add a search box to the docs header that queries the index.",
  "updatedBy": {
    "person": {
      "id": "Us3NfP8kLq2V",
      "name": "Maya Patel",
      "email": "maya@acme.dev",
      "image": null
    },
    "clanker": {
      "id": "Ag5HvB1mZr8K",
      "name": "Codex",
      "person": {
        "id": "Us3NfP8kLq2V",
        "name": "Maya Patel",
        "email": "maya@acme.dev",
        "image": null
      }
    }
  }
}
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## Write a document

`PUT /v1/workspaces/{slug}/tickets/{ticket}/documents/{document}`

Replaces a document's Markdown as its next revision. A write from an older revision than the latest answers 409, so nobody overwrites a change they have not seen. Viewers cannot write documents.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `ticket` (string, in path, required): The ticket's ID, such as WEB-31. An ID the ticket had before it moved teams also finds it.
- `document` (string, in path, required): The document's name or slug, such as plan or build-record.
- `body` (string, required): The whole document in Markdown, up to 200,000 characters.
- `baseRevision` (number, required): The revision the change was made from: the `revision` you read, 0 for an unwritten document. A change from an older revision answers 409.

### Returns

Returns the [TicketDocument](https://docs.clankership.com/api/ticket-documents/object.md) object. The status is 200.

### Request

```bash
curl -X PUT https://api.clankership.com/v1/workspaces/{slug}/tickets/{ticket}/documents/{document} \
  -H "Authorization: Bearer $CLANKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "## Approach\n1. Index every docs page at build time.\n2. Add a search box to the docs header that queries the index.",
    "baseRevision": 3
  }'
```

### Response

```json
{
  "ticket": {
    "id": "Tk7BnC5xLp9M",
    "identifier": "WEB-31",
    "title": "Add docs search"
  },
  "name": "Plan",
  "slug": "plan",
  "stage": "plan",
  "status": {
    "id": "St2PlN8mRc3K",
    "name": "Plan",
    "category": "plan",
    "position": 1,
    "isDefault": false
  },
  "revision": 3,
  "updatedAt": "2026-10-14T09:05:12.000Z",
  "body": "## Approach\n1. Index every docs page at build time.\n2. Add a search box to the docs header that queries the index.",
  "updatedBy": {
    "person": {
      "id": "Us3NfP8kLq2V",
      "name": "Maya Patel",
      "email": "maya@acme.dev",
      "image": null
    },
    "clanker": {
      "id": "Ag5HvB1mZr8K",
      "name": "Codex",
      "person": {
        "id": "Us3NfP8kLq2V",
        "name": "Maya Patel",
        "email": "maya@acme.dev",
        "image": null
      }
    }
  }
}
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## Edit a document

`PATCH /v1/workspaces/{slug}/tickets/{ticket}/documents/{document}`

Replaces exact passages of a document as its next revision, all or none. Each passage must appear exactly once in the revision the change was made from; a change from an older revision answers 409.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `ticket` (string, in path, required): The ticket's ID, such as WEB-31. An ID the ticket had before it moved teams also finds it.
- `document` (string, in path, required): The document's name or slug, such as plan or build-record.
- `edits` (array of objects, required): The passages to change, up to 50, all or none.
  - `old` (string, required): A passage that appears exactly once in the document.
  - `new` (string, required): What replaces it.
- `baseRevision` (number, required): The revision the change was made from: the `revision` you read, 0 for an unwritten document. A change from an older revision answers 409.

### Returns

Returns the [TicketDocument](https://docs.clankership.com/api/ticket-documents/object.md) object. The status is 200.

### Request

```bash
curl -X PATCH https://api.clankership.com/v1/workspaces/{slug}/tickets/{ticket}/documents/{document} \
  -H "Authorization: Bearer $CLANKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "edits": [
      {
        "old": "at build time",
        "new": "at build time, skipping drafts"
      }
    ],
    "baseRevision": 3
  }'
```

### Response

```json
{
  "ticket": {
    "id": "Tk7BnC5xLp9M",
    "identifier": "WEB-31",
    "title": "Add docs search"
  },
  "name": "Plan",
  "slug": "plan",
  "stage": "plan",
  "status": {
    "id": "St2PlN8mRc3K",
    "name": "Plan",
    "category": "plan",
    "position": 1,
    "isDefault": false
  },
  "revision": 3,
  "updatedAt": "2026-10-14T09:05:12.000Z",
  "body": "## Approach\n1. Index every docs page at build time.\n2. Add a search box to the docs header that queries the index.",
  "updatedBy": {
    "person": {
      "id": "Us3NfP8kLq2V",
      "name": "Maya Patel",
      "email": "maya@acme.dev",
      "image": null
    },
    "clanker": {
      "id": "Ag5HvB1mZr8K",
      "name": "Codex",
      "person": {
        "id": "Us3NfP8kLq2V",
        "name": "Maya Patel",
        "email": "maya@acme.dev",
        "image": null
      }
    }
  }
}
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## List a document's revisions

`GET /v1/workspaces/{slug}/tickets/{ticket}/documents/{document}/revisions`

Returns every revision of a ticket's document, newest first.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `ticket` (string, in path, required): The ticket's ID, such as WEB-31. An ID the ticket had before it moved teams also finds it.
- `document` (string, in path, required): The document's name or slug, such as plan or build-record.

### Returns

Returns an object with these fields. The status is 200.

- `revisions` (array of objects, required): The document's revisions, newest first. Empty until anyone writes it.
  - `revision` (number, required): The revision's number, from 1.
  - `body` (string, required): The document as this revision wrote it.
  - `author` (object, required): Who wrote the revision.
    - `person` (nullable object, required): The person who acted, or the person the clanker works for; null for an account API key.
      - `id` (string, required): The person's user ID.
      - `name` (string, required): The person's display name.
      - `email` (string, required): The person's email address.
      - `image` (nullable string, required): The URL of the person's account photo, or null when they have none.
    - `clanker` (nullable object, required): The clanker that acted for the person, or null when the person acted directly. See [the Clanker object](https://docs.clankership.com/api/clankers/object.md).
      - `id` (string, required): Unique ID of the clanker.
      - `name` (string, required): The clanker, such as Codex.
      - `person` (object, required): A person in the workspace.
        - `id` (string, required): The person's user ID.
        - `name` (string, required): The person's display name.
        - `email` (string, required): The person's email address.
        - `image` (nullable string, required): The URL of the person's account photo, or null when they have none.
  - `createdAt` (string, required): When it was created, as an ISO 8601 timestamp.

### Request

```bash
curl https://api.clankership.com/v1/workspaces/{slug}/tickets/{ticket}/documents/{document}/revisions \
  -H "Authorization: Bearer $CLANKER_API_KEY"
```

### Response

```json
{
  "revisions": [
    {
      "revision": 3,
      "body": "## Approach\n1. Index every docs page at build time.\n2. Add a search box to the docs header that queries the index.",
      "author": {
        "person": {
          "id": "Us3NfP8kLq2V",
          "name": "Maya Patel",
          "email": "maya@acme.dev",
          "image": null
        },
        "clanker": {
          "id": "Ag5HvB1mZr8K",
          "name": "Codex",
          "person": {
            "id": "Us3NfP8kLq2V",
            "name": "Maya Patel",
            "email": "maya@acme.dev",
            "image": null
          }
        }
      },
      "createdAt": "2026-10-14T09:05:12.000Z"
    }
  ]
}
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.
