---
title: "Attaches a photo to a job; it appears on the job's timeline as a `Photo` entry."
method: POST
path: "/api/v1/jobs/{id}/photos"
tags: ["Jobs"]
---

# Attaches a photo to a job; it appears on the job's timeline as a \`Photo\` entry.

`POST /api/v1/jobs/{id}/photos`

## Request

- Base URL: `https://app.flexonthejob.com`
- URL: `https://app.flexonthejob.com/api/v1/jobs/{id}/photos`
- Auth: HTTP bearer

Send `multipart/form-data` with the image in a part named `file` and an optional
`caption` part, e.g.
`curl -X POST https://app.flexonthejob.com/api/v1/jobs/42/photos -H "Authorization: Bearer flx_live_…" -H "Idempotency-Key: 5f0c…" -F "file=@roof.jpg" -F "caption=Rust on the bottom panel"`.
JPEG and PNG only (detected from the file's bytes), up to 10 MB. The entry is attributed to the
key's creator and is the same kind of entry the Flex field app creates from the phone camera:
it shows as a thumbnail on the job's page in the web app, syncs to every device, and its image
is included in the organization data export. Needs `ManageJobs` — the same permission as
adding a job update in the app — and an `Idempotency-Key` header (a retry with the same
key and the same file replays the first response instead of adding a second photo).

## Path parameters

- `id` integer, required

## Response `201`

The created timeline entry (`type` = `Photo`, `attachmentName` set). `Location` points at the job.

- JobNoteDto: One entry on a job's timeline (note, progress update, issue, status change…).
  - `id` integer: Entry id.
  - `syncId` string, uuid: Stable cross-device identifier of the entry.
  - `type` string: Kind of entry — JobUpdateType name such as `GeneralNote`, `ProgressUpdate`, `Issue`, `CustomerRequest`, `StatusChange`, `Photo` or `Signature`.
  - `description` string: The note text.
  - `isInternal` boolean: True when the note is staff-only and must not be shown to the customer.
  - `createdAt` string, date-time: When the entry was written, ISO-8601 UTC.
  - `createdByName` string, nullable: Display name of the author, when recorded.
  - `oldStatus` string, nullable: For status-change entries: the previous status name; otherwise null.
  - `newStatus` string, nullable: For status-change entries: the new status name; otherwise null.
  - `attachmentName` string, nullable: For `Photo` / `Signature` entries: the attached image's file name (e.g. `J-1042-20260929-143015.jpg`); null for entries without an attachment. The image itself is viewable on the job's page in the web app and is included in the organization data export.

## Other responses

- `400`: No `file` part, an empty file, a caption over 2000 characters, or a missing `Idempotency-Key`.
- `401`: Unauthorized
- `403`: Forbidden
- `404`: No such job in this organization.
- `409`: The key's creator is unknown on this server or deactivated, or this server does not store photos (the demo sandbox).
- `413`: The file is larger than 10 MB.
- `415`: The file is not a JPEG or PNG image.
- `429`: Too Many Requests

---

[API](https://skmtc.dev/flexonthejob/apis/flex-api.md) · [All operations](https://skmtc.dev/flexonthejob/apis/flex-api/llms.txt) · [OpenAPI document](https://skmtc.dev/flexonthejob/apis/flex-api/revisions/19c8f4d27acf?raw)
