# Add Readings

Source: https://help.zira.us/developers/api-reference/add-readings
Summary: Insert meter readings where values are sent as a metric map keyed by metric ID.
Updated: 2026-08-06

## `POST /reading`

Use this endpoint to post readings in the primary SDK ingestion format, with values keyed by metric ID inside each reading.

## Authentication

```http
x-api-key: <your-api-key>
Content-Type: application/json
```

See [API Authentication](/developers/api-reference/api-authentication) for how to create a key.

## Request body

Send an array of meters, each with one or more timestamped readings keyed by metric ID.

```json
[
  {
    "meterId": "871",
    "readings": [
      {
        "timestamp": "2026-01-01T10:00:00Z",
        "values": {
          "501": 12.4,
          "502": 55
        }
      }
    ]
  }
]
```

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `meterId` | string | yes | Data source that will receive the readings. Must be a JSON string — a number is rejected. |
| `readings` | object[] | yes | One entry per timestamped reading for that data source. |
| `readings[].timestamp` | string | yes | When the reading was taken. Send `null` to use the server’s current time — but the key must be present either way. |
| `readings[].values` | object | yes | Metric IDs mapped to their values. Get the IDs from `GET /data-sources/{id}?withSchema=true`. |

## Response

`200 OK`

Returns the fixed string `"SUCCESS"`. There is no per-reading result and no generated ID, so treat the `200` itself as the signal — there is nothing in the body worth parsing.

```json
{
  "data": "SUCCESS"
}
```

| Field | Type | Description |
| --- | --- | --- |
| `data` | string | Always the literal `"SUCCESS"` on a 200. It never carries counts, IDs or per-row detail. |

### Good to know

- `SUCCESS` means the batch was accepted for ingestion, not that it is stored yet. Allow for a short lag before reading the same values back with `GET /reading`.
- The whole batch is accepted or rejected together. If the API key cannot write to any one data source in the payload, the request fails with `401` and nothing is written.
- Send `"timestamp": null` to have Zira stamp the current time. The key still has to be present — omitting it is a `400`.
- Reading objects take only `timestamp` and `values`; any other key inside a reading is rejected with `400`. Keys you add on the enclosing data-source object are ignored instead.
- Leave out metrics that Zira calculates. Any writable metric you omit from `values` is stored as `null` rather than left at its previous value.
- Do not add a query string to this request — it interferes with body validation and produces a misleading `400`.

## Errors

Failures return a JSON body with a `message` and an `internalErrorCode`, at the status shown below. Whenever the underlying error carries structured detail — which every schema-validation failure does — the body is replaced with a generic `Internal server error.` / `02-001` pair while the real status code is kept. Branch on the HTTP status and on `internalErrorCode`; never parse the message text.

```json
{
  "message": "Internal server error.",
  "internalErrorCode": "02-001"
}
```

| Status | Meaning |
| --- | --- |
| `400` | The request failed validation — a missing required field, a field the schema does not allow, an unparseable body, a value out of range, or a malformed `lastValue`. |
| `401` | The API key resolved to a user context that is not allowed to read or write the record you asked for. |
| `403` | The `x-api-key` header is missing or is not a recognised key. API Gateway rejects the call before the endpoint runs, so the body is only `{"message":"Forbidden"}`. |
| `409` | The write conflicts with an existing record, or it references a record that does not exist. |
| `429` | The API key exceeded the rate limit or quota on its usage plan. Also returned by API Gateway rather than the endpoint. |
| `500` | Unexpected server error, or the database returned no result where one was required. |
