# Create a Photo with a file

`POST /v1/photos`

Creates the Photo immediately, then imports its file in the background. Accepted formats are JPEG, PNG, PDF, and DICOM (`.dcm`). Convert TIFF or BMP files before uploading. Provide the file as a public HTTPS `file_url` or Base64-encoded `file_base64`. Poll `/v1/remote_file_import_batches/:remote_file_import_batch_id` for the import status.

**Required API scopes:** `photos.create`

## Parameters

- `Idempotency-Key` (header, `string`) (required) - Client-generated idempotency key. Required for every POST/PATCH write. Replay of the same key with the same body returns the stored response with an `Idempotency-Replayed: true` header. Same key + different body returns `422 idempotency_key_reused`. A duplicate that arrives while the first request is still in flight returns `409 idempotency_conflict` with `Retry-After: 1`.

## Request body (`application/json`)

- `object`
  - `assigned_staff_member_id` (`string | null`) - format: `uuid`; The identifier of the StaffMember assigned to the photo. Send null to clear the assignment. Omit the field to leave the current assignment unchanged.
  - `automatically_create_resource_permission_for_patient` (`boolean`) - Whether Carebit should automatically share the photo with the patient after processing.
  - `booking_id` (`string | null`) - format: `uuid`; The identifier of the booking associated with the photo, or null when it is not linked to a booking.
  - `clinician_id` (`string | null`) - format: `uuid`; The identifier of the clinician associated with the photo, or null when none is assigned.
  - `created_at` (`string`) - format: `date-time`; When the photo was created. This is the Date created field in Carebit. Omit it to use the current time. On update, omit it to keep the stored time.
  - `description` (`string | null`) - A description of the photo.
  - `file_base64` (`string`) - format: `byte`; The photo bytes encoded as Base64. Provide this with `filename` instead of `file_url`. The decoded file can be at most 7 MB.
  - `file_url` (`string`) - format: `uri`; The public HTTPS URL that Carebit can fetch.
  - `filename` (`string | null`) - The filename to use for the photo. Required with `file_base64`; defaults to the remote file's filename for URL sources.
  - `notify_patient_of_resource_permission` (`boolean`) - Whether Carebit should notify the patient when the photo is shared with them.
  - `patient_id` (`string`) - format: `uuid`; The identifier of the patient that the photo belongs to.
  - `sent_at` (`string | null`) - format: `date-time`; When the photo was sent. Send null to clear the sent time. Omit the field to leave the current value unchanged. A timestamp stores that time and marks the photo complete.
  - `status` (`string`) - enum: `awaiting_receipt`, `awaiting_review`, `awaiting_sending`, `complete`, `draft`, `reviewed`; The workflow status to assign to the photo.
  - `title` (`string`) - The display title of the photo.

### Example

```json
{
  "automatically_create_resource_permission_for_patient": false,
  "file_url": "https://storage.example.invalid/photos/photo.jpg",
  "filename": "photo.jpg",
  "notify_patient_of_resource_permission": false,
  "patient_id": "00000000-0000-4000-8000-000000000004",
  "status": "awaiting_review",
  "title": "Clinical photograph"
}
```

## Response `201`

The requested `Photo`.

- `object`
  - `assigned_staff_member` (`any`) - The StaffMember assigned to the photo, or null when nobody is assigned.
  - `automatically_create_resource_permission_for_patient` (`boolean`) - Whether Carebit automatically shares the photo with the patient after processing.
  - `created_at` (`string`) - format: `date-time`; An ISO 8601 timestamp in UTC, with a `Z` suffix. For example, `2026-07-01T09:00:00Z`.
  - `download_url` (`string | null`) - format: `uri`; The short-lived signed download URL for the Photo. Null until the file passes malware scanning and the Photo is complete or reviewed.
  - `filename` (`string | null`) - The original filename of the photo.
  - `id` (`string`) - format: `uuid`; The resource's unique identifier, formatted as an RFC 4122 version 4 UUID.
  - `links` (`object`) - URLs to related resources.
    - `assigned_staff_member` (`string | null`) - format: `uri`; The full URL of a related resource.
    - `booking` (`string | null`) - format: `uri`; The full URL of a related resource.
    - `clinician` (`string | null`) - format: `uri`; The full URL of a related resource.
    - `patient` (`string | null`) - format: `uri`; The full URL of a related resource.
    - `remote_file_import_batch` (`string`) - format: `uri`; The full URL of a related resource.
  - `notify_patient_of_resource_permission` (`boolean | null`) - Whether Carebit notifies the patient when the photo is shared with them.
  - `object` (`any`) - Discriminator value emitted at `object`.
  - `remote_file_import_batch_id` (`string`) - format: `uuid`; The identifier of the remote file import batch created for the uploaded file. Set on the create or update response when a file was submitted.
  - `sent_at` (`string | null`) - format: `date-time`; When the photo was sent. Null when it has not been sent.
  - `status` (`string | null`) - enum: `awaiting_proofreading`, `awaiting_receipt`, `awaiting_review`, `awaiting_sending`, `awaiting_typing`, `complete`, `draft`, `reviewed`, `null`; The Photo's workflow status. An owned Photo is readable while it is awaiting review.
  - `title` (`string | null`) - The display title of the photo.
  - `updated_at` (`string`) - format: `date-time`; An ISO 8601 timestamp in UTC, with a `Z` suffix. For example, `2026-07-01T09:00:00Z`.

