# Get Import URL

Source: https://help.zira.us/developers/api-reference/get-import-url
Summary: Get a one-hour upload URL for bulk-importing readings from a CSV file.
Updated: 2026-08-06

## `GET /import`

Use this endpoint to bulk-load readings for one data source from a CSV file. It returns a pre-signed URL that you upload the file to; Zira imports it in the background once the file lands.

## 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 | Data source the readings will be imported into. |
| `timestampFormat` | string | no | Format of the timestamp column in your CSV, e.g. `MM/DD/YYYY HH:mm:ss`. Leave it off to let Zira detect the format. |

## Response

`200 OK`

Returns a single pre-signed URL to upload your CSV file to. Upload the raw file with an HTTP `PUT`, and send no authentication headers with it — the URL is already signed.

```json
{
  "data": {
    "fileUrl": "https://prod-imports-lightapp.s3.us-west-2.amazonaws.com/120/1/774/17794_.csv?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=%3Ccredential%3E&X-Amz-Date=20260114T091500Z&X-Amz-Expires=3600&X-Amz-Security-Token=%3Ctoken%3E&X-Amz-Signature=%3Csignature%3E&X-Amz-SignedHeaders=host&x-id=PutObject"
  }
}
```

| Field | Type | Description |
| --- | --- | --- |
| `data` | object | Wrapper holding the upload URL. Never an array, never paginated. |
| `data.fileUrl` | string | The URL to upload your CSV to. Treat it as opaque, single-use and time-limited — do not parse it or try to re-sign it. |

### Good to know

- Upload with `PUT` and the raw CSV as the request body — the URL is signed for `PUT`, so a multipart or `POST` upload will not work. Do not send an `Authorization` header with the upload; the signature in the URL is the authorisation.
- The URL is valid for one hour. Request a fresh one after that.
- Build your CSV header row from `GET /reading/template` for the same data source. Columns are matched by name rather than position, and only `timestamp` is mandatory.
- Every call to this endpoint starts a new import run and the URL points at one fixed file path for one data source, so request a URL only when you are about to use it.
- Importing is asynchronous. A successful upload means the file arrived, not that the readings are stored — an empty file is discarded without notice.
- Treat an empty `fileUrl` as a failure and retry, rather than uploading to it.
- An unknown or inaccessible `meterId` comes back as `401`, not `404`.

## Importing readings from a CSV (the full flow)

1. [Download Template](/developers/api-reference/get-reading-template) — learn the exact columns for
   this data source.
2. Fill in one row per reading, keeping the column order.
3. Call this endpoint to get an upload URL.
4. `PUT` the file to `data.fileUrl`, with the raw CSV as the body and no authentication headers.

Importing then happens in the background. A successful upload means the file arrived — not that the
readings are stored yet.

Prefer [Add Readings](/developers/api-reference/add-readings) when you want to push readings as JSON
instead of uploading a 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. |
