Ingest API reference and Python client

Xero and QuickBooks Online only report usage for your app as a whole. To see which of your tenants drives the bill, you send Tierguard one small event per outbound API call, from the code that makes the call. This page documents that endpoint and the Python client that wraps it.

Overview

There is one endpoint. Base URL: https://tierguard.dev.

POST https://tierguard.dev/v1/ingest
Authorization: Bearer <ingest token>
Content-Type: application/json

The body is a JSON array of events, or a single event object. Each event says which tenant a call was for, which endpoint was called and how much it cost in bytes and calls.

Authentication

Send an ingest token as a bearer token: Authorization: Bearer <token>. A token belongs to one project, and every event you send with it is recorded under that project.

Request

Each event has these fields. Unknown fields are rejected.

FieldTypeRequiredDescription
tenant_refstring, max 256 charsyesYour identifier for the tenant (connected Xero organisation or QuickBooks company) the call was made for.
endpointstring, max 256 charsyesThe upstream API endpoint that was called, for example /Invoices.
bytes_egressinteger, 0 to 1015one of the two metricsResponse size in bytes.
call_countinteger, 0 to 1015one of the two metricsNumber of API calls this event represents, usually 1.
occurred_atISO 8601 date-timeyesWhen the call happened. A timestamp with an offset is converted to UTC; a timestamp without an offset is stored as given and treated as UTC.
event_idstring, max 256 charsyesIdempotency key, unique per project. See Idempotency.

At least one of bytes_egress and call_count must be present. A missing metric is stored as 0.

Response

A successful request returns 200 with the number of newly stored events and the number of events that were already known:

{"accepted": 2, "duplicates": 0}

Errors

Errors are JSON with a detail field.

StatusMeaningBodyRetry?
401Missing or malformed Authorization header, unknown token or revoked token.{"detail": "Invalid or revoked token"} or {"detail": "Missing or invalid Authorization header"}No. Fix the token.
422The body failed validation: a missing or unknown field, a value out of range, neither metric present, or more than 1000 events.For too many events: {"detail": "Batch size exceeds maximum of 1000"}. For field errors, detail is a list of validation errors, each with loc and msg.No. The whole request is rejected and nothing is stored.
5xxServer-side failure.Not specified.Yes, with backoff. Re-sending is safe.

Body validation runs before the token is checked, so a malformed body gets 422 even when the token is missing or wrong.

Idempotency

event_id is unique per project. If you send an event whose event_id was already stored for the project, it is not stored again and is counted in duplicates instead of accepted. Use a stable id per logical API call, such as the upstream request id, and a retry after a timeout or a 5xx cannot double-count.

Events in a batch are stored one at a time. If the token is revoked or its project deleted while a batch is being stored, the events stored before that point stay stored and the request answers 401; sending the same batch again with a valid token counts them in duplicates.

Limits

curl examples

One event:

curl -X POST https://tierguard.dev/v1/ingest \
  -H "Authorization: Bearer $TIERGUARD_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"tenant_ref": "org-123", "endpoint": "/Invoices", "bytes_egress": 48211, "call_count": 1, "occurred_at": "2026-10-06T09:30:00Z", "event_id": "req-0001"}'

A batch:

curl -X POST https://tierguard.dev/v1/ingest \
  -H "Authorization: Bearer $TIERGUARD_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[
    {"tenant_ref": "org-123", "endpoint": "/Invoices", "bytes_egress": 48211, "occurred_at": "2026-10-06T09:30:00Z", "event_id": "req-0001"},
    {"tenant_ref": "org-456", "endpoint": "/Contacts", "call_count": 3, "occurred_at": "2026-10-06T09:30:02Z", "event_id": "req-0002"}
  ]'

Python client

You can call the endpoint directly, or use tierguard_client.py, a single file that buffers events and sends them in the background. It needs Python 3.9 or later and httpx, nothing else.

Verify the download with shasum -a 256 tierguard_client.py; the output must match the SHA-256 above.

Create one client per process, at module level, and reuse it:

from tierguard_client import TierguardClient

tg = TierguardClient("https://tierguard.dev", "YOUR_INGEST_TOKEN")

resp = xero.get(f"/connections/{org_id}/Invoices")
tg.record(tenant_ref=org_id, endpoint="/Invoices",
          bytes_egress=len(resp.content), call_count=1)

record() returns the event's event_id. If you do not pass one, a random id is generated; pass your own to make retries idempotent. record_response(tenant_ref, response) does the same from an httpx.Response, taking the endpoint path and body size from it.

Constructor options

OptionDefaultMeaning
base_url, tokenrequiredTierguard origin and your ingest token.
batch_size100Send as soon as this many events are waiting (capped at 1000).
flush_interval5.0Seconds between background sends of whatever is buffered.
timeout5.0HTTP timeout per request, in seconds.
max_buffer10000Events kept in memory. Beyond this the oldest are dropped.
backoff_base, backoff_max1.0, 300.0Seconds of the first and the longest pause after a failed send. The pause doubles on each consecutive failure and is jittered.
shutdown_timeout5.0Longest wait, in seconds, for the final flush in close() and at interpreter exit.

Read-only properties: pending (events waiting to be sent) and dropped (events discarded because the buffer overflowed or the server rejected them).

What the client guarantees

Serverless: the process can be frozen as soon as your handler returns, before the background thread runs. Call tg.flush() before returning.