# Documents

> Create documents, name them, set link access, move them to the trash, export them and read their provenance.

A document is what people write in the embedded editor: its text, its suggestions and comments, and the record of how it was written. Every document has one or more owners, and other people reach it as [members](/reference/api/members/) or through its link access. See [Documents and access](/concepts/documents-and-access/).

## The document object

| Attribute | Type | Description |
| --- | --- | --- |
| `id` | string | Proof's id for the document, starting `doc_`. |
| `name` | string or null | The name an editor gave it, or `null` when it has none. |
| `title` | string | What to call it in your lists: its `name`, or its first line when it has no name. |
| `owners` | array | The Proof ids of its owners, in the order they became owners. |
| `link_access` | string | What people get by link without being members: `restricted`, `view`, `suggest` or `edit`. |
| `created_at` | timestamp | When it was created. |
| `edited_at` | timestamp | When its text last changed. |
| `trashed_at` | timestamp or null | When it was moved to the trash, or `null`. |

```json
{
  "id": "doc_8Hq2Lm4R",
  "name": "On the Analytical Engine",
  "title": "On the Analytical Engine",
  "owners": ["usr_5Kq2Lm8R"],
  "link_access": "restricted",
  "created_at": "2026-10-13T21:12:40Z",
  "edited_at": "2026-10-13T23:30:52Z",
  "trashed_at": null
}
```

## Create a document

`POST /v1/documents`

