Skip to main content

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
Every provider tile the Integrations grid offers is answered here — each row carries the provider’s key, the syncs and actions it advertises, and a status flag so you can gate a roadmap row before you send an operator into an OAuth flow. ?coming_soon= on the dashboard merges preview rows; the plain list covers the live set. Request
Response
Row-level badges come from a different call — 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/connect
Starts the connect flow for a catalogue key and returns the provider authorization URL your operator should be sent to. The handler resolves the provider’s own authorize endpoint — there is no connection record to create beforehand; the kickoff is one POST. Owner/admin role plus the integrations:write scope; a one-time audit event records who started the flow. Request
Response
Redirect the operator’s browser to 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}/status
Reads the connection back after the OAuth redirect: granted provider scopes sit under metadata, 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
Response
A 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}/features
Per-tenant capability flags on the connection: audience (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
Response
Rotate a connected connection off the same way: 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}/sync
The scheduled cadence is usually enough, but after a big import on the provider side you can force the named sync to run now instead of waiting. The call returns as soon as the run is queued, not when it finishes: poll section 3 for the outcome. Owner/admin role plus the integrations:write scope; a failed upstream trigger answers 502 SYNC_TRIGGER_FAILED. Request
Response
Once that run lands, pull the synced records for one model yourself. The read walks the full record set, not a first page, so the array is the model’s complete current copy.
GET /api/v1/integrations/{id}/data?model=contacts
Request
Response
model 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 same X-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
Request
Response
/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-environment
Request
Response
Import the collection and the environment together; the environment carries the base URL and scopes the collection’s requests to your org.

7. Sync states worth polling

The status 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

A 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

A 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

A 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

The 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

The features PATCH refuses to store an override for a key with no live connection — send the operator through the connect flow (section 2), then re-apply the override. 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
A declined install answers 400 with the standard error envelope; re-run the start call to retry. The inbound receiver. Once connected, Intercom delivers workspace events to 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.