# Photos

Photos of a unit's nameplates, labels and overviews. A nameplate or a label is read by AI into suggestions with a confidence for each field. Nothing on the unit changes until the suggestions are saved with the calls for units and components.

## The photo model

- `id` (string (uuid))
- `unit_id` (string (uuid))
- `component_id` (string (uuid), nullable)
- `kind` (string) One of `nameplate`, `label`, `overview`.
- `label_kind` (string, nullable)
- `reading_status` (string, nullable): `null` when the photo is not read. One of `queued`, `running`, `done`, `failed`.
- `taken_at` (string (date-time))

## The photo reading model

- `id` (string (uuid))
- `photo_id` (string (uuid))
- `status` (string) One of `queued`, `running`, `done`, `failed`.
- `readable` (boolean, nullable)
- `fields` (array) See the photo field model.
- `components` (array)
- `service_label` (object, nullable)
- `model` (string, nullable)
- `prompt_version` (string)
- `applied_at` (string (date-time), nullable)
- `created_at` (string (date-time))

## List photos

`GET /units/{unit}/photos` · scope `units:read`

Photos of a unit.

The unit's nameplates, labels and overviews, with the status of each reading. The photos themselves are seen in the app.

### Path parameters

- `unit` (string (uuid), required): The ID of the unit.

### Query parameters

- `limit` (integer): Records per page. At most 100, default 50.
- `cursor` (string): The value of `next_cursor` from the page before.

### Request

```bash
curl https://api.maci.energy/v1/units/0b6f4c1e-5a1d-4a57-9f0e-2f6f3f0c9a11/photos \
  -H "Authorization: Bearer $MACI_API_KEY"
```

```javascript
const response = await fetch('https://api.maci.energy/v1/units/0b6f4c1e-5a1d-4a57-9f0e-2f6f3f0c9a11/photos', {
  headers: {
    Authorization: `Bearer ${process.env.MACI_API_KEY}`,
  },
});
const data = await response.json();
```

```python
import os
import requests

response = requests.get(
    "https://api.maci.energy/v1/units/0b6f4c1e-5a1d-4a57-9f0e-2f6f3f0c9a11/photos",
    headers={"Authorization": f"Bearer {os.environ['MACI_API_KEY']}"},
)
data = response.json()
```

```php
$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.maci.energy/v1/units/0b6f4c1e-5a1d-4a57-9f0e-2f6f3f0c9a11/photos', [
    'headers' => [
        'Authorization' => 'Bearer '.getenv('MACI_API_KEY'),
    ],
]);
$data = json_decode($response->getBody(), true);
```

### Response 200

A page of photos. The fields are those of the photo model.

```json
{
  "data": [
    {
      "id": "4e8a1b2c-3d5f-4a6b-8c9d-0e1f2a3b4c5d",
      "unit_id": "0b6f4c1e-5a1d-4a57-9f0e-2f6f3f0c9a11",
      "component_id": null,
      "kind": "nameplate",
      "label_kind": null,
      "reading_status": "queued",
      "taken_at": "2026-10-30T09:15:00Z"
    }
  ],
  "next_cursor": null
}
```

## Create photo

`POST /units/{unit}/photos` · scope `units:write`

Send a photo.

A nameplate or a label is read in the background into suggestions. Nothing on the unit changes until the suggestions are saved with the calls for units and components. An overview is never read.

### Path parameters

- `unit` (string (uuid), required): The ID of the unit.

### Headers

- `Idempotency-Key` (string): A value of your own choosing, so that a repeated call does not create two records.

### Form fields (multipart/form-data)

- `kind` (string, required) One of `nameplate`, `label`, `overview`.
- `photo` (string (binary), required): JPEG, PNG or WebP of at most 10 MB.
- `label_kind` (string, nullable): For a label: which kind. One of `service`, `refrigerant`, `ce`, `leak_check`, `other`.
- `component_id` (string (uuid), nullable): A photo of one of the unit's components.

### Request

```bash
curl -X POST https://api.maci.energy/v1/units/0b6f4c1e-5a1d-4a57-9f0e-2f6f3f0c9a11/photos \
  -H "Authorization: Bearer $MACI_API_KEY" \
  -H "Idempotency-Key: a-value-of-your-own" \
  -F "kind=nameplate" \
  -F "photo=@nameplate.jpg"
```

