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.