API Reference — v1

Snap & Bill
Third-Party Reading Ingestion API

Submit and retrieve meter readings via token-authenticated REST endpoints

Versionv1.0
Base URLhttps://api.snapnbill.com/api/v1
FormatJSON only
DateApril 2026

Contents

  1. Overview
  2. Authentication
  3. IP Whitelisting
  4. Endpoints — Submit Readings (POST)
  5. Endpoints — Retrieve Previous Readings (GET)
  6. Idempotency
  7. Error Reference
  8. HTTP Status Codes
  9. Changelog & Contact

1   Overview

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.

All endpoints accept and return JSON only. Always send Content-Type: application/json and Accept: application/json.

2   Authentication

Pass your integration token as a standard HTTP Bearer token:

Authorization: Bearer <your-integration-token>

Token behaviour

Treat your token like a password. The raw token value is displayed only once at creation time and is never stored in plain text. If you lose it, a new token must be issued.

3   IP Whitelisting

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 exampleEffect
203.0.113.45Single IPv4 address
203.0.113.0/24Entire /24 subnet
0.0.0.0/0Any IP — useful for testing environments only
Inform your Snap & Bill administrator of your egress IP address(es) before you go live. Using 0.0.0.0/0 in production is strongly discouraged.

4   Submit Readings

POST /api/v1/integrations/readings Submit one or more meter readings

Request body

{
  "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 reference

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

Success response — all accepted

{
  "status":         "accepted",
  "request_id":     "partner-unique-id-001",
  "accepted_count": 2,
  "rejected_count": 0,
  "results": [
    {
      "barcode":         "ABC123456",
      "status":          "accepted",
      "open_session_id": 4567
    }
  ]
}

Partial success response

{
  "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"
    }
  ]
}

Overall status values

ValueMeaningHTTP code
acceptedAll readings accepted200
partial_successSome accepted, some rejected200
rejectedAll readings rejected (or payload-level rejection)422

5   Retrieve Previous Readings

GET /api/v1/integrations/readings Fetch last recorded readings for all meters on the site

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.

Query parameters

ParameterTypeRequiredNotes
service_type_id integer optional Filter results to a specific service type. Omit to return all service types for the site.

Response

{
  "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"
    }
  ]
}

6   Idempotency

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.

Replayed response indicator

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 ... */ ]
}
The HTTP status code on a replay mirrors the original response — a replayed rejection returns 422, a replayed acceptance returns 200. Always check the replayed field to distinguish a cached result from a fresh one.
Use a new 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.

7   Error Reference

Per-reading error codes

These codes appear in results[].code when a single reading is rejected:

CodeMeaningResolution
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

Payload-level errors (HTTP 422)

These errors cause the entire request to be rejected before per-row processing:

{
  "message": "The readings field is required."
}

8   HTTP Status Codes

CodeDescription
200 OKRequest processed. Check status field — may be accepted, partial_success, or rejected.
401 UnauthorizedToken is missing, invalid, revoked, or expired.
403 ForbiddenToken is valid but the source IP is not on the whitelist.
422 Unprocessable EntityPayload failed schema validation (missing fields, invalid formats, etc.).
429 Too Many RequestsRate limit exceeded. Back off and retry.
500 Internal Server ErrorUnexpected server error. Contact support with the request time and your request_id.

9   Changelog & Contact

VersionDateNotes
1.0April 2026Initial release — POST/GET readings, token management, idempotency, IP whitelisting

For integration support, provisioning of tokens, or to report an issue, contact [email protected].