### Example

```json
{
  "id": "24097db0-9916-44a3-8959-93b7a787c6da",
  "object": "photo",
  "assigned_staff_member": {
    "id": "af02fa53-0af3-48ab-83b5-82488923e84f",
    "object": "staff_member",
    "created_at": "2026-01-01T09:00:00Z",
    "email": "alex.morgan@example.com",
    "first_name": "Alex",
    "last_name": "Morgan",
    "links": {
      "self": "https://api.carebit.co/v1/staff_members/af02fa53-0af3-48ab-83b5-82488923e84f"
    },
    "name": "Initial consultation",
    "title": "Dr",
    "updated_at": "2026-01-01T09:00:00Z"
  },
  "automatically_create_resource_permission_for_patient": true,
  "created_at": "2026-01-01T09:00:00Z",
  "download_url": "https://files.example.invalid/document.pdf?signature=test",
  "filename": "referral-letter.pdf",
  "links": {
    "assigned_staff_member": "https://api.carebit.co/v1/assigned_staff_members/e6ff1712-cb57-449f-8960-eb185db9a9a8",
    "booking": "https://api.carebit.co/v1/bookings/00000000-0000-4000-8000-000000000001",
    "clinician": "https://api.carebit.co/v1/clinicians/00000000-0000-4000-8000-000000000002",
    "patient": "https://api.carebit.co/v1/patients/00000000-0000-4000-8000-000000000004",
    "remote_file_import_batch": "https://api.carebit.co/v1/remote_file_import_batches/00000000-0000-4000-8000-00000000000a"
  },
  "notify_patient_of_resource_permission": true,
  "remote_file_import_batch_id": "00000000-0000-4000-8000-00000000000a",
  "sent_at": "2026-01-01T09:00:00Z",
  "status": "awaiting_proofreading",
  "title": "Dr",
  "updated_at": "2026-01-01T09:00:00Z"
}
```

## Response `400`

The `Idempotency-Key` header is missing (`idempotency_key_required`) or exceeds 255 characters (`idempotency_key_too_long`).

- `object`
  - `error` (`object`) - The structured details that describe why the request failed.
    - `code` (`string`) - The machine-readable error code.
    - `errors` (`array | null`) - Additional errors from a failed validation.
      - `items` (`object`)
        - `code` (`string`) - The machine-readable code for this validation error.
        - `message` (`string`) - A message that explains this validation error.
        - `param` (`string | null`) - The name of the parameter that caused this validation error, when known.
    - `message` (`string`) - A message that explains the error and how to resolve it.
    - `param` (`string | null`) - The name of the parameter that caused the error, when known.
    - `type` (`string`) - enum: `authentication_error`, `permission_error`, `invalid_request_error`, `rate_limit_error`, `api_error`; The high-level category of the error.

### Example

```json
{
  "error": {
    "code": "resource_missing",
    "errors": [
      {
        "code": "resource_missing",
        "message": "The requested resource was not found.",
        "param": "patient_id"
      }
    ],
    "message": "The requested resource was not found.",
    "param": "patient_id",
    "type": "authentication_error"
  }
}
```

## Response `401`

The access token is missing, invalid, expired, or revoked.

- `object`
  - `error` (`object`) - The structured details that describe why the request failed.
    - `code` (`string`) - The machine-readable error code.
    - `errors` (`array | null`) - Additional errors from a failed validation.
      - `items` (`object`)
        - `code` (`string`) - The machine-readable code for this validation error.
        - `message` (`string`) - A message that explains this validation error.
        - `param` (`string | null`) - The name of the parameter that caused this validation error, when known.
    - `message` (`string`) - A message that explains the error and how to resolve it.
    - `param` (`string | null`) - The name of the parameter that caused the error, when known.
    - `type` (`string`) - enum: `authentication_error`, `permission_error`, `invalid_request_error`, `rate_limit_error`, `api_error`; The high-level category of the error.

### Example