```javascript
const form = new FormData();
form.append('kind', 'nameplate');
form.append('photo', fileInput.files[0]); // nameplate.jpg

const response = await fetch('https://api.maci.energy/v1/units/0b6f4c1e-5a1d-4a57-9f0e-2f6f3f0c9a11/photos', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.MACI_API_KEY}`,
    'Idempotency-Key': 'a-value-of-your-own',
  },
  body: form,
});
const data = await response.json();
```

```python
import os
import requests

response = requests.post(
    "https://api.maci.energy/v1/units/0b6f4c1e-5a1d-4a57-9f0e-2f6f3f0c9a11/photos",
    headers={"Authorization": f"Bearer {os.environ['MACI_API_KEY']}", "Idempotency-Key": "a-value-of-your-own"},
    data={"kind": "nameplate"},
    files={"photo": open("nameplate.jpg", "rb")},
)
data = response.json()
```

```php
$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.maci.energy/v1/units/0b6f4c1e-5a1d-4a57-9f0e-2f6f3f0c9a11/photos', [
    'headers' => [
        'Authorization' => 'Bearer '.getenv('MACI_API_KEY'),
        'Idempotency-Key' => 'a-value-of-your-own',
    ],
    'multipart' => [
        ['name' => 'kind', 'contents' => 'nameplate'],
        ['name' => 'photo', 'contents' => fopen('nameplate.jpg', 'r')],
    ],
]);
$data = json_decode($response->getBody(), true);
```

### Response 201

The photo that was saved. The fields are those of the photo model.

```json
{
  "id": "4e8a1b2c-3d5f-4a6b-8c9d-0e1f2a3b4c5d",
  "unit_id": "0b6f4c1e-5a1d-4a57-9f0e-2f6f3f0c9a11",
  "component_id": null,
  "kind": "nameplate",
  "label_kind": null,
  "reading_status": "queued",
  "taken_at": "2026-10-30T09:15:00Z"
}
```

## Get photo reading

`GET /photos/{photo}/reading` · scope `units:read`

What was read on a photo.

The newest reading, with each field's value in SI units, the text it was read from, the confidence (high, medium or low) and where it stands in the photo.

### Path parameters

- `photo` (string (uuid), required): The ID of the photo.

### Request

```bash
curl https://api.maci.energy/v1/photos/4e8a1b2c-3d5f-4a6b-8c9d-0e1f2a3b4c5d/reading \
  -H "Authorization: Bearer $MACI_API_KEY"
```

```javascript
const response = await fetch('https://api.maci.energy/v1/photos/4e8a1b2c-3d5f-4a6b-8c9d-0e1f2a3b4c5d/reading', {
  headers: {
    Authorization: `Bearer ${process.env.MACI_API_KEY}`,
  },
});
const data = await response.json();
```

```python
import os
import requests

response = requests.get(
    "https://api.maci.energy/v1/photos/4e8a1b2c-3d5f-4a6b-8c9d-0e1f2a3b4c5d/reading",
    headers={"Authorization": f"Bearer {os.environ['MACI_API_KEY']}"},
)
data = response.json()
```

```php
$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.maci.energy/v1/photos/4e8a1b2c-3d5f-4a6b-8c9d-0e1f2a3b4c5d/reading', [
    'headers' => [
        'Authorization' => 'Bearer '.getenv('MACI_API_KEY'),
    ],
]);
$data = json_decode($response->getBody(), true);
```

### Response 200

The reading. The fields are those of the photo reading model.

```json
{
  "id": "e7981672-d483-458e-a358-811e2de301c8",
  "photo_id": "4e8a1b2c-3d5f-4a6b-8c9d-0e1f2a3b4c5d",
  "status": "queued",
  "readable": false,
  "fields": [
    {
      "key": "refrigerant",
      "value": "Value",
      "raw_text": "R449A 42 kg",
      "confidence": "high",
      "bbox": null
    }
  ],
  "components": [
    {
      "type": "rum-for-varmt",
      "fields": [
        {
          "key": "refrigerant",
          "value": "Value",
          "raw_text": "R449A 42 kg",
          "confidence": "high",
          "bbox": null
        }
      ]
    }
  ],
  "service_label": null,
  "model": "K-100",
  "prompt_version": "v3",
  "applied_at": null,
  "created_at": "2026-10-30T09:15:00Z"
}
```
