# Download Template

Source: https://help.zira.us/developers/api-reference/get-reading-template
Summary: Return a template or schema helper for building valid reading payloads for a specific data source.
Updated: 2026-08-06

## `GET /reading/template`

Use this endpoint before posting readings when your integration needs schema guidance for a specific data source or form.

## Authentication

```http
x-api-key: <your-api-key>
```

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

## Query parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `meterId` | string | yes | Required ID of the data source or form you want the template for. |

## Response

`200 OK`

Returns a CSV document as a single string — not a JSON structure. The first line is the header row: `timestamp` followed by one column per metric you are allowed to write to, in the order the importer expects. When the data source already has at least one reading, a second line holds that reading as a worked example.

```json
{
  "data": "\"timestamp\",\"Accumulated Volume\",\"Flow Rate\"\n\"2026-01-14T09:15:00.000+02:00\",148.25,7.52"
}
```

| Field | Type | Description |
| --- | --- | --- |
| `data` | string | CSV text. Line 1 is the header row. Line 2 is the data source’s most recent reading in the same column order, and is present only when it has readings. Split on newlines and parse as CSV — do not try to JSON-parse this value. |

### Good to know

- This is the column contract for the CSV import flow, not a description of the `POST /reading` JSON body. The columns are metric display names; `POST /reading` is keyed by metric ID instead.
- A data source with no readings yet returns the header row only — one line, no trailing newline. Handle the missing second line.
- Columns cover only the metrics you can write. Anything Zira calculates for you is left out, because supplying it would be ignored.
- The importer matches columns by their header name, not by position, so you can reorder them. Only the `timestamp` column is mandatory. Keeping the headers exactly as returned is still the safest option.
- Leave out the `Submission Id`, `Submitted By` and `Submitted At` columns — the importer ignores them.
- The sample row’s timestamp is in the data source’s own time zone and carries an offset, unlike the UTC timestamps that `GET /reading` returns.
- Typical flow: call this to learn the columns, fill in one row per reading, then call `GET /import` for an upload URL and upload the finished file.

## 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. |