```json
{
  "error": {
    "code": "resource_missing",
    "errors": [
      {
        "code": "resource_missing",
        "message": "The requested resource was not found.",
        "param": "patient_id"
      }
    ],
    "message": "The requested resource was not found.",
    "param": "patient_id",
    "type": "authentication_error"
  }
}
```

## Response `403`

The access token lacks the required scope, or the project is disabled.

- `object`
  - `error` (`object`) - The structured details that describe why the request failed.
    - `code` (`string`) - The machine-readable error code.
    - `errors` (`array | null`) - Additional errors from a failed validation.
      - `items` (`object`)
        - `code` (`string`) - The machine-readable code for this validation error.
        - `message` (`string`) - A message that explains this validation error.
        - `param` (`string | null`) - The name of the parameter that caused this validation error, when known.
    - `message` (`string`) - A message that explains the error and how to resolve it.
    - `param` (`string | null`) - The name of the parameter that caused the error, when known.
    - `type` (`string`) - enum: `authentication_error`, `permission_error`, `invalid_request_error`, `rate_limit_error`, `api_error`; The high-level category of the error.

### Example

```json
{
  "error": {
    "code": "resource_missing",
    "errors": [
      {
        "code": "resource_missing",
        "message": "The requested resource was not found.",
        "param": "patient_id"
      }
    ],
    "message": "The requested resource was not found.",
    "param": "patient_id",
    "type": "authentication_error"
  }
}
```

## Response `404`

Error response.

- `object`
  - `error` (`object`) - The structured details that describe why the request failed.
    - `code` (`string`) - The machine-readable error code.
    - `errors` (`array | null`) - Additional errors from a failed validation.
      - `items` (`object`)
        - `code` (`string`) - The machine-readable code for this validation error.
        - `message` (`string`) - A message that explains this validation error.
        - `param` (`string | null`) - The name of the parameter that caused this validation error, when known.
    - `message` (`string`) - A message that explains the error and how to resolve it.
    - `param` (`string | null`) - The name of the parameter that caused the error, when known.
    - `type` (`string`) - enum: `authentication_error`, `permission_error`, `invalid_request_error`, `rate_limit_error`, `api_error`; The high-level category of the error.

### Example

```json
{
  "error": {
    "code": "resource_missing",
    "errors": [
      {
        "code": "resource_missing",
        "message": "The requested resource was not found.",
        "param": "patient_id"
      }
    ],
    "message": "The requested resource was not found.",
    "param": "patient_id",
    "type": "authentication_error"
  }
}
```

## Response `409`

A concurrent request holds the idempotency lease (`idempotency_conflict`). Retry after the delay indicated by `Retry-After`.

- `object`
  - `error` (`object`) - The structured details that describe why the request failed.
    - `code` (`string`) - The machine-readable error code.
    - `errors` (`array | null`) - Additional errors from a failed validation.
      - `items` (`object`)
        - `code` (`string`) - The machine-readable code for this validation error.
        - `message` (`string`) - A message that explains this validation error.
        - `param` (`string | null`) - The name of the parameter that caused this validation error, when known.
    - `message` (`string`) - A message that explains the error and how to resolve it.
    - `param` (`string | null`) - The name of the parameter that caused the error, when known.
    - `type` (`string`) - enum: `authentication_error`, `permission_error`, `invalid_request_error`, `rate_limit_error`, `api_error`; The high-level category of the error.

### Example

```json
{
  "error": {
    "code": "resource_missing",
    "errors": [
      {
        "code": "resource_missing",
        "message": "The requested resource was not found.",
        "param": "patient_id"
      }
    ],
    "message": "The requested resource was not found.",
    "param": "patient_id",
    "type": "authentication_error"
  }
}
```

## Response `422`

The `Idempotency-Key` was previously used with a different request body (`idempotency_key_reused`), or the request body failed validation.

- `object`
  - `error` (`object`) - The structured details that describe why the request failed.
    - `code` (`string`) - The machine-readable error code.
    - `errors` (`array | null`) - Additional errors from a failed validation.
      - `items` (`object`)
        - `code` (`string`) - The machine-readable code for this validation error.
        - `message` (`string`) - A message that explains this validation error.
        - `param` (`string | null`) - The name of the parameter that caused this validation error, when known.
    - `message` (`string`) - A message that explains the error and how to resolve it.
    - `param` (`string | null`) - The name of the parameter that caused the error, when known.
    - `type` (`string`) - enum: `authentication_error`, `permission_error`, `invalid_request_error`, `rate_limit_error`, `api_error`; The high-level category of the error.

### Example

```json
{
  "error": {
    "code": "resource_missing",
    "errors": [
      {
        "code": "resource_missing",
        "message": "The requested resource was not found.",
        "param": "patient_id"
      }
    ],
    "message": "The requested resource was not found.",
    "param": "patient_id",
    "type": "authentication_error"
  }
}
```

