API v1 reference
When to use this guide: This is the complete, authoritative contract for OutboundSync API v1 — the vocabulary, every endpoint’s request and response shape, the account-status blocker and warning codes, and the security model. To create and manage keys first, see Creating API keys.
Public REST API for OutboundSync account introspection and pipeline status checks. Use at any lifecycle stage — before a campaign, mid-flight when CRM data looks wrong, or after completion. Authenticate with a Bearer token:
Authorization: Bearer osapi_<key>Making requests
Section titled “Making requests”Every v1 endpoint is a GET request authenticated with a Bearer token. The base URL is:
https://app.outboundsync.com/api/v1Start with GET /api/v1/me to confirm the key is valid and see which CRM connections it can access.
curl https://app.outboundsync.com/api/v1/me \ -H "Authorization: Bearer osapi_<your-secret>"const response = await fetch('https://app.outboundsync.com/api/v1/me', { headers: { Authorization: `Bearer ${process.env.OUTBOUNDSYNC_API_KEY}`, },});
if (!response.ok) { throw new Error(`OutboundSync API request failed: ${response.status}`);}
const data = await response.json();The API is read-only and returns no secrets (see Security). For 401 / 403 / 429 handling and a retry pattern, see Errors and rate limits.
Vocabulary
Section titled “Vocabulary”The word “webhook” is overloaded in OutboundSync. The public API uses industry-standard sources → destinations vocabulary:
| Concept | Schema today | UI label today | API term | Path |
|---|---|---|---|---|
Inbound URLs sequencers POST events to (POST /webhooks/:code) | Webhook model | ”Webhook receiver” | source | GET /api/v1/sources |
| Customer URLs OutboundSync forwards received events to | UserAccount + UserAccountWebhook junction | ”Webhook endpoint” / “Additional endpoints” | destination | field names only; /api/v1/destinations later |
| OutboundSync platform-emitted events (future) | — | — | webhooks | /api/v1/webhooks reserved, unused |
Notes:
- “Webhooks” is reserved for platform-emitted events (Stripe/HubSpot/Segment convention).
- “Destination” is the umbrella term for managed integrations and raw webhook URLs.
eventTypes[]lives on the destination binding, not on the source.- The physical inbound route
/webhooks/:codeis unchanged — it is the value of a source’surlfield. Legacy sources created before code-based routing use/webhooks/:hubId/:ownerId; theurlfield always reflects the correct form.
Namespace convention
Section titled “Namespace convention”| Namespace | Now | Later |
|---|---|---|
/api/v1/account/* | status | usage, limits; /me stays as key-identity check |
/api/v1/connections | list w/ embedded status | /:id, /:id/test, /:id/properties |
/api/v1/sources | list w/ config + destinations summary | /:id, /:id/logs, POST create |
/api/v1/destinations | — (fields only) | standalone resource |
/api/v1/blocklists | — (fast-follow) | list, /:id/entries |
/api/v1/webhooks | reserved | platform webhooks + subscriptions |
/api/v1/events, /api/v1/sync-logs | — | activity/observability |
Rules: plural kebab-case resources; status embedded on resources; cross-cutting checks under account/; every list response wrapped in a named key ({ "sources": [...] }). GET /api/v1/me is the identity exception — flat top-level fields plus a links object for related endpoints.
Endpoints
Section titled “Endpoints”GET /api/v1/me
Section titled “GET /api/v1/me”Returns account, API key metadata, accessible CRM connections, and links to related v1 endpoints. Use as the bootstrap call after authentication.
apiKey.connectionId is set when the key is connection-scoped (connectionScope: "connection"); it is null for account-scoped keys and matches the single entry in connections[].
connections[] order matches accessibleConnectionIds — the same order as /connections and /account/status.
Response: MeResponse
{ "account": { "id": 42, "email": "harris@outboundsync.com" }, "apiKey": { "name": "prod", "scopes": ["read"], "connectionScope": "account", "connectionId": null }, "connections": [ { "id": 7, "crm": "HUBSPOT", "organizationId": "123", "organizationDomain": "acme.com" } ], "links": { "accountStatus": { "href": "/api/v1/account/status", "description": "Pipeline readiness with blockers and warnings for each connection." }, "connections": { "href": "/api/v1/connections", "description": "CRM connections with OAuth status and plan capabilities." }, "sources": { "href": "/api/v1/sources", "description": "Inbound paste URLs, platform config, and destination bindings." }, "documentation": { "href": "https://outboundsync.com/docs/api/v1/", "description": "OutboundSync API v1 reference." } }}For connection-scoped keys, connectionScope is "connection", connectionId matches connections[0].id, and connections[] has one element.
GET /api/v1/account/status
Section titled “GET /api/v1/account/status”Point-in-time pipeline status across all connections the key can access. Answers: can OutboundSync receive events and sync them to the CRM right now? Connection-scoped keys receive a one-element array.
This is account-specific and requires a key. For platform-wide availability (independent of your account), use the public, unauthenticated Platform health endpoints instead.
Response: AccountStatusResponse
The response has three outcome layers:
| Layer | Field | Meaning |
|---|---|---|
| Red light | blockers[] | Sync cannot work until resolved. Gates ready. |
| Yellow light | warnings[] | Optional features not configured or non-fatal issues. Never gates ready. |
| Detail | connections[].* per-component status | Embedded component state for each connection. |
{ "ready": true, "checkedAt": "2026-07-03T12:00:00.000Z", "connections": [ { "connectionId": 7, "crm": "HUBSPOT", "ready": true, "crmConnection": { "status": "ready", "crm": "HUBSPOT", "organizationId": "123", "organizationDomain": "acme.com" }, "sources": { "status": "ready", "count": 2, "platforms": ["smartlead", "instantly"] }, "destinations": { "status": "not_configured", "count": 0, "eventTypes": [] }, "blocklists": { "status": "not_configured", "enabledCount": 0, "lastError": null, "lastFetchedAt": null } } ], "blockers": [], "warnings": [ { "code": "destinations_not_configured", "connectionId": 7, "crm": "HUBSPOT", "message": "No additional endpoints are configured — events still sync to HubSpot; nothing is forwarded to external URLs.", "remediation": "Optional: add forwarding URLs in the OutboundSync dashboard (https://app.outboundsync.com/admin/dashboard/webhooks) if you need events sent to Clay, Zapier, or other systems.", "docUrl": "https://outboundsync.com/docs/api/v1/#warning-destinations_not_configured" }, { "code": "blocklists_not_configured", "connectionId": 7, "crm": "HUBSPOT", "message": "No block list is configured — sync to HubSpot is active but no contacts are being suppressed.", "remediation": "Optional: configure a block list in the OutboundSync dashboard (https://app.outboundsync.com/admin/dashboard/smartlead-block-lists) if you need to skip certain contacts.", "docUrl": "https://outboundsync.com/docs/api/v1/#warning-blocklists_not_configured" } ]}When CRM is disconnected, ready is false and blockers contains crm_disconnected — warnings may also be present but do not affect ready.
HTTP semantics: authenticated-but-not-ready returns HTTP 200 with ready: false. Auth failures return 401/403; rate limits return 429.
GET /api/v1/connections
Section titled “GET /api/v1/connections”CRM connections with embedded status and capability flags.
Response: ConnectionsResponse
{ "connections": [ { "id": 7, "crm": "HUBSPOT", "status": "ready", "organizationId": "123", "organizationDomain": "acme.com", "capabilities": { "sync": true, "destinations": true, "blocklists": true }, "createdAt": "2025-01-01T00:00:00.000Z" } ]}GET /api/v1/sources
Section titled “GET /api/v1/sources”Inbound sources with the URL to paste into a sequencer, platform, config summary, and destination bindings.
Response: SourcesResponse
{ "sources": [ { "id": 10, "url": "https://sync.outboundsync.com/webhooks/abc123", "platform": "smartlead", "connectionId": 7, "crm": "HUBSPOT", "config": { "createOrUpdateCompany": true, "createOrUpdateTask": true, "assignContactOwner": true, "salesforceObjectType": null }, "destinations": [ { "url": "https://hooks.example.com/os", "description": "Clay", "eventTypes": ["EMAIL_SENT", "EMAIL_REPLY"], "isDelayed": false } ], "createdAt": "2025-01-02T00:00:00.000Z" } ]}Status enums
Section titled “Status enums”ComponentStatus
Section titled “ComponentStatus”| Value | Meaning |
|---|---|
ready | Component is configured and operational (DB-derived) |
not_configured | Feature enabled but nothing set up yet |
disconnected | CRM OAuth token missing (refreshToken is null) |
disabled | Feature not enabled on this connection (plan/capability gate) |
error | Component has a recorded error (blocklists only) |
BlockerCode
Section titled “BlockerCode”Blockers are hard failures — sync cannot work. They gate the top-level ready flag. Only these three codes appear in blockers[].
Each blocker includes:
| Field | Type | Description |
|---|---|---|
code | BlockerCode | Machine-readable identifier |
connectionId | number | Connection this blocker applies to |
crm | string | CRM profile enum (e.g. HUBSPOT, SALESFORCE) |
message | string | Human-readable explanation of what is wrong |
remediation | string | Actionable next step, including dashboard deep links where applicable |
docUrl | string | Link to this documentation section for the blocker code |
WarningCode
Section titled “WarningCode”Warnings are optional gaps or non-fatal issues. They never gate ready. A connection can be ready: true with warnings present.
Each warning includes the same fields as blockers (code, connectionId, crm, message, remediation, docUrl).
When a feature is disabled on the plan (canUserAccount or canBlockList is false), the component status is disabled and no warning is emitted — the customer chose not to use that feature.
Readiness derivation
Section titled “Readiness derivation”Per connection:
crmConnection
refreshToken != null→readyrefreshToken == null→disconnected+ blockercrm_disconnected
sources
canWebhook == false→disabled+ blockersync_not_enabledcanWebhook && count == 0→not_configured+ blockerno_sourcescount > 0→ready
destinations (component detail; may also surface as warnings[])
canUserAccount == false→disabled(no warning)- zero destination bindings →
not_configured+ warningdestinations_not_configured - ≥1 binding →
ready
blocklists (component detail; may also surface as warnings[])
canBlockList == false→disabled(no warning)- no lists or none enabled →
not_configured+ warningblocklists_not_configured - any enabled list with
lastError != null→error+ warningblocklists_error - otherwise →
ready
Connection ready: crmConnection.status === "ready" && sources.status === "ready"
Top-level ready: connections.length > 0 && every(connection.ready)
Security
Section titled “Security”Serialization allowlist
Section titled “Serialization allowlist”Responses never include: refreshToken, settings, details, keyHash, apiKeyId, salesfinityWebhookSecret.
refreshToken is read from the database only to derive crmConnected status; the token value is discarded before any response assembly.
Sensitive fields by design
Section titled “Sensitive fields by design”| Field | Endpoint | Note |
|---|---|---|
sources[].url | /sources | Contains the webhook ingress code. Treat API keys like secrets — anyone with read access can obtain paste URLs. |
destinations[].url | /sources | Customer-configured forwarding URLs. Only returned for bindings owned by the same connection. |
blocklists.lastError | /account/status | Operational error text from CRM sync jobs. May contain vendor API messages; surfaced in warnings[] when applicable. |
account.email | /me | PII; only returned on the identity endpoint. |
Authentication and rate limits
Section titled “Authentication and rate limits”| Status | Meaning |
|---|---|
401 | Missing/invalid/revoked API key (WWW-Authenticate: Bearer realm="OutboundSync API") |
403 | Valid key but no API-enabled connections (canUseApi) |
429 | Per-IP or per-account rate limit exceeded (Retry-After header set) |
API keys are scoped to accessibleConnectionIds resolved at auth time. Connection-scoped keys see exactly one connection. All list queries re-apply deletedAt: null on connections. See Errors and rate limits for the full error contract and retry guidance.
SOC 2 notes
Section titled “SOC 2 notes”- Endpoints are read-only; the only write on authenticated requests is fire-and-forget
lastUsedAton the API key (existing auth guard behavior). - Status is DB-derived; no live CRM calls that would expand the data processing boundary.
- Scope enforcement (
scopes[]) is stored on keys but not yet checked per-endpoint — all issued keys currently receivereadonly.
Deferred
Section titled “Deferred”lastEventReceivedAton sources (requiresWebhookLogindex migration)POST /api/v1/connections/:id/test(live CRM token probe)GET /api/v1/blocklists- Standalone
/api/v1/destinationsresource - OpenAPI spec
Ask an AI coding assistant
Section titled “Ask an AI coding assistant”Using the OutboundSync API v1 (https://outboundsync.com/docs/api/v1/), write a function that:1. Reads OUTBOUNDSYNC_API_KEY from the environment.2. Calls GET https://app.outboundsync.com/api/v1/account/status with Bearer authentication.3. Returns `ready`, plus any blockers[] and warnings[] (code, message, remediation).Handle 401, 403, and 429 per https://outboundsync.com/docs/api/errors-and-rate-limits/.