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: /v1 prefix; additive changes preferred.
  • Idempotent: write APIs support Idempotency-Key where 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:

  1. Load the Organization-scoped Connected Profile.
  2. Load the Connected Profile's Connection and current provider credential.
  3. Verify that the Connection satisfies the operation policies, including credential kind, scopes, provider profile access, and health.
  4. 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 conflict with Retry-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 authorizationUrl for 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 the connectedProfileId that matches the actor or automation for each operation.
  • Provider — the upstream platform, such as instagram, facebook, or youtube.
  • Surface — a broad provider product area, such as media, dm, videos, or locations.
  • Resource — a named object/read boundary inside a surface, such as items, comments, messages, mentions, or reactions.
  • Content Type — the fully qualified provider content collection used in content URLs, composed as {provider}.{surface}.{resource}, such as instagram.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, or reply.
  • 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:

ContextURL ShapeStored By KoilEvent Family
Publish requestPOST /v1/publish-requestsLedger metadata onlypublish_request.*
Connected Profile-observed contentGET /v1/content/{contentType}?connectedProfileId={connectedProfileId}Nonecontent.*
Moderate contentPOST /v1/content/{contentType}/{externalId}/moderate?connectedProfileId={connectedProfileId}Nonecontent.*

Endpoints

Health

  • GET /healthz
  • GET /readyz

Provider Discovery

  • GET /v1/providers
  • GET /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/sessions
  • GET /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/connections
  • POST /v1/connections/import
  • GET /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-profiles
  • POST /v1/connected-profiles
  • GET /v1/connected-profiles/{connectedProfileId}
  • GET /v1/connected-profiles/{connectedProfileId}/capabilities
  • PATCH /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? }.
  • args is validated against that operation's inputSchema.
  • 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.
  • groupId and clientItemId are 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-Key is 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=1

providerRaw 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 and mediaId.
  • 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 mediaId for reliability and dedupe.
  • sourceUrl must be HTTPS and is fetched/staged by Koil before use.
  • Optional media metadata such as altText is 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-subscriptions
  • POST /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 use content.*.
  • 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. since expresses the desired oldest event timestamp; maxItems caps 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-destinations
  • POST /v1/event-destinations
  • GET /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 supports webhook; Outpost-backed transports such as sqs, pubsub, kafka, and kinesis may be added without changing Event Subscription shape.
  • config: transport-specific delivery config. Webhook destinations require url; custom headers are optional and must not override Koil or transport-reserved headers.
  • signatureMode: webhook signing mode; standard_webhooks is 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/usage
  • GET /v1/policies/providers/{providerId}/surfaces/{surface}
  • GET /v1/connected-profiles/{connectedProfileId}/policies
  • GET /v1/keys
  • POST /v1/keys
  • PATCH /v1/keys/{keyId}
  • DELETE /v1/keys/{keyId}

Public Errors

Errors use:

{
  "error": {
    "code": "validation_error",
    "message": "Human-readable summary",
    "meta": {}
  }
}

Stable public codes:

  • validation_error
  • unauthorized
  • invalid_token
  • not_found
  • conflict
  • scope_insufficient
  • rate_limit_exceeded
  • platform_rate_limit
  • not_supported
  • internal

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.uploaded
  • media.upload_failed
  • media.processing.completed
  • media.processing.failed
  • connect_session.completed
  • connect_session.failed
  • connected_profile.created
  • connected_profile.connection_failed
  • connected_profile.disconnected
  • event_subscription.backfill.completed
  • event_subscription.backfill.failed
  • connection.created
  • connection.expired
  • connection.refresh_failed
  • connection.invalidated
  • connection.scopes_insufficient
  • quota.exceeded
  • policy.violation
  • key.created
  • key.rotated
  • key.revoked
  • event_destination.disabled

Publish Events

publish_request.* events correspond to one pubreq_* ledger entry. Multi-item submissions produce independent event streams per item.

  • publish_request.created
  • publish_request.updated
  • publish_request.scheduled
  • publish_request.cancelled
  • publish_request.started
  • publish_request.succeeded
  • publish_request.failed
  • publish_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.created
  • content.updated
  • content.deleted
  • content.moderated
  • content.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 as lastSyncedAt, cursorDigest, callsConsumedToday, and reauthRequired.
  • Media objects: media_*, stored separately in R2 and referenced by mediaId.

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.

On this page