Third-Party Integration API Platform
Partner-facing REST API for Zimi devices: OAuth 2.0 user consent, scoped control, and signed event delivery so integrations do not poll the fleet.

Technologies
- TypeScript
- Node.js
- Express
- Fastify
- OAuth2
- JWT
- Google Cloud PubSub
- PostgreSQL
- Redis
- Docker
- Kubernetes
- OpenAPI
- Swagger
Key Achievements
- OAuth 2.0 so a user grants a partner access to their own devices, not the fleet
- Device read/control APIs with rate limits and cached hot reads
- Webhook events for status changes instead of partner polling
Project Links
Partners needed to read Zimi devices, change them, and hear about state changes. They did not need our MQTT broker, our document store, or our admin paths. I built the partner-facing REST API in TypeScript so third parties talk to a contract: OAuth 2.0, scoped resources, and events.
Partners needed the devices, not the internals
An integration that “just uses the internal API” couples their release cycle to ours and puts every internal handler on the public internet. The other failure mode is API keys that represent the company, not the household — a partner with a leaked key should not be able to reach devices a user never authorised.
Scale makes the obvious sync strategy lethal. If every partner polls every device they care about, their retry loops become our QPS. Status changes already exist as internal events. The API should forward the ones a subscriber is allowed to see.
This surface also had to survive the same production bar as the rest of the platform (99.999% uptime): timeouts, retries, and rate limits as part of the design, not as an afterthought.
What changed
The public API is a boundary in front of the platform:
- OAuth 2.0 authorisation code with user consent. The partner never gets a god-mode key for the fleet.
- Scopes such as device read, device control, and event subscription. A weather-display integration should not be able to toggle loads.
- REST for reads and commands, documented with OpenAPI / Swagger so onboarding is the spec, not a slide deck.
- Ownership checks on every call. A valid token is not enough; the user must actually have that device.
- Redis for hot device reads and for rate-limit counters per client and route.
- Pub/Sub → signed webhooks for
device_status_change(and related) events, with retries on failed delivery.
Voice assistants use the same idea — user linking and a narrow fulfilment path — and are covered separately in the voice write-up and the C4 identity/voice views. This API is the partner contract around device data and events; it is not a dump of every internal service.
Push events, don’t let them poll
The decision that keeps this API from becoming a load generator is event delivery.
Internal telemetry already fans out after decode. Once a partner has events:subscribe and a webhook, we push the authorised subset, signed so they can reject spoofed callbacks. They do not need a cron job walking /devices.
Commands still go through REST: validate token and scope, check ownership, execute, publish the resulting state event. Partners who issued the command and partners who only listen both see the same change.
Caching is conservative. Device snapshots are safe to cache briefly; a command invalidates that key. Rate limits are per client and stricter on control routes than on reads. Exact quotas are a commercial setting, not a number I am publishing here.
I did not bolt GraphQL onto this. The partner questions are known — list devices, act, subscribe — and a stable REST+events contract is easier to support than a query language over the internals.
After
External integrations sit on an authorised, documented API instead of on internal handlers. Users grant access per partner. Status moves as events. The MQTT broker and stores stay private.
Related: C4 identity and access, voice, migration.