Skip to main content

Gateway bundle synchronization

After public-key registration, Edge License Gateway pulls complete Cloud-signed Edge Bundle v2 generations through outbound HTTPS. Cloud never opens an inbound connection to the Host, and the Host does not connect to Cloud RabbitMQ or a Cloud database.

POST /api/v1/edge-gateways/{gatewayInstanceId}/sync/pull
POST /api/v1/edge-gateways/{gatewayInstanceId}/sync/ack
POST /api/v1/edge-gateways/{gatewayInstanceId}/usage/batches

All requests use Content-Type: application/json and require Idempotency-Key plus X-Edge-Gateway-Request-Jws. The JWS is ES256 with type MH-EDGE-GATEWAY-REQUEST-V1, signed by the current private Gateway key. Its canonical payload binds operation, POST, the exact path and Gateway id, the idempotency key, the lowercase SHA-256 body digest and a UTC timestamp. Cloud accepts at most 60 seconds of clock skew. Tenant, Customer, Site and Product are not request authority.

Pull

Bootstrap sends {"cursor":null}. Later pulls echo only the last cursor that was durably activated and acknowledged. Success is application/vnd.myhoreca.edge-bundle.v2+zip with headers:

X-Edge-Gateway-Cursor
X-Edge-Gateway-Generation
X-Edge-Gateway-Manifest-Id
Cache-Control: no-store
Pragma: no-cache

The body is one complete signed Edge Bundle v2 archive. Retrying the same idempotency key and semantic request returns the same persisted bytes, including after a Cloud process restart. Reusing the key for another body returns 409. A new pull before acknowledgement returns the same pending generation.

Acknowledge

Send acknowledgement only after signature, digest and binding verification and durable atomic local activation:

{
"cursor": "1.019c4400-0000-7000-8000-000000000010",
"generation": 1,
"manifestId": "019c4400-0000-7000-8000-000000000010",
"appliedAtUtc": "2026-08-08T12:00:00Z",
"outcomeCode": "activated"
}

Cloud advances the cursor only for the exact delivered tuple. A foreign, skipped, stale or contradictory acknowledgement returns 409. Exact retry is safe and returns the stored receipt.

Usage upload

An active Gateway uploads one bounded usage batch with JWS operation usage-upload. Idempotency-Key is derived exactly as edge-usage-batch-{batchId:N}. The JSON body is strict: it contains only the batch id and 1–500 ordered events, and it is limited to 512 KiB.

{
"batchId": "019c5bc0-0000-7000-8000-000000000005",
"events": [
{
"localEventId": "019c5bc0-0000-7000-8000-000000000006",
"meterDefinitionId": "019c5bc0-0000-7000-8000-000000000020",
"productId": "019c5bc0-0000-7000-8000-000000000003",
"licenseId": "019c5bc0-0000-7000-8000-000000000004",
"quantity": 1,
"occurredAtUtc": "2026-08-11T12:00:00Z"
}
]
}

Tenant, Gateway and Customer are never accepted from the body. Cloud obtains them from the authenticated Gateway binding and rejects unknown properties. Product must match the referenced BC-USG meter. A durable success response is:

{
"batchId": "019c5bc0-0000-7000-8000-000000000005",
"status": "accepted",
"acceptedAtUtc": "2026-08-11T12:00:01Z",
"eventCount": 1,
"replayed": false
}

The acknowledgment is returned only after every event and the receipt are durable. Exact replay returns the original acceptedAtUtc, event count and replayed: true without duplicate events. Reusing the batch key with changed content returns 409.

Errors and recovery

400 means malformed input or a rejected usage event; 401 invalid proof-of-possession; 403 expired signature or forbidden Gateway state; 409 cursor/idempotency/generation/content conflict; 413 a body over 512 KiB; 415 non-JSON input; 503 unavailable bundle, authentication state or usage owner. All errors use the standard public error envelope and do not echo keys, signatures, Tenant or Customer data.

The synchronization endpoints deliver and acknowledge Cloud generations; the usage endpoint records facts and cannot grant or extend rights. The Host keeps usage events and batch membership append-only, retries exact stored bytes, and records the matching Cloud acknowledgment durably. See Gateway public-key registration and Local Relay.