Submit and retrieve meter readings via token-authenticated REST endpoints
The Snap & Bill Reading Ingestion API lets authorised third-party systems push meter readings directly into the platform without requiring a user login, JWT flow, or CSV upload. Each integration receives a long-lived bearer token that is scoped to a specific service provider and site.
The API supports partial success — a single call may accept some readings while rejecting others. Every response contains per-row status information so your system can react individually to each result.
Content-Type: application/json and Accept: application/json.Pass your integration token as a standard HTTP Bearer token:
Authorization: Bearer <your-integration-token>
expires_at) configured at creation time.
Every integration token is associated with one or more allowed source IP addresses or CIDR ranges. Requests arriving from an IP not on the whitelist receive a 403 Forbidden response, even if the token is otherwise valid.
| CIDR example | Effect |
|---|---|
| 203.0.113.45 | Single IPv4 address |
| 203.0.113.0/24 | Entire /24 subnet |
| 0.0.0.0/0 | Any IP — useful for testing environments only |
0.0.0.0/0 in production is strongly discouraged.{
"request_id": "partner-unique-id-001", // required, your idempotency key
"service_type_id": 2, // required, must be valid for the site
"readings": [ // required, non-empty array
{
"barcode": "ABC123456", // required
"current_unit": 452.7, // required, numeric
"reading_date": "2026-03-25" // required, YYYY-MM-DD, not in future
}
]
}
| Field | Type | Required | Notes |
|---|---|---|---|
| request_id | string | required | Your unique identifier for this submission. Used for idempotency — replaying the same request_id returns the original result without reprocessing. Max 255 chars. |
| service_type_id | integer | required | Must correspond to a valid service type (e.g. 1 = Water, 2 = Electricity). Will be validated against platform data. |
| readings | array | required | One or more reading objects. Must not be empty. Duplicate barcodes within one request are rejected. |
| readings[].barcode | string | required | The meter barcode identifier. Must exist in the platform and belong to the site associated with your token. |
| readings[].current_unit | numeric | required | Current meter reading value. Must be ≥ the previous recorded unit for that meter. |
| readings[].reading_date | date | required | Date of reading in YYYY-MM-DD format. Cannot be a future date. |
{
"status": "accepted",
"request_id": "partner-unique-id-001",
"accepted_count": 2,
"rejected_count": 0,
"results": [
{
"barcode": "ABC123456",
"status": "accepted",
"open_session_id": 4567
}
]
}
{
"status": "partial_success",
"request_id": "partner-unique-id-001",
"accepted_count": 1,
"rejected_count": 1,
"results": [
{
"barcode": "ABC123456",
"status": "accepted",
"open_session_id": 4567
},
{
"barcode": "ZZZ999",
"status": "rejected",
"code": "BARCODE_NOT_FOUND",
"message": "Barcode does not exist"
}
]
}
| Value | Meaning | HTTP code |
|---|---|---|
| accepted | All readings accepted | 200 |
| partial_success | Some accepted, some rejected | 200 |
| rejected | All readings rejected (or payload-level rejection) | 422 |
Returns the most recent recorded unit and date for every meter lot associated with your token's site and service provider. Use this to determine the baseline before submitting new readings.
| Parameter | Type | Required | Notes |
|---|---|---|---|
| service_type_id | integer | optional | Filter results to a specific service type. Omit to return all service types for the site. |
{
"service_provider_id": 7,
"service_provider_name": "Pieter Both Syndic Services",
"site_id": 3,
"site_name": "Aqua Springs",
"site_fullname": "SYNDICAT DES COPROPRIÉTAIRES DE AQUA SPRINGS",
"readings": [
{
"barcode": "ABC123456",
"service_type_id": 2,
"previous_unit": 448.2,
"last_reading_date": "2026-02-28"
}
]
}
The API enforces idempotency at the (token, request_id) level. If a request is retried with the same request_id, the original response is returned unchanged — no duplicate sessions or readings are created.
An idempotent replay returns the original result verbatim with one extra field added:
{
"status": "accepted",
"request_id": "partner-unique-id-001",
"accepted_count": 1,
"rejected_count": 0,
"replayed": true,
"results": [ /* ... original results ... */ ]
}
422, a replayed acceptance returns 200. Always check the replayed field to distinguish a cached result from a fresh one.request_id (e.g. a UUID or a hash of your batch) for each distinct submission. Reuse the same request_id only when retrying after a network failure on a request you are unsure was processed.These codes appear in results[].code when a single reading is rejected:
| Code | Meaning | Resolution |
|---|---|---|
| BARCODE_NOT_FOUND | The barcode value does not exist in the platform | Verify the barcode against the GET endpoint or your provisioned list |
| LOT_NOT_FOUND | The barcode exists but is not linked to any lot for your site and service type | Contact your Snap & Bill administrator to verify the meter assignment |
| READING_BELOW_PREVIOUS | The submitted current_unit is less than the previously recorded value |
Use GET /readings to retrieve the current baseline and resubmit |
| DUPLICATE_BARCODE_IN_PAYLOAD | The same barcode appears more than once in the same request | Deduplicate your readings array before sending |
These errors cause the entire request to be rejected before per-row processing:
{
"message": "The readings field is required."
}
request_id, service_type_id, readings)service_type_id (does not exist in platform)readings is empty or not an arrayreading_date is in the future or invalid format (must be YYYY-MM-DD)current_unit is not numeric| Code | Description |
|---|---|
| 200 OK | Request processed. Check status field — may be accepted, partial_success, or rejected. |
| 401 Unauthorized | Token is missing, invalid, revoked, or expired. |
| 403 Forbidden | Token is valid but the source IP is not on the whitelist. |
| 422 Unprocessable Entity | Payload failed schema validation (missing fields, invalid formats, etc.). |
| 429 Too Many Requests | Rate limit exceeded. Back off and retry. |
| 500 Internal Server Error | Unexpected server error. Contact support with the request time and your request_id. |
| Version | Date | Notes |
|---|---|---|
| 1.0 | April 2026 | Initial release — POST/GET readings, token management, idempotency, IP whitelisting |
For integration support, provisioning of tokens, or to report an issue, contact [email protected].