# CRM · Documents

Part of AppEngine API Documentation. Full index: https://appengine.appmint.io/documentation.md
## GET /crm/signed-documents

**List signed documents**

`operationId: SignedDocumentController_list`

The documents sent for signature, with their current state.

#### Signature

```http
GET /crm/signed-documents () -> Signed documents
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /crm/signed-documents/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Signed documents |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/signed-documents

**Create a signed document**

`operationId: SignedDocumentController_create`

Creates a document for signature. It starts unsent — sending it is a separate step, so it can be reviewed first.

#### Signature

```http
POST /crm/signed-documents (body) -> The created document
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/signed-documents/{id}/send`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The document to create.

```json
{
  "title": "Storage agreement",
  "signers": [
    {
      "email": "ada@example.com",
      "name": "Ada Lovelace"
    }
  ]
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The created document |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/signed-documents/context/{datatype}/{id}

**Get documents for a record**

`operationId: SignedDocumentController_listForContext`

The signature documents attached to another record — the contracts on a booking, the waivers on a rental. Use this rather than storing document ids on the record yourself.

#### Signature

```http
GET /crm/signed-documents/context/{datatype}/{id} (datatype: string, id: string) -> Documents attached to that record
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/signed-documents`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `datatype` | path | string | yes | The record's datatype. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Documents attached to that record |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/signed-documents/{id}

**Get a signed document**

`operationId: SignedDocumentController_get`

Fetches one document with its signature status and audit trail.

#### Signature

```http
GET /crm/signed-documents/{id} (id: string) -> The document
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | NOT_FOUND | Document not found | No document has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/signed-documents/{id}/send`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The document |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Document not found — No document has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## DELETE /crm/signed-documents/{id}

**Delete a signed document**

`operationId: SignedDocumentController_remove`

Deletes a document and its audit trail. For anything already signed, **void it instead** — the signature record is legal evidence and deleting it destroys that.

#### Signature

```http
DELETE /crm/signed-documents/{id} (id: string) -> Deletion result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Destroys the signature audit trail. Use `void` for anything signed.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | NOT_FOUND | Document not found | No document has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/signed-documents/{id}/void`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Deletion result |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Document not found — No document has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/signed-documents/{id}/update

**Update a signed document**

`operationId: SignedDocumentController_update`

Updates a document before it is signed. Editing one that has already been signed would invalidate the signature — change it before sending, or void it and start again.

#### Signature

```http
POST /crm/signed-documents/{id}/update (id: string, body) -> The updated document
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | NOT_FOUND | Document not found | No document has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/signed-documents/{id}/void`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Request body

Fields to change.

```json
{
  "title": "Storage agreement (2026)"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The updated document |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Document not found — No document has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/signed-documents/{id}/send

**Send a document for signature**

`operationId: SignedDocumentController_send`

Sends the document to its signers, each receiving a portal link. Once sent, the content should not change.

#### Signature

```http
POST /crm/signed-documents/{id}/send (id: string) -> Dispatch result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | NOT_FOUND | Document not found | No document has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/signed-documents/{id}/remind`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | Dispatch result |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Document not found — No document has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/signed-documents/{id}/remind

**Remind signers**

`operationId: SignedDocumentController_remind`

Re-sends the signature request to anyone who has not yet signed. Sends every time it is called — there is no cooldown.

#### Signature

```http
POST /crm/signed-documents/{id}/remind (id: string) -> Dispatch result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | NOT_FOUND | Document not found | No document has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/signed-documents/{id}/send`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | Dispatch result |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Document not found — No document has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/signed-documents/{id}/cancel

**Cancel a signature request**

`operationId: SignedDocumentController_cancel`

Withdraws a document from signature before it is complete. Portal links stop working; the record is kept.

#### Signature

```http
POST /crm/signed-documents/{id}/cancel (id: string) -> The cancelled document
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | NOT_FOUND | Document not found | No document has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/signed-documents/{id}/void`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The cancelled document |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Document not found — No document has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/signed-documents/{id}/void

**Void a signed document**

`operationId: SignedDocumentController_voidIt`

Voids a document that has already been signed, marking it no longer in force while **keeping the signature record intact**.

This is the correct way to retire a signed agreement: cancel applies before signature, delete destroys the evidence, void preserves it.

#### Signature

```http
POST /crm/signed-documents/{id}/void (id: string) -> The voided document
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `404` | NOT_FOUND | Document not found | No document has that identifier. | Check the identifier against the corresponding list endpoint. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /crm/signed-documents/{id}/cancel`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Record id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The voided document |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `404` | Document not found — No document has that identifier. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /crm/signed-documents/portal/{token}

**Get a document from the signing portal**

`operationId: SignedDocumentController_getByToken`

The **public** read a signer uses to view their document, authenticated by the token in their email rather than by an account.

The token is the only credential — anyone holding the link can read the document.

#### Signature

```http
GET /crm/signed-documents/portal/{token} (token: string) -> The document to sign
```

#### Access

Public — no credentials required.

#### Notes

- Unauthenticated. Treat signing links as secrets.

#### Errors

Plus the standard platform errors: `429`, `500`.

#### See also

- `POST /crm/signed-documents/portal/{token}/sign`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes |  |
| `token` | path | string | yes | Signing token from the invitation email. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The document to sign |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/signed-documents/portal/{token}/sign

**Sign a document**

`operationId: SignedDocumentController_sign`

Records a signature from the signing portal. **Public**, authenticated only by the token, and irreversible — a signed document can be voided but not unsigned.

#### Signature

```http
POST /crm/signed-documents/portal/{token}/sign (token: string, body) -> The signed document
```

#### Access

Public — no credentials required.

#### Notes

- Irreversible. The signature and its metadata become the audit record.

#### Errors

Plus the standard platform errors: `429`, `500`.

#### See also

- `POST /crm/signed-documents/portal/{token}/decline`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes |  |
| `token` | path | string | yes | Signing token. |

### Request body

The signature.

```json
{
  "name": "Ada Lovelace",
  "signature": "data:image/png;base64,…"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The signed document |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /crm/signed-documents/portal/{token}/decline

**Decline to sign**

`operationId: SignedDocumentController_decline`

Records that a signer declined, with their reason. The document stops awaiting them and the sender is informed.

#### Signature

```http
POST /crm/signed-documents/portal/{token}/decline (token: string, body) -> The declined document
```

#### Access

Public — no credentials required.

#### Errors

Plus the standard platform errors: `429`, `500`.

#### See also

- `POST /crm/signed-documents/portal/{token}/sign`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes |  |
| `token` | path | string | yes | Signing token. |

### Request body

Why they declined.

```json
{
  "reason": "Terms need review by our legal team"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The declined document |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

