# Get Data Source

Source: https://help.zira.us/developers/api-reference/get-data-source
Summary: Fetch a single data source or device by ID.
Updated: 2026-08-06

## `GET /data-sources/{id}`

Use this endpoint to load one data source and optionally include its schema and last reading.

## Authentication

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

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

## Path parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | ID of the data source to fetch. |

## Query parameters

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `withSchema` | boolean | no | Send the literal string `true` to include the data source’s metric schema. Any other value is treated as false. |
| `withLastReading` | boolean | no | Send the literal string `true` to include the most recent reading. Any other value is treated as false. |

## Response

`200 OK`

Returns one data source with its identity, location, time zone, collection settings and status. The example below is trimmed — a real payload also carries per-type blocks such as `formConfig` for forms and `cv` for cameras.

```json
{
  "data": {
    "id": "43510",
    "name": "Line 1 Label Counter",
    "description": "Camera 5",
    "externalId": null,
    "type": {
      "id": "50",
      "name": "Form"
    },
    "offset": -420,
    "timezoneId": "3",
    "tzTimezoneName": "US/Pacific",
    "site": {
      "id": "1512",
      "name": "Fresno Plant",
      "channelId": "45187"
    },
    "model": {
      "id": null,
      "name": null
    },
    "manufacturer": null,
    "inactiveThreshold": 1500,
    "validationStatus": {
      "id": "10",
      "name": "Pending Validation"
    },
    "activityStatus": {
      "id": "40",
      "name": "Pending Readings"
    },
    "collector": {
      "id": "29",
      "name": "Form"
    },
    "collectorType": {
      "id": "1",
      "name": "Push"
    },
    "applying": {
      "id": "7",
      "name": "AI Vision"
    },
    "isCustomSchema": true,
    "collectionEnabled": true,
    "readingInterval": null,
    "channelId": "67363",
    "companyId": "1216",
    "isShared": false,
    "isTemplate": false,
    "isDeleted": false,
    "isPrivate": false,
    "displayUoms": [
      {
        "metricId": "81",
        "uomId": "2501"
      },
      {
        "metricId": "86",
        "uomId": "2600"
      }
    ],
    "images": null,
    "files": null,
    "auditInfo": {
      "createdBy": {
        "id": "318",
        "name": "Alex Rivera"
      },
      "createdDate": "2026-01-14T08:36:16.120973Z",
      "lastModifiedBy": {
        "id": "318",
        "name": "Alex Rivera"
      },
      "lastModifiedDate": "2026-01-14T08:36:16.120973Z"
    }
  }
}
```

| Field | Type | Description |
| --- | --- | --- |
| `data.id` | string | Data source ID. |
| `data.name` | string | Display name. |
| `data.description` | string (nullable) | Free-text description. |
| `data.externalId` | string (nullable) | Your own identifier for this data source, if one was set. |
| `data.type` | object | `{ id, name }` — what kind of data source it is, e.g. `Form`, `Power Meter`. |
| `data.applying` | object | `{ id, name }` — how it is used: `1` Sensor, `2` form, `7` AI Vision. |
| `data.site` | object | `{ id, name, channelId }` for the owning site. |
| `data.tzTimezoneName` | string | IANA time zone, e.g. `US/Pacific`. `offset` is the same thing in minutes. |
| `data.validationStatus` | object | `{ id, name }` — where the data source is in validation. In the list endpoint this same field is a bare number. |
| `data.activityStatus` | object | `{ id, name }` — whether it is actively reporting, e.g. `Active`, `Pending Readings`. |
| `data.collector` | object (nullable) | What collects the data. `collectorType` says whether Zira pulls it or the device pushes it. |
| `data.inactiveThreshold` | number (nullable) | Seconds of silence after which the data source counts as inactive. |
| `data.readingInterval` | number (nullable) | Expected seconds between readings. `null` for push sources. |
| `data.collectionEnabled` | boolean | Whether collection is currently switched on. |
| `data.channelId` | string | Channel that carries this data source’s feed. |
| `data.displayUoms` | array (nullable) | Per-metric unit choices, as `{ metricId, uomId }`. |
| `data.meterSchema` | object (nullable) | Only present with `withSchema=true`. Holds `metrics[]`, each with its `metric` `{ id, name }`, `uom`, `displayName` and `isRequired` — this is where you map metric IDs to names. |
| `data.formConfig` | object (nullable) | Present on form data sources. Describes the fields operators fill in, with `order`, `required` and a `properties` map. |
| `data.cv` | object (nullable) | Present on camera data sources. Holds vision configuration such as counting lines, shifts and posting intervals. |
| `data.auditInfo` | object | `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate`. |

### Good to know

- Add `withSchema=true` to get `meterSchema`. That is how you map the numeric metric IDs used by `GET /reading` and `POST /reading` to human-readable names.
- Metrics that Zira calculates for you are left out of the schema, because you cannot write to them.
- Add `withLastReading=true` to get the most recent reading inline, instead of making a separate `GET /reading` call.
- `withSchema=true` on a data source that has no schema fails with a server error rather than returning an empty schema.
- The payload varies by type: forms carry `formConfig`, cameras carry `cv`, metered devices carry connection fields such as `ipAddress`, `port` and `modbus`. Read the blocks you need and ignore the rest.
- Both flags are compared against the literal string `true` — `withSchema=1` or `withSchema=yes` will not work.

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