Creates an empty document owned by `owner`, who becomes its first member, and sends [`document.created`](/reference/webhooks/#documentcreated).

| Parameter | Type | Description |
| --- | --- | --- |
| `owner` | string, required | The Proof id of the user who owns it. The user must exist. |
| `name` | string | Up to 200 characters. Without one, the document is called by its first line. |
| `reuse_untouched` | boolean | `true` to answer with one of the owner's documents that nobody has touched yet, if there is one, instead of making another: none written in, named, shared or sealed. It comes back to the top of their list, with `200`. For a "New document" button people may press again and again. |
| `except` | array | With `reuse_untouched`, document ids not to reuse, such as the one already open. |

Starting text isn't supported yet: `markdown` is refused with `400 invalid_parameter`. People and agents write the text in the editor or through the [MCP tools](/reference/mcp/).

<Tabs syncKey="lang">
<TabItem label="curl">

```bash
curl https://api.proof.exalto.ai/v1/documents \
  -H "Authorization: Bearer $PROOF_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"owner": "usr_5Kq2Lm8R", "name": "On the Analytical Engine"}'
```

</TabItem>
<TabItem label="Node">

```ts
const doc = await proof.documents.create({
  owner: "usr_5Kq2Lm8R",
  name: "On the Analytical Engine",
});
```

</TabItem>
</Tabs>

Returns the [document](#the-document-object) with `201`, or a reused one with `200`.

```json
{
  "id": "doc_8Hq2Lm4R",
  "name": "On the Analytical Engine",
  "title": "On the Analytical Engine",
  "owners": ["usr_5Kq2Lm8R"],
  "link_access": "restricted",
  "created_at": "2026-10-13T21:12:40Z",
  "edited_at": "2026-10-13T21:12:40Z",
  "trashed_at": null
}
```

| Error | When |
| --- | --- |
| `400 invalid_parameter` on `markdown` | Starting text isn't supported yet. |
| `404 user_not_found` | `owner` doesn't exist. Create the user first. |

## Retrieve a document

`GET /v1/documents/{document_id}`

| Parameter | Type | Description |
| --- | --- | --- |
| `document_id` | string, path, required | The document's id. |

<Tabs syncKey="lang">
<TabItem label="curl">

```bash
curl https://api.proof.exalto.ai/v1/documents/doc_8Hq2Lm4R \
  -H "Authorization: Bearer $PROOF_SECRET_KEY"
```

</TabItem>
<TabItem label="Node">

```ts
const doc = await proof.documents.retrieve("doc_8Hq2Lm4R");
```

</TabItem>
</Tabs>

Returns the [document](#the-document-object), or `404 document_not_found`. Documents in the trash are returned with `trashed_at` set.

## Update a document

`PATCH /v1/documents/{document_id}`

Renames a document or changes its link access. Only the fields you send change. People in the editor see a new name at once.

| Parameter | Type | Description |
| --- | --- | --- |
| `document_id` | string, path, required | The document's id. |
| `name` | string or null | Up to 200 characters. `null` removes the name, so the document is called by its first line again. |
| `link_access` | string | `restricted`, `view`, `suggest` or `edit`. See [Share documents](/guides/share-documents/). |

<Tabs syncKey="lang">
<TabItem label="curl">

```bash
curl https://api.proof.exalto.ai/v1/documents/doc_8Hq2Lm4R \
  -X PATCH \
  -H "Authorization: Bearer $PROOF_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"link_access": "suggest"}'
```

</TabItem>
<TabItem label="Node">

```ts
const doc = await proof.documents.update("doc_8Hq2Lm4R", {
  link_access: "suggest",
});
```

</TabItem>
</Tabs>

Returns the updated [document](#the-document-object).

Lowering link access takes effect at once: people in the editor who came by link get the new role, or lose the document if it's now `restricted`.

## Move a document to the trash

`POST /v1/documents/{document_id}/trash`

A document goes to the trash for everyone when an owner trashes it. While it's there, its owners can still open it in the editor, only to read; nobody else can, agents can't reach it, and its seals are down. It's listed with `trashed=true` on [its owners' documents](/reference/api/users/#list-a-users-documents). Proof deletes it 30 days after it was trashed, as if you had called [Delete a document](#delete-a-document).

Any other member who trashes it puts it in their own trash only: it leaves their list and nobody else's, and they can still open it to read. After 30 days there, they stop being a member.

| Parameter | Type | Description |
| --- | --- | --- |
| `document_id` | string, path, required | The document's id. |
| `user` | string | The Proof id of the member trashing it. Leave it out to trash it as its first owner, for everyone. |

<Tabs syncKey="lang">
<TabItem label="curl">

```bash
curl https://api.proof.exalto.ai/v1/documents/doc_8Hq2Lm4R/trash \
  -X POST \
  -H "Authorization: Bearer $PROOF_SECRET_KEY"
```

</TabItem>
<TabItem label="Node">

```ts
const doc = await proof.documents.trash("doc_8Hq2Lm4R", { user: "usr_5Kq2Lm8R" });
```

</TabItem>
</Tabs>

Returns the [document](#the-document-object). With `user`, it has one more field, `trash`: `"everyone"` when it went to the trash for everyone, `"own"` when it went to the member's own, or `null` when it was there already.

```json
{
  "id": "doc_8Hq2Lm4R",
  "name": "On the Analytical Engine",
  "title": "On the Analytical Engine",
  "owners": ["usr_5Kq2Lm8R"],
  "link_access": "restricted",
  "created_at": "2026-10-13T21:12:40Z",
  "edited_at": "2026-10-13T23:30:52Z",
  "trashed_at": "2026-10-30T18:20:00Z",
  "trash": "everyone"
}
```

Trashing a document that's already in the trash returns it unchanged.

| Error | When |
| --- | --- |
| `403 not_member` | `user` isn't a member of the document. |

## Restore a document

`POST /v1/documents/{document_id}/restore`

Takes a document out of the trash, with its members, link access and agents' roles as they were. With `user`, it takes it out of that member's trash: an owner's restores it for everyone; anyone else's, for them.

| Parameter | Type | Description |
| --- | --- | --- |
| `document_id` | string, path, required | The document's id. |
| `user` | string | The Proof id of the member restoring it. Leave it out to restore it as its first owner. |

<Tabs syncKey="lang">
<TabItem label="curl">

```bash
curl https://api.proof.exalto.ai/v1/documents/doc_8Hq2Lm4R/restore \
  -X POST \
  -H "Authorization: Bearer $PROOF_SECRET_KEY"
```

</TabItem>
<TabItem label="Node">

```ts
const doc = await proof.documents.restore("doc_8Hq2Lm4R");
```

</TabItem>
</Tabs>

Returns the [document](#the-document-object), with `trash` when you passed `user`, as for [trashing](#move-a-document-to-the-trash).

## Delete a document

`DELETE /v1/documents/{document_id}`

Deletes a document now, for everyone. Open editors close, agents lose it, its images are deleted, and every seal made from it is withdrawn: its pages at `proof.exalto.ai` say it was withdrawn. This can't be undone; to give people a way back, [move it to the trash](#move-a-document-to-the-trash) instead.

| Parameter | Type | Description |
| --- | --- | --- |
| `document_id` | string, path, required | The document's id. |

<Tabs syncKey="lang">
<TabItem label="curl">

```bash
curl https://api.proof.exalto.ai/v1/documents/doc_8Hq2Lm4R \
  -X DELETE \
  -H "Authorization: Bearer $PROOF_SECRET_KEY"
```

</TabItem>
<TabItem label="Node">

```ts
await proof.documents.delete("doc_8Hq2Lm4R");
```

</TabItem>
</Tabs>

```json
{
  "id": "doc_8Hq2Lm4R",
  "deleted": true
}
```

## Export a document

`GET /v1/documents/{document_id}/export`

The document's text as it stands, as Markdown (`text/markdown; charset=utf-8`). Pending suggestions and comments are left out. The response body is the file itself, not JSON. HTML and Word come from the editor's [`export` method](/reference/embed/#methods) for now.

| Parameter | Type | Description |
| --- | --- | --- |
| `document_id` | string, path, required | The document's id. |
| `format` | string, required | `markdown`. Anything else is refused with `400 invalid_parameter`. |

<Tabs syncKey="lang">
<TabItem label="curl">

```bash
curl "https://api.proof.exalto.ai/v1/documents/doc_8Hq2Lm4R/export?format=markdown" \
  -H "Authorization: Bearer $PROOF_SECRET_KEY"
```

</TabItem>
<TabItem label="Node">

```ts
const markdown = await proof.documents.export("doc_8Hq2Lm4R", { format: "markdown" });
```

</TabItem>
</Tabs>

```markdown
# On the Analytical Engine

The Analytical Engine weaves algebraical patterns just as the Jacquard loom weaves flowers and leaves.
```

## Read a document's provenance

`GET /v1/documents/{document_id}/provenance`

Where a document's words came from and how it was written, read live under the current method. These are the numbers its seal would show if it were sealed now; a [seal](/reference/api/seals/) keeps the numbers it was sealed with. See [Provenance](/concepts/provenance/) for what each part means.

| Parameter | Type | Description |
| --- | --- | --- |
| `document_id` | string, path, required | The document's id. |

<Tabs syncKey="lang">
<TabItem label="curl">

```bash
curl https://api.proof.exalto.ai/v1/documents/doc_8Hq2Lm4R/provenance \
  -H "Authorization: Bearer $PROOF_SECRET_KEY"
```

</TabItem>
<TabItem label="Node">

```ts
const reading = await proof.documents.provenance("doc_8Hq2Lm4R");
```

</TabItem>
</Tabs>

```json
{
  "document": "doc_8Hq2Lm4R",
  "method": "v0.2",
  "composition": { "typed": 71.4, "shaped": 15.9, "ai": 7.8, "external": 4.9 },
  "presence": { "share": 97, "verified_share": 0, "verified": false },
  "prompts": 4,
  "sittings": 2,
  "duration": "1hr48min"
}
```

| Attribute | Type | Description |
| --- | --- | --- |
| `method` | string | The version of the method that produced these numbers. |
| `composition` | object | Percentages of the document by where its words came from, summing to 100: `typed` by people, `shaped` (agent text people revised), `ai` (agent text kept as written) and `external` (pasted or imported). Unrounded; a [seal](/reference/api/seals/) rounds them to whole numbers. |
| `presence` | object | `share`: percent of session time with a person present, or `null` before anyone has worked on it. `verified_share`: the part of it vouched for by check-ins. `verified`: `true` when every sitting people wrote in was verified. |
| `prompts` | integer | Rounds of back-and-forth with agents. |
| `sittings` | integer | Separate stretches of work on the document. |
| `duration` | string | Session time across those sittings, as the seal line writes it, such as `1hr48min`; empty when nothing was measured. |

Who wrote what, person by person and agent by agent, is in the editor's breakdown (the [`openBreakdown` method](/reference/embed/#methods)) and on each seal's page.