## Response `429`

Too many requests. Retry after the delay indicated by `Retry-After`.

- `object`
  - `error` (`object`) - The structured details that describe why the request failed.
    - `code` (`string`) - The machine-readable error code.
    - `errors` (`array | null`) - Additional errors from a failed validation.
      - `items` (`object`)
        - `code` (`string`) - The machine-readable code for this validation error.
        - `message` (`string`) - A message that explains this validation error.
        - `param` (`string | null`) - The name of the parameter that caused this validation error, when known.
    - `message` (`string`) - A message that explains the error and how to resolve it.
    - `param` (`string | null`) - The name of the parameter that caused the error, when known.
    - `type` (`string`) - enum: `authentication_error`, `permission_error`, `invalid_request_error`, `rate_limit_error`, `api_error`; The high-level category of the error.

### Example

```json
{
  "error": {
    "code": "resource_missing",
    "errors": [
      {
        "code": "resource_missing",
        "message": "The requested resource was not found.",
        "param": "patient_id"
      }
    ],
    "message": "The requested resource was not found.",
    "param": "patient_id",
    "type": "authentication_error"
  }
}
```

## Code samples

```bash
curl -X POST "https://api.carebit.co/v1/photos" \
  -H "Authorization: Bearer $CAREBIT_ACCESS_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
  "automatically_create_resource_permission_for_patient": false,
  "file_url": "https://storage.example.invalid/photos/photo.jpg",
  "filename": "photo.jpg",
  "notify_patient_of_resource_permission": false,
  "patient_id": "00000000-0000-4000-8000-000000000004",
  "status": "awaiting_review",
  "title": "Clinical photograph"
}'
```

```javascript
const response = await fetch("https://api.carebit.co/v1/photos", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CAREBIT_ACCESS_TOKEN}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
  "automatically_create_resource_permission_for_patient": false,
  "file_url": "https://storage.example.invalid/photos/photo.jpg",
  "filename": "photo.jpg",
  "notify_patient_of_resource_permission": false,
  "patient_id": "00000000-0000-4000-8000-000000000004",
  "status": "awaiting_review",
  "title": "Clinical photograph"
}),
});

if (!response.ok) {
  throw new Error(`Carebit API error: ${response.status}`);
}

const data = await response.json();
```

```python
import os
import requests
import uuid

response = requests.post(
    "https://api.carebit.co/v1/photos",
    headers={
        "Authorization": f"Bearer {os.environ['CAREBIT_ACCESS_TOKEN']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "automatically_create_resource_permission_for_patient": False,
        "file_url": "https://storage.example.invalid/photos/photo.jpg",
        "filename": "photo.jpg",
        "notify_patient_of_resource_permission": False,
        "patient_id": "00000000-0000-4000-8000-000000000004",
        "status": "awaiting_review",
        "title": "Clinical photograph"
    }
)
response.raise_for_status()
data = response.json()
```

```ruby
require "httparty"
require "json"
require "securerandom"

response = HTTParty.post(
  "https://api.carebit.co/v1/photos",
  headers: {
    "Authorization" => "Bearer #{ENV.fetch("CAREBIT_ACCESS_TOKEN")}",
    "Idempotency-Key" => SecureRandom.uuid,
    "Content-Type" => "application/json"
  },
  body: {
    "automatically_create_resource_permission_for_patient" => false,
    "file_url" => "https://storage.example.invalid/photos/photo.jpg",
    "filename" => "photo.jpg",
    "notify_patient_of_resource_permission" => false,
    "patient_id" => "00000000-0000-4000-8000-000000000004",
    "status" => "awaiting_review",
    "title" => "Clinical photograph"
  }.to_json
)
raise "Carebit API error: #{response.code}" unless response.success?
data = response.parsed_response
```

```php
<?php

$client = new GuzzleHttp\Client();

$response = $client->post("https://api.carebit.co/v1/photos", [
    "headers" => [
      "Authorization" => "Bearer " . getenv("CAREBIT_ACCESS_TOKEN"),
      "Idempotency-Key" => bin2hex(random_bytes(16)),
    ],
    "json" => [
      "automatically_create_resource_permission_for_patient" => false,
      "file_url" => "https://storage.example.invalid/photos/photo.jpg",
      "filename" => "photo.jpg",
      "notify_patient_of_resource_permission" => false,
      "patient_id" => "00000000-0000-4000-8000-000000000004",
      "status" => "awaiting_review",
      "title" => "Clinical photograph"
    ]
]);
$data = json_decode((string) $response->getBody(), true, flags: JSON_THROW_ON_ERROR);
```
