API Reference
Public API, MCP, SDK, event, and storage contracts for Koil.
Public API, MCP, SDK, event, and storage contracts for Koil.
For provider availability and surface naming, see Provider Coverage.
Principles
- Versioned:
/v1prefix; additive changes preferred. - Idempotent: write APIs support
Idempotency-Keywhere noted. - Connected Profile-scoped: provider work starts from a Koil Connected Profile, not a raw OAuth connection or provider profile id.
- Operation-backed: HTTP, MCP, SDKs, and events derive from the same operation registry.
- Events-first: async state changes are delivered through signed, retried outbound events.
- Zero-storage by default: provider-observed content is emitted and discarded.
- Sandbox-compatible: test-mode keys exercise the same public contracts without touching live providers.
Public Surfaces
Koil exposes one operation registry through multiple public contracts:
- HTTP API (
api.koil.co) — REST endpoints under/v1. - MCP (
mcp.koil.co) — operation-shaped tools for AI agents. - SDKs — generated types and client methods from operation schemas.
- Outbound events — signed delivery through configured destinations.
These surfaces share authentication, Organization scoping, policy enforcement, errors, and generated schemas. Adding a provider operation updates HTTP, MCP, SDK, and event contracts from the same source of truth.
Authentication And Context
Data-plane requests use:
Authorization: Bearer <api_key>The API key resolves the Organization. Each provider operation resolves connectedProfileId to one Connected Profile and its Connection:
- Load the Organization-scoped Connected Profile.
- Load the Connected Profile's Connection and current provider credential.
- Verify that the Connection satisfies the operation policies, including credential kind, scopes, provider profile access, and health.
- Fail before provider I/O if the active Connection is expired, unhealthy, missing scopes, or otherwise ineligible.
Koil manages Connect Sessions, Connections, provider credentials, refresh, and connection health behind stable /v1/connect/sessions and /v1/connections routes. Internally, Nango is the source of truth for OAuth session, connection, credential, refresh, and health state; Koil owns the public resource names and Organization scoping. Koil stores Connected Profiles, event subscriptions, and lightweight operational metadata. Callers never pass raw provider OAuth grants at request time, and Koil never chooses an arbitrary credential when more than one end user can access the same provider profile. The caller chooses the connectedProfileId for the actor or automation that is taking the action.
Idempotency
Mutating operations that create durable Koil work or provider side effects require an idempotency key. In HTTP, send it as:
Idempotency-Key: <unique_key>Koil scopes each key to the Organization and operation id. The same key may be reused for a different operation, but not for a different payload on the same operation.
Behavior:
- Same key + same payload after the first invocation resolves returns the same stable result.
- Same key + same payload while the first invocation is still resolving returns
409 conflictwithRetry-After. - Same key + different payload returns
409 conflict. - Failed invocations should be retried with a new idempotency key.
The idempotency record resolves when Koil has accepted the command and produced stable result references, such as pubreq_* ids. It does not track the full lifecycle of long-running work, such as a scheduled publish completing months later.
Use idempotency keys for non-idempotent writes, including publish submission, media creation, event subscription or destination creation, secret rotation, backfill/replay requests, and API key creation. Reads, deletes that are naturally repeatable, and updates that are defined as idempotent do not require a key unless the endpoint says otherwise.
Sandbox Mode
Sandbox mode is customer-facing test mode, not Koil's internal staging environment. A sandbox API key uses the same /v1 endpoints, schemas, SDKs, MCP tools, event names, idempotency rules, pagination shapes, and error envelopes as live mode, but routes to sandbox-scoped state and simulated provider behavior.
Sandbox mode persists Koil-owned resources such as Connected Profiles, media objects, publish requests, idempotency keys, Event Destinations, Event Subscriptions, delivery attempts, and lifecycle history. Koil also scopes sandbox Connect Sessions and Connections to the sandbox Organization context through Nango tags. WorkOS owns API key identity and validation for both live and sandbox keys. Provider-side data is generated from deterministic fixtures and triggerable scenarios so customers can test success paths, pagination, webhook signatures, retries, auth failures, rate limits, media failures, and provider errors without posting real content.
Externally visible sandbox-created Koil IDs use a test-mode prefix, such as test_cs_*, test_cp_*, test_media_*, test_pubreq_*, test_evt_sub_*, and test_evt_dest_*. Connection IDs use Nango's opaque conn_* values directly. The prefix is for clarity and supportability only; the isolation boundary is the API key mode, Organization scope, sandbox datastore, and provider adapter selection.
Core Resources
- Organization — the Koil customer/workspace; billing, usage, policy, and API key scope.
- User — a person associated with one or more Organizations through the control plane.
- Connect Session — a short-lived Koil-hosted authorization flow that returns an
authorizationUrlfor the customer's end user to approve provider access. - Connection — an Organization-scoped provider authorization, exposed with Nango's opaque
conn_*id, created by Koil-hosted OAuth or by importing an existing customer-held grant. Provider credentials and refresh state live in Nango. - Connected Profile — an Organization-scoped provider profile, page, channel, location, or account that Koil can access through one specific Connection, exposed as
cp_*. Two end users authorizing the same provider profile produce two Connected Profiles, and callers choose theconnectedProfileIdthat matches the actor or automation for each operation. - Provider — the upstream platform, such as
instagram,facebook, oryoutube. - Surface — a broad provider product area, such as
media,dm,videos, orlocations. - Resource — a named object/read boundary inside a surface, such as
items,comments,messages,mentions, orreactions. - Content Type — the fully qualified provider content collection used in content URLs, composed as
{provider}.{surface}.{resource}, such asinstagram.media.items. - Capabilities — the operations and event collection modes a content type supports, such as
list,get,publish, webhook-backed events, polling-backed events, and backfill. - Variant — a subtype inside a resource, such as
feed,reel,story,post,quote, orreply. - Publish Request — a Koil-managed request to create or update provider content. It may be scheduled, pending, running, completed, failed, or canceled.
- Content — a provider-owned object addressed through a Connected Profile's Connection, such as a post, video, story, comment, message, or reaction.
- Event Destination — an Organization-owned delivery target for outbound events, such as HTTPS, Kafka, SQS, or Pub/Sub.
- Event Subscription — a Connected Profile-scoped request for Koil to emit selected event families and content types to selected destinations.
Koil-owned public resource IDs are type-prefixed, such as org_*, user_*, cs_*, cp_*, pubreq_*, media_*, evt_sub_*, evt_dest_*, and key_*. Connection IDs use Nango's opaque conn_* values directly, while Connect Session tokens are exposed as opaque Koil tokens and decoded only at Koil's Nango boundary. Sandbox-created IDs add the test-mode prefix described above. Provider-owned identifiers remain provider-defined and are passed as externalId.
Write and live-content contexts:
| Context | URL Shape | Stored By Koil | Event Family |
|---|---|---|---|
| Publish request | POST /v1/publish-requests | Ledger metadata only | publish_request.* |
| Connected Profile-observed content | GET /v1/content/{contentType}?connectedProfileId={connectedProfileId} | None | content.* |
| Moderate content | POST /v1/content/{contentType}/{externalId}/moderate?connectedProfileId={connectedProfileId} | None | content.* |
Endpoints
Health
GET /healthzGET /readyz
Provider Discovery
GET /v1/providersGET /v1/providers/{providerId}GET /v1/providers/{providerId}/surfaces
Provider discovery returns surfaces, content types, variants, schemas, and supported capabilities needed by clients, SDKs, and MCP tools.
Connect Sessions
POST /v1/connect/sessionsGET /v1/connect/sessions/{connectSessionToken}
POST /v1/connect/sessions starts a short-lived provider authorization flow and returns a Koil authorizationUrl. After the end user completes authorization, GET /v1/connect/sessions/{connectSessionToken} returns the resulting connectionId.
{
"provider": "instagram",
"integrationId": "facebook",
"externalActorId": "customer-user-123"
}Customers should treat the session read endpoint as the source of truth for the connect flow. Koil may also emit lifecycle events such as connect_session.completed or connect_session.failed so customer backends do not need to poll continuously; those events carry identifiers and status, not a full eligible-profile payload.
Connections
GET /v1/connectionsPOST /v1/connections/importGET /v1/connections/{connectionId}DELETE /v1/connections/{connectionId}GET /v1/connections/{connectionId}/eligible-profiles
POST /v1/connections/import creates a Koil Connection from an existing refreshable provider grant. Koil imports the credential material and manages refresh, health, and runtime credential resolution after import.
{
"provider": "instagram",
"providerAppId": "customer_meta_app_123",
"externalActorId": "customer-user-123",
"grant": {
"type": "oauth_refresh_token",
"refreshToken": "..."
},
"scopes": ["instagram_basic", "pages_read_engagement"],
"metadata": {
"customerUserId": "user_123"
}
}{
"connectionId": "conn_123",
"provider": "instagram",
"status": "active",
"scopes": ["instagram_basic", "pages_read_engagement"]
}Imported grants must be refreshable or otherwise manageable by Koil. Callers never pass imported credentials on provider operations.
GET /v1/connections/{connectionId}/eligible-profiles lists provider profiles currently accessible through that Connection. Customers explicitly create Koil Connected Profiles from selected eligible profiles.
Connected Profiles
GET /v1/connected-profilesPOST /v1/connected-profilesGET /v1/connected-profiles/{connectedProfileId}GET /v1/connected-profiles/{connectedProfileId}/capabilitiesPATCH /v1/connected-profiles/{connectedProfileId}DELETE /v1/connected-profiles/{connectedProfileId}
Connected Profiles are connection-scoped. If Alice and Bob both authorize the same Instagram Business Profile through separate provider OAuth grants, Koil creates two cp_* resources. A publish, read, moderation action, or subscription uses the selected connectedProfileId, preserving the provider audit path for the acting user or configured automation.
Publish Requests
POST /v1/publish-requests— submit one or more publish requests.GET /v1/publish-requests— list Koil-origin publish request ledger entries.GET /v1/publish-requests/{publishRequestId}— read one publish request ledger entry.PATCH /v1/publish-requests/{publishRequestId}— update a scheduled publish request before it runs.POST /v1/publish-requests/{publishRequestId}/cancel— cancel a pending or scheduled publish request.
POST /v1/publish-requests accepts:
{
"publishRequests": [
{
"contentType": "instagram.media.items",
"variant": "feed",
"connectedProfileId": "cp_123",
"groupId": "campaign-42",
"clientItemId": "draft-1",
"args": {
"text": "Hello world",
"media": [{ "mediaId": "media_123", "altText": "A cat" }],
"scheduling": { "at": "2026-05-01T10:00:00Z" }
}
}
]
}Rules:
- Each item resolves to one publish operation using
{ connectedProfileId, contentType, variant? }. argsis validated against that operation'sinputSchema.- Replies, reposts, quotes, comments, and similar creation shapes are modeled as publish variants and target/reference fields rather than separate write endpoints.
- Validation is atomic across the request: if any item fails, no item runs.
- After validation, each item executes independently and receives its own
pubreq_*ledger entry. groupIdandclientItemIdare caller-owned correlation metadata, not dedupe keys.- Write inputs are normalized Koil fields only. Provider-specific raw passthrough is not accepted on writes.
- SDK-local validation uses handlerless validation operations so callers can check schemas and policies without invoking publish handlers.
Submit responses return one result per item. Durable items return 202 Accepted with status URLs; synchronous items may return completed per-item outcomes.
{
"publishRequests": [
{
"publishRequestId": "pubreq_123",
"status": "created",
"statusUrl": "/v1/publish-requests/pubreq_123",
"contentType": "instagram.media.items",
"variant": "feed",
"connectedProfileId": "cp_123",
"groupId": "campaign-42",
"clientItemId": "draft-1"
}
]
}Idempotency:
Idempotency-Keyis required.- The key dedupes the whole submit operation, not individual items.
- To retry failed items from a batch, submit only those items with a new idempotency key.
Per-request limits: up to 100 items or 1 MB body, whichever is lower.
Content Reads
GET /v1/content/{contentType}?connectedProfileId={connectedProfileId}GET /v1/content/{contentType}/{externalId}?connectedProfileId={connectedProfileId}
In live mode, these are provider-backed reads. In sandbox mode, they return deterministic fixture data through the same schemas and pagination rules. They are not served from Koil's event stream or publish ledger.
Content type boundaries follow the provider honestly. contentType is the fully qualified {provider}.{surface}.{resource} identifier, such as instagram.media.items or instagram.media.comments. If a provider exposes one mixed cursor, Koil models one content type with variant values rather than pretending separate cursors exist.
There is no cross-content-type GET /v1/content?connectedProfileId={connectedProfileId} aggregate. Organizations that need a unified inbox or timeline should persist from content.* events.
Live-read responses may include providerRaw when explicitly requested:
GET /v1/content/{contentType}?connectedProfileId={connectedProfileId}&includeProviderRaw=1providerRaw is opaque, provider-defined, signed when delivered in events, and never durably stored by Koil.
Moderation And Actions
POST /v1/content/{contentType}/{externalId}/moderate?connectedProfileId={connectedProfileId}DELETE /v1/content/{contentType}/{externalId}?connectedProfileId={connectedProfileId}when supported by the content type.
Moderation operates on existing provider content, whether Koil published it or only observed it. Outcomes emit content.moderated or content.moderation_failed events.
Media
POST /v1/media— request upload; returns a pre-signed URL andmediaId.GET /v1/media/{mediaId}— media status and derived variants.DELETE /v1/media/{mediaId}— delete media and variants, unless referenced by pending publish requests.
Media inputs may use either mediaId or sourceUrl:
- Prefer
mediaIdfor reliability and dedupe. sourceUrlmust be HTTPS and is fetched/staged by Koil before use.- Optional media metadata such as
altTextis validated by the target operation schema.
Event Subscriptions
Event subscriptions ask Koil to send events for a Connected Profile, content set, and destination set. Koil may use provider-native webhooks, polling, reconciliation, or backfill internally; callers do not branch on delivery mechanics. Polling and backfill use the Connection attached to the selected Connected Profile.
GET /v1/event-subscriptionsPOST /v1/event-subscriptions— create a subscription.GET /v1/event-subscriptions/{subscriptionId}— read config, delivery health, and freshness projection.PATCH /v1/event-subscriptions/{subscriptionId}— update selected content types, destinations, or policy overrides.DELETE /v1/event-subscriptions/{subscriptionId}— stop future collection and delivery.POST /v1/event-subscriptions/{subscriptionId}/backfill— ask Koil to fetch bounded provider history and emit matching events.POST /v1/event-subscriptions/{subscriptionId}/replay— redeliver previously emitted events for this subscription.
Create body:
{
"connectedProfileId": "cp_123",
"eventTypes": ["content.*"],
"contentTypes": ["instagram.media.items", "instagram.media.comments", "instagram.dm.messages"],
"destinations": ["evt_dest_123"],
"policy": {
"cadence": "PT5M",
"maxCallsPerDay": 10000,
"backoff": { "strategy": "exponential", "maxDelay": "PT1H" }
},
"backfill": {
"since": "2026-04-01T00:00:00Z",
"maxItems": 5000
}
}Field semantics:
connectedProfileId: Connected Profile whose Connection authorizes provider reads and event collection.eventTypes: event family filter; V1 content subscriptions usually usecontent.*.contentTypes: fully qualified{provider}.{surface}.{resource}identifiers to observe.destinations: Event Destination ids that should receive matching events.policy: organization-sourced polling limits and cadence.backfill: optional bounded provider-history collection at subscription creation.sinceexpresses the desired oldest event timestamp;maxItemscaps provider reads and emitted events when provider ordering, filtering, or timestamp support is incomplete.
Response includes effective config and freshness projection:
{
"subscriptionId": "evt_sub_123",
"connectedProfileId": "cp_123",
"state": "active",
"eventTypes": ["content.*"],
"contentTypes": ["instagram.media.items", "instagram.media.comments", "instagram.dm.messages"],
"destinations": ["evt_dest_123"],
"effectivePolicy": { "source": "organization", "cadence": "PT5M", "maxCallsPerDay": 10000 },
"lastSyncedAt": "2026-04-21T14:03:00Z",
"cursorDigest": "sha256:...",
"callsConsumedToday": 1284,
"reauthRequired": false
}Backfill and replay are intentionally separate: backfill asks the provider for historical source data and emits events, while replay redelivers events Koil already emitted.
Event Destinations
These endpoints manage outbound event delivery targets:
GET /v1/event-destinationsPOST /v1/event-destinationsGET /v1/event-destinations/{destinationId}PATCH /v1/event-destinations/{destinationId}DELETE /v1/event-destinations/{destinationId}POST /v1/event-destinations/{destinationId}/rotate-secret
Create body:
{
"type": "webhook",
"config": {
"url": "https://example.com/koil/events",
"signatureMode": "standard_webhooks",
"customHeaders": {
"X-Customer-Route": "inbox"
}
}
}Field semantics:
type: delivery transport. V1 supportswebhook; Outpost-backed transports such assqs,pubsub,kafka, andkinesismay be added without changing Event Subscription shape.config: transport-specific delivery config. Webhook destinations requireurl; custom headers are optional and must not override Koil or transport-reserved headers.signatureMode: webhook signing mode;standard_webhooksis preferred for new HTTPS destinations.- Destination secrets are generated by Koil/Outpost. Callers can rotate them through
rotate-secret; during the rotation window, both previous and current secrets verify delivery signatures.
Destinations do not own Connected Profile, content type, or event filters. Event Subscriptions decide which events route to which destinations.
Usage, Policies, And Keys
GET /v1/usageGET /v1/policies/providers/{providerId}/surfaces/{surface}GET /v1/connected-profiles/{connectedProfileId}/policiesGET /v1/keysPOST /v1/keysPATCH /v1/keys/{keyId}DELETE /v1/keys/{keyId}
Public Errors
Errors use:
{
"error": {
"code": "validation_error",
"message": "Human-readable summary",
"meta": {}
}
}Stable public codes:
validation_errorunauthorizedinvalid_tokennot_foundconflictscope_insufficientrate_limit_exceededplatform_rate_limitnot_supportedinternal
Provider response metadata may appear in meta when safe. Unsupported operations return 501 not_supported.
Outbound Events
Delivery And Security
Outbound events are signed, retried, deduped, and replayable.
Headers:
Koil-Signature: HMAC SHA-256 over{timestamp}.{body}.Koil-Timestamp: signing timestamp.Koil-Event-Id: dedupe id.
Naming
Use past-tense lifecycle events and noun-prefixed families:
publish_request.*— Koil-managed publish request lifecycle.content.*— provider content observed through event subscriptions or changed by Koil actions.
Other event families:
media.uploadedmedia.upload_failedmedia.processing.completedmedia.processing.failedconnect_session.completedconnect_session.failedconnected_profile.createdconnected_profile.connection_failedconnected_profile.disconnectedevent_subscription.backfill.completedevent_subscription.backfill.failedconnection.createdconnection.expiredconnection.refresh_failedconnection.invalidatedconnection.scopes_insufficientquota.exceededpolicy.violationkey.createdkey.rotatedkey.revokedevent_destination.disabled
Publish Events
publish_request.* events correspond to one pubreq_* ledger entry. Multi-item submissions produce independent event streams per item.
publish_request.createdpublish_request.updatedpublish_request.scheduledpublish_request.cancelledpublish_request.startedpublish_request.succeededpublish_request.failedpublish_request.expired
Content Events
Provider-observed content events are emitted only while an Event Subscription includes the Connected Profile and content type. Moderation events are emitted when Koil acts on existing provider content.
content.createdcontent.updatedcontent.deletedcontent.moderatedcontent.moderation_failed
Data And Storage Guarantees
Koil stores only the application data needed to operate the content path:
- Publish request ledger metadata:
pubreq_*, status, provider references, scheduling metadata,groupId,clientItemId, and operational timestamps. - Idempotency records: operation id, idempotency key, request hash, resolution state, stable result references, and expiry.
- Event subscription projection rows:
evt_sub_*, freshness/status fields such aslastSyncedAt,cursorDigest,callsConsumedToday, andreauthRequired. - Media objects:
media_*, stored separately in R2 and referenced bymediaId.
Provider-observed content and moderation outcomes are event-only. Koil emits them and discards the content body. Durable publish payloads may live only in encrypted workflow payload/state while needed for execution; they are not copied into application tables.
Default retention:
- Publish request ledger metadata: 12 months, then
publish_request.expired. - Event subscription projections: retained while the subscription is active.
- Provider-observed content: not retained by Koil.
Pagination And Filtering
List endpoints return:
{
"data": [],
"nextCursor": null,
"hasMore": false
}Common filters include status, provider, surface, resource, variant, connectedProfileId, time range, groupId, clientItemId, and idempotency key where relevant.