# Photos and documents

> Time-limited links to the photos on a job or a customer, and to a customer's documents.

- Canonical URL: https://couplrfsm.com/developers/api-reference/photos-and-documents
- Markdown: https://couplrfsm.com/developers/api-reference/photos-and-documents.md
- API version: v1 (base URL https://couplrfsm.com/api/v1)
- OpenAPI specification: https://couplrfsm.com/developers/openapi.yaml
- All documentation pages: https://couplrfsm.com/llms.txt

> **Flip:** Technicians take photos on the job and the office files documents on a customer. Ask me and I hand you a link to each one. Each link stops working after a while, usually an hour, and tells you when, so fetch the file when you get the link and ask me again later for a new one.

Base URL: `https://couplrfsm.com/api/v1`. Conventions shared by every endpoint (examples, ids, dates, money) are on the [API reference](https://couplrfsm.com/developers/api-reference) page. Errors, pagination and idempotency are in [Errors, limits and retries](https://couplrfsm.com/developers/errors-and-limits).

## List a job's photos

`GET /api/v1/jobs/{id}/photos` · requires the `photos:read` ability

The photos filed on a job.
Each `url` works until `url_expires_at`, usually an hour after the
request. Download the file or ask again for a fresh link; do not
store the link.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | integer | Yes |  |

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | integer | No | Default: `1`. |
| `per_page` | integer | No | Values above 100 are served as 100. Default: `25`. |
| `updated_since` | string (date-time) | No | Only records changed at or after this time. ISO 8601, for example `2026-11-14T15:00:00Z`. |
| `stage` | string: `before`, `during`, `after` | No |  |

**Example request**

```bash
curl "https://couplrfsm.com/api/v1/jobs/1842/photos" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Example response** (`200`)

```json
{
    "data": [
        {
            "id": 4410,
            "caption": "Condenser before cleaning",
            "description": null,
            "stage": "before",
            "taken_at": "2026-11-14T15:02:11+00:00",
            "url": "https://files.example.com/photo-4410?signature=EXAMPLE",
            "thumbnail_url": "https://files.example.com/photo-4410-thumb?signature=EXAMPLE",
            "url_expires_at": "2026-11-14T17:10:00+00:00",
            "created_at": "2026-11-14T15:03:00+00:00",
            "updated_at": "2026-11-14T15:03:00+00:00"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "last_page": 1
    }
}
```

**Errors**

| Status | When |
| --- | --- |
| `401` | No key (`api_key_missing`), the key is unknown or revoked (`api_key_invalid`), the key had an end date that has passed (`api_key_expired`), or the key belongs to a private trial sandbox whose access window has ended (`sandbox_expired`). |
| `403` | The key lacks the ability this endpoint needs (`ability_missing`), no admin user is left to act for the key (`no_acting_admin`), or the company's account is not yet verified. |
| `404` | No such record in this company (`not_found`). |
| `422` | A parameter or field is invalid (`validation_failed`). `errors` maps each field to its messages. |
| `429` | Too many requests (`rate_limited`). Wait `Retry-After` seconds. |

## List a customer's photos

`GET /api/v1/customers/{id}/photos` · requires the `photos:read` ability

The photos filed on the customer. Photos filed on the customer's
jobs are listed under each job.
Each `url` works until `url_expires_at`, usually an hour after the
request. Download the file or ask again for a fresh link; do not
store the link.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | integer | Yes |  |

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | integer | No | Default: `1`. |
| `per_page` | integer | No | Values above 100 are served as 100. Default: `25`. |
| `updated_since` | string (date-time) | No | Only records changed at or after this time. ISO 8601, for example `2026-11-14T15:00:00Z`. |
| `stage` | string: `before`, `during`, `after` | No |  |

**Example request**

```bash
curl "https://couplrfsm.com/api/v1/customers/1842/photos" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Example response** (`200`)

```json
{
    "data": [
        {
            "id": 4410,
            "caption": "Condenser before cleaning",
            "description": null,
            "stage": "before",
            "taken_at": "2026-11-14T15:02:11+00:00",
            "url": "https://files.example.com/photo-4410?signature=EXAMPLE",
            "thumbnail_url": "https://files.example.com/photo-4410-thumb?signature=EXAMPLE",
            "url_expires_at": "2026-11-14T17:10:00+00:00",
            "created_at": "2026-11-14T15:03:00+00:00",
            "updated_at": "2026-11-14T15:03:00+00:00"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "last_page": 1
    }
}
```

**Errors**

| Status | When |
| --- | --- |
| `401` | No key (`api_key_missing`), the key is unknown or revoked (`api_key_invalid`), the key had an end date that has passed (`api_key_expired`), or the key belongs to a private trial sandbox whose access window has ended (`sandbox_expired`). |
| `403` | The key lacks the ability this endpoint needs (`ability_missing`), no admin user is left to act for the key (`no_acting_admin`), or the company's account is not yet verified. |
| `404` | No such record in this company (`not_found`). |
| `422` | A parameter or field is invalid (`validation_failed`). `errors` maps each field to its messages. |
| `429` | Too many requests (`rate_limited`). Wait `Retry-After` seconds. |

## List a customer's documents

`GET /api/v1/customers/{id}/documents` · requires the `photos:read` ability

The PDF documents filed on the customer.
Each `url` works until `url_expires_at`, usually an hour after the
request. Download the file or ask again for a fresh link; do not
store the link.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | integer | Yes |  |

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | integer | No | Default: `1`. |
| `per_page` | integer | No | Values above 100 are served as 100. Default: `25`. |
| `updated_since` | string (date-time) | No | Only records changed at or after this time. ISO 8601, for example `2026-11-14T15:00:00Z`. |

**Example request**

```bash
curl "https://couplrfsm.com/api/v1/customers/1842/documents" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Example response** (`200`)

```json
{
    "data": [
        {
            "id": 88,
            "customer_id": 1842,
            "file_name": "warranty-registration.pdf",
            "size_bytes": 482113,
            "url": "https://files.example.com/document-88?signature=EXAMPLE",
            "url_expires_at": "2026-11-14T17:10:00+00:00",
            "created_at": "2026-10-02T12:00:00+00:00",
            "updated_at": "2026-10-02T12:00:00+00:00"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 25,
        "total": 1,
        "last_page": 1
    }
}
```

**Errors**

| Status | When |
| --- | --- |
| `401` | No key (`api_key_missing`), the key is unknown or revoked (`api_key_invalid`), the key had an end date that has passed (`api_key_expired`), or the key belongs to a private trial sandbox whose access window has ended (`sandbox_expired`). |
| `403` | The key lacks the ability this endpoint needs (`ability_missing`), no admin user is left to act for the key (`no_acting_admin`), or the company's account is not yet verified. |
| `404` | No such record in this company (`not_found`). |
| `422` | A parameter or field is invalid (`validation_failed`). `errors` maps each field to its messages. |
| `429` | Too many requests (`rate_limited`). Wait `Retry-After` seconds. |
