Worked request and response samples
Copy a request as written, substitute your own ids, and compare the response envelope. Successful reads and writes return{ data, meta }; failures return the { error, meta } envelope, and the retry-vs-surface matrix for every class sits at the bottom of this page.
The four calls below are the chain every third-party CRM connection walks: discover what the catalogue offers, start the OAuth handshake, confirm the connection stuck and data is flowing, then trim the capabilities the connection exposes. HubSpot, Salesforce and Pipedrive ride the same rails — pick whichever the customer runs.
A hidden catch-up sync exists too: POST /:id/sync with { "sync_name": … } forces a re-pull, and GET /:id/data?model=contacts reads back the synced records. Both are in the generated operation list below; the chain spans them.
1. Discover the catalogue
GET /api/v1/integrations/catalog?coming_soon= on the dashboard merges preview rows; the plain list covers the live set.
Request
GET /api/v1/integrations pages the same providers with the org’s connected flag folded in. Treat status: "coming_soon" as un-connectable and skip the OAuth kickoff for it.
2. Start the OAuth handshake
POST /api/v1/integrations/connectintegrations:write scope; a one-time audit event records who started the flow.
Request
data.auth_url. When the provider callback returns, the connection registers and the next poll of GET /api/v1/integrations/{id}/status flips connected to true — re-fetch with GET /api/v1/integrations/connections?fresh=1 if your page caches connection state.
3. Confirm the connection — scopes and last sync
GET /api/v1/integrations/{id}/statusmetadata, and each sync plus its last heartbeat carries the timestamp your data-freshness banner renders. A never-connected or revoked key returns connected: false with an empty syncs list rather than an error, so poll it freely.
Request
PAUSED, STOPPED, ERROR or FAILED status on any sync marks the connection as errored at the fan-out layer (GET /api/v1/integrations/connections), and the dashboard card flips to a warning state — rely on the same classification when you build your own health probe.
4. Tune what the connection exposes
PATCH /api/v1/integrations/{id}/featuresaudience (list building), crm_context (agent CRM tools), agent_tools, engagement, automation. Unknown keys are rejected with a 422; a disconnected integration returns 409 instead of silently saving a dead override. GET /:id/features returns the same object with every flag defaulted to true before any save.
Request
DELETE /:id/disconnect, or key-scoped DELETE /connections/{uniqueKey} — for Slack it also flushes the per-tenant bot token before the upstream revoke so no stale events keep landing.
5. Force a sync, then read the records back
POST /api/v1/integrations/{id}/syncintegrations:write scope; a failed upstream trigger answers 502 SYNC_TRIGGER_FAILED.
Request
GET /api/v1/integrations/{id}/data?model=contactsmodel is required and is one of the sync names GET /catalog lists for the provider (for HubSpot: contacts, companies, deals). A model the connection does not sync answers 422 VALIDATION_ERROR with error.details.field: "model", the same envelope as the 422 branch in the matrix below; the new 502 SYNC_RECORDS_FAILED class means the integration server failed mid-fetch, so back off and retry rather than treating it as “no records”. If the integration server is not configured the endpoint returns data: [], which reads exactly like a model that has not synced yet — disambiguate with GET /{id}/status.
6. Take the integration further than the API — blueprints and Postman
Every GET below is authenticated with the sameX-API-Key and answers application/json with a per-org Content-Disposition filename (for an org slug of acme-corp, orbit-acme-corp.…), so save the body straight to that file. The blueprints are pre-wired to your tenant’s API host: triggers subscribe to the Orbit webhook event catalog and actions call the existing message, contact and verify endpoints.
GET /api/v1/developers/connector/zapier · GET /api/v1/developers/connector/make · GET /api/v1/developers/connector/n8n/connector/make and /connector/n8n serve the same download for Make (orbit-acme-corp.make-app.json) and n8n (orbit-acme-corp.n8n-nodes.json); import the file into the tool rather than coding against it. Need the machine-readable trigger/action summary without downloading all three? GET /api/v1/developers/connector/catalog is the one inline { data, meta } read of the set.
For Postman, the pair is a tenant-scoped collection plus its environment:
GET /api/v1/developers/postman-collection · GET /api/v1/developers/postman-environment7. Sync states worth polling
Thestatus values GET /{id}/status returns per sync, and what to do with each while you poll:
A never-run sync reports
last_synced_at: null; the connections fan-out mirrors the same classification in its errored flag, so a health probe of your own can branch on the identical set.
Errors worth branching on
These failures cover the chain above. Each block is a full{ error, meta } envelope as the API returns it, and the matrix at the bottom answers retry vs surface for the same classes. For the platform-wide decision table these branches plug into, see the error handling guide.
401 — Unauthorized
401 on this page is the bearer key failing before the route ran — it never means the resource is wrong. Rotate the key or re-mint the scoped token; retrying the same request changes nothing.
403 — Forbidden
403 means the key authenticated but the operation is gated by scope — check the key’s scopes on the developer page; a 403 is never a data-not-found shape. The connect, feature-flag and disconnect writes additionally require an owner or admin role.
422 — validation or connection-state mismatch
422 means the payload did not match the request schema — branch on error.details.field and resend with one of the keys GET /catalog returns instead of blind-retrying the same body. On the features PATCH the validator rejects any key outside audience | crm_context | agent_tools | engagement | automation the same way, and re-saving a feature override the catalogue no longer lists surfaces as a 409 with code INTEGRATION_NOT_CONNECTED (see below) — connect first, then re-apply.
429 — Rate
Retry-After header and error.details.retry_after are both set on every 429 — resend the SAME request after the lower of the two.
409 — the connection was not there yet
GET /{id}/status tells you whether that has happened without erroring on a false.
60-second retry matrix
8. Connect Intercom — OAuth install and the inbound receiver
Intercom rides the same rails as sections 1–5, with one structural difference: Orbit registers the event subscriptions on the Intercom side itself during the connect handshake, so there is no per-topic registration call for you to make. Start the install.GET /api/v1/integrations/intercom/oauth/start
answers, in the standard envelope, with the Intercom authorization URL for
your app. Hand that URL to a workspace admin; the install only completes
once an admin approves it on the Intercom side.
The redirect back. Intercom sends the admin’s browser to
GET /api/v1/integrations/intercom/oauth/callback, which verifies the
signed state, exchanges the code for a workspace token, registers the
webhook topics, and stores the connection:
200
POST /api/v1/integrations/webhooks/intercom — the URL Orbit
registered during the handshake. Your integration never calls this
endpoint; it is documented so you can recognize it in access logs and
allow-list it. Every delivery authenticates with
X-Hub-Signature: sha1=<hex>, an HMAC-SHA1 over the raw body, and
re-deliveries (Intercom retries a notification up to 30 times) no-op on the
(topic, notification_id) dedupe — a 200 from the receiver always means
“recorded exactly once.”
Read-back and re-pull. GET /api/v1/integrations/intercom/status reads
the connection back exactly like section 3 (connected, per-sync
last_synced_at); POST /api/v1/integrations/intercom/sync forces a re-pull
after a big import on the Intercom side instead of waiting for the
scheduled cadence. Any write against a workspace that has not completed the
handshake answers 409 INTEGRATION_NOT_CONNECTED (matrix above) — connect
first, then re-apply.