> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ergoly.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a connection

> Two shapes, told apart by `app`. WITHOUT it, this stores a secret: `value` is write-only, it goes in here and no route gives it back; give at least one host unless the key already has some — a credential no host points at is never injected by the gateway, so it would sit there doing nothing; a key already taken at the same scope is a `conflict`, as is a host already mapped to another connection. WITH `app` — a catalog slug or a tool server's URL — this connects an app and places it: omit `auth` and the answer carries `connectUrl`, a hosted sign-in to open in a browser, with the connection `pending` until consent finishes and `active` after (poll `GET /api/v1/connections/{id}`); send `auth.mode: "key"` with the key in `value` and it is stored at Composio and checked with one real call, `verified` reporting the verdict; a tool server that takes a key names the header it goes in with `auth.header` (absent is `Authorization`, sent as `Bearer <key>`; any other header carries the key on its own); a tool server that takes no credential is `auth.mode: "none"`, and gets a row and a placement with nothing stored anywhere. A `placement` no ework could read — `instance` on a Worker with no active ework for that Client, `client` for a Client no Worker has one for — is a `conflict` naming the pair, refused before a key or a sign-in reaches Composio. The check is made once, up front: an ework removed while Composio is being called can still leave the placement in place, and adding or moving a placement later is checked again at the write. `connectUrl` is shown once — a replay under the same `Idempotency-Key` withholds it — and the pasted key is never in any response. `sessions` says which eworks can use the app now.



## OpenAPI

````yaml /openapi.json post /api/v1/connections
openapi: 3.1.0
info:
  title: Ergoly platform API
  version: 1.0.0
  description: >-
    Read and change one organization's own Ergoly environment. Every route is
    scoped to the organization the presented credential resolves to; there is no
    parameter that widens it. The human-readable reference is served from the
    same namespace at `/api/v1/docs`, behind the same credential.


    The namespace's front door is `GET /api/v1`, and it is deliberately absent
    from `paths` below. It answers without a credential — what this API is, how
    an organization admin mints a key, and where the reference lives — so it is
    the way in to this contract rather than a part of it: it has no scope, takes
    no parameters, and there is nothing about it for a generated client to call.
    Every `unauthorized` refusal carries that same URL as `error.docs`.
servers:
  - url: https://control.ergoly.ai
security:
  - orgApiKey: []
tags:
  - name: auth
    description: How to present a key, what it is scoped to, and what a 401 means.
  - name: organization
    description: Which organization your credential resolves to.
  - name: workers
    description: The Slack apps and bot identities in your organization.
  - name: eworks
    description: The runtime instances under a Worker — one core, one per bound Client.
  - name: clients
    description: The customers a Worker can be scoped to, and how to bind one.
  - name: channels
    description: Which Slack rooms a Worker answers in, and who it answers as.
  - name: tasks
    description: >-
      Tasks handed between Workers and people, scheduled jobs, their targets,
      and their run history.
  - name: jobs
    description: How a provisioning run is going — the other half of every 202.
  - name: memory
    description: Read and write the memory tiers an ework mounts.
  - name: connections
    description: The credentials your agents use, and the hosts they are injected on.
  - name: webhooks
    description: Inbound URLs an outside system can POST to, each of which starts a turn.
  - name: activity
    description: Your organization's audit feed.
  - name: usage
    description: Cost, turn outcomes and a per-day pulse over a window you choose.
  - name: errors
    description: The closed set of refusal codes, and what to do about each.
paths:
  /api/v1/connections:
    post:
      tags:
        - connections
      summary: Create a connection
      description: >-
        Two shapes, told apart by `app`. WITHOUT it, this stores a secret:
        `value` is write-only, it goes in here and no route gives it back; give
        at least one host unless the key already has some — a credential no host
        points at is never injected by the gateway, so it would sit there doing
        nothing; a key already taken at the same scope is a `conflict`, as is a
        host already mapped to another connection. WITH `app` — a catalog slug
        or a tool server's URL — this connects an app and places it: omit `auth`
        and the answer carries `connectUrl`, a hosted sign-in to open in a
        browser, with the connection `pending` until consent finishes and
        `active` after (poll `GET /api/v1/connections/{id}`); send `auth.mode:
        "key"` with the key in `value` and it is stored at Composio and checked
        with one real call, `verified` reporting the verdict; a tool server that
        takes a key names the header it goes in with `auth.header` (absent is
        `Authorization`, sent as `Bearer <key>`; any other header carries the
        key on its own); a tool server that takes no credential is `auth.mode:
        "none"`, and gets a row and a placement with nothing stored anywhere. A
        `placement` no ework could read — `instance` on a Worker with no active
        ework for that Client, `client` for a Client no Worker has one for — is
        a `conflict` naming the pair, refused before a key or a sign-in reaches
        Composio. The check is made once, up front: an ework removed while
        Composio is being called can still leave the placement in place, and
        adding or moving a placement later is checked again at the write.
        `connectUrl` is shown once — a replay under the same `Idempotency-Key`
        withholds it — and the pasted key is never in any response. `sessions`
        says which eworks can use the app now.
      operationId: createConnection
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: >-
            Any unique string. A retry under the same key is answered from the
            first attempt's record, with `X-Idempotent-Replay: true`. The same
            key with a different body is a conflict.
          schema:
            type: string
            description: Any unique string.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                app:
                  type: string
                  description: >-
                    CONNECT AN APP. A catalog slug (`hubspot`, `exa`) or a tool
                    server's `https://` URL. Present, the body is the app shape
                    — `placement` and `name` are required, `auth` and
                    `discoveryUrl` may follow, and nothing from the secret shape
                    below may appear beside them. Absent, the body is the secret
                    shape and `key`, `kind` and `value` are required.
                placement:
                  type: object
                  description: >-
                    Where the connected app may be used. Required with `app`.
                    More placements can be added afterwards.
                  properties:
                    tier:
                      type: string
                      enum:
                        - org
                        - worker
                        - client
                        - instance
                      description: >-
                        `org` is the whole company; `worker` one Worker;
                        `client` one Client on every Worker serving it;
                        `instance` one Worker for one Client.
                    workerId:
                      type: string
                      description: Required for `worker` and `instance`.
                    clientId:
                      type: string
                      description: Required for `client` and `instance`.
                  required:
                    - tier
                  additionalProperties: false
                name:
                  type: string
                  description: >-
                    What to call the connected app. Required with `app`; unique
                    in the organization.
                auth:
                  type: object
                  description: >-
                    How the person authenticates, with `app`. Omitted means a
                    hosted sign-in.
                  properties:
                    mode:
                      type: string
                      enum:
                        - key
                        - oauth
                        - none
                      description: >-
                        `oauth` (the default): a hosted sign-in, answered as
                        `connectUrl`. `key`: a key or token the caller holds, in
                        `value`. `none`: a tool server that takes no credential.
                    value:
                      type: string
                      description: >-
                        The key or token, with `mode: "key"`. WRITE-ONLY, like
                        every value on this API.
                    fields:
                      type: object
                      additionalProperties: true
                      description: >-
                        The app's other sign-in fields beside the key (a
                        subdomain), with `mode: "key"`. Never the key itself.
                  required:
                    - mode
                  additionalProperties: false
                discoveryUrl:
                  type: string
                  description: >-
                    With an `app` that is a tool server URL: its authorization
                    server's metadata URL, when you know it. Omitted, Ergoly
                    asks the server.
                key:
                  type: string
                  description: >-
                    STORE A SECRET. The slug the agent and the gateway will know
                    it by. Lowercase letters, digits, `_` and `-`. Unique per
                    scope. Required without `app`.
                kind:
                  type: string
                  enum:
                    - api_key
                    - secret
                    - basic
                  description: >-
                    How the gateway injects it. Required without `app`. `oauth`
                    is not accepted here — an OAuth account is made by the
                    connect flow, which is the only path that can revoke it
                    later.
                value:
                  type: string
                  description: >-
                    The secret itself. Required without `app`. WRITE-ONLY: no
                    response on this API returns it, and there is no route that
                    reads it back.
                scope:
                  type: string
                  enum:
                    - org
                    - worker
                    - client
                  description: Which identities resolve it. Defaults to `org`.
                workerId:
                  type: string
                  description: Required when `scope` is `worker`.
                clientId:
                  type: string
                  description: Required when `scope` is `client`.
                internalOnly:
                  type: boolean
                  description: >-
                    Hide it from client-tier eworks. Cannot be combined with a
                    client scope — it would resolve for no one.
                hosts:
                  type: array
                  items:
                    type: string
                    description: A bare hostname — no scheme, path, port or wildcard.
                  description: >-
                    The hosts the gateway injects this credential on. At least
                    one is required unless the key already has hosts: a
                    credential no host points at is never injected.
              required: []
              additionalProperties: false
            example:
              key: stripe_live
              kind: api_key
              value: sk_live_51H8xY2eZvKYlo2C
              scope: org
              hosts:
                - api.stripe.com
      responses:
        '201':
          description: Success.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    oneOf:
                      - $ref: '#/components/schemas/Connection'
                      - $ref: '#/components/schemas/CreatedAppConnection'
                required:
                  - data
                additionalProperties: false
              example:
                data:
                  id: 6d5c4b3a-2918-4706-9f8e-1d2c3b4a5968
                  key: stripe_live
                  kind: api_key
                  type: secret
                  scope: org
                  workerId: null
                  clientId: null
                  internalOnly: false
                  managed: false
                  hosts:
                    - api.stripe.com
                  description: Billing — subscriptions, invoices and refunds.
                  provider: null
                  account: null
                  providerScopes: []
                  expiresAt: null
                  health: null
                  issuer: null
                  issuerHost: null
                  mcpEndpoint: null
                  mcpTransport: null
                  mcpTools: []
                  mcpStatus: null
                  mcpServerName: null
                  providerClientId: null
                  redirectUri: null
                  createdAt: '2026-02-01T09:14:00.000Z'
                  updatedAt: '2026-02-01T09:14:00.000Z'
        '400':
          description: '`invalid_request`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: '`unauthorized`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: '`not_found`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: '`conflict`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: '`rate_limited`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '502':
          description: '`upstream_error`'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - orgApiKey:
            - connections:write
components:
  schemas:
    Connection:
      type: object
      properties:
        id:
          type: string
          description: The connection's id.
        key:
          type: string
          description: The slug the agent and the egress gateway know it by.
        kind:
          type: string
          description: '`api_key`, `secret`, `basic`, `oauth` or `oauth-app`.'
        type:
          type: string
          enum:
            - secret
            - oauth
            - provider-app
            - mcp
          description: >-
            Which family this is. Decides which of the state fields below are
            set.
        scope:
          type: string
          enum:
            - org
            - worker
            - client
          description: >-
            Which identities resolve it. Most specific wins: client, then
            worker, then org.
        workerId:
          type:
            - string
            - 'null'
          description: The Worker it is scoped to, when `scope` is `worker`.
        clientId:
          type:
            - string
            - 'null'
          description: The Client it is scoped to, when `scope` is `client`.
        internalOnly:
          type: boolean
          description: >-
            Hidden from client-tier eworks. Never true on a client-scoped
            connection.
        managed:
          type: boolean
          description: System-managed. Readable, never modifiable.
        hosts:
          type: array
          items:
            type: string
            description: A hostname.
          description: >-
            Hostnames the egress gateway injects this credential on. A
            connection with no host is never injected.
        description:
          type:
            - string
            - 'null'
          description: >-
            What this connection is for, in an operator's own words. Carried to
            a worker in the per-turn connections index, where it is the only
            thing that can say a connection named `lemlist` is how LinkedIn
            outreach gets done. Null when nobody wrote one.
        provider:
          type:
            - string
            - 'null'
          description: Provider id, for an OAuth account, provider app or MCP server.
        account:
          type:
            - string
            - 'null'
          description: The connected account's label, as the provider reported it.
        providerScopes:
          type: array
          items:
            type: string
            description: A scope.
          description: OAuth scopes the provider granted.
        expiresAt:
          type:
            - string
            - 'null'
          format: date-time
          description: When the access token expires, if the provider said.
        health:
          type:
            - string
            - 'null'
          description: '`healthy`, `expiring`, `expired` or `unknown`.'
        issuer:
          type:
            - string
            - 'null'
          description: An MCP server's canonical identity.
        issuerHost:
          type:
            - string
            - 'null'
          description: That issuer's host — what an operator recognises.
        mcpEndpoint:
          type:
            - string
            - 'null'
          description: Where the agent POSTs MCP calls.
        mcpTransport:
          type:
            - string
            - 'null'
          description: The observed MCP transport, or `unknown`.
        mcpTools:
          type: array
          items:
            type: string
            description: A tool name.
          description: Tools advertised at the last handshake.
        mcpStatus:
          type:
            - string
            - 'null'
          description: >-
            `ok`, `failed`, `pending`, or `needs_reconnect` — the last meaning
            no endpoint was ever recorded, so it cannot work until it is
            connected again.
        mcpServerName:
          type:
            - string
            - 'null'
          description: The server's own product name, when it supplied one.
        providerClientId:
          type:
            - string
            - 'null'
          description: >-
            A provider app's PUBLIC OAuth client id. Its client secret has no
            field here.
        redirectUri:
          type:
            - string
            - 'null'
          description: The redirect URI Ergoly derives for this provider.
        createdAt:
          type: string
          format: date-time
          description: When it was created.
        updatedAt:
          type: string
          format: date-time
          description: When it last changed.
      required:
        - id
        - key
        - kind
        - type
        - scope
        - workerId
        - clientId
        - internalOnly
        - managed
        - hosts
        - description
        - provider
        - account
        - providerScopes
        - expiresAt
        - health
        - issuer
        - issuerHost
        - mcpEndpoint
        - mcpTransport
        - mcpTools
        - mcpStatus
        - mcpServerName
        - providerClientId
        - redirectUri
        - createdAt
        - updatedAt
      additionalProperties: false
    CreatedAppConnection:
      type: object
      properties:
        id:
          type: string
          description: The connection's id.
        app:
          type:
            - string
            - 'null'
          description: >-
            The app's slug (`hubspot`); for a tool server, the slug it was
            registered under.
        kind:
          type: string
          enum:
            - composio
            - key
          description: >-
            `composio` — the credential is held at Composio; `key` — a raw key
            the egress gateway injects on `baseUrl`'s host.
        baseUrl:
          type:
            - string
            - 'null'
          description: A `key` connection's base URL. Null on a `composio` one.
        name:
          type: string
          description: What the organization calls it. Unique per kind.
        description:
          type:
            - string
            - 'null'
          description: What it is for, in an operator's own words.
        status:
          type: string
          enum:
            - pending
            - active
            - expired
            - failed
            - revoked
          description: >-
            `pending` until a hosted sign-in finishes; `active` once the
            credential works.
        managed:
          type: boolean
          description: Provided by Ergoly. Readable; every change is refused.
        placements:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
                description: The placement's id. What `DELETE …/placements/{pid}` takes.
              tier:
                type: string
                enum:
                  - org
                  - worker
                  - client
                  - instance
                description: >-
                  `org` is the whole company; `worker` one Worker; `client` one
                  Client, on every Worker serving it; `instance` one Worker for
                  one Client.
              workerId:
                type:
                  - string
                  - 'null'
                description: The Worker, on a `worker` or `instance` placement.
              clientId:
                type:
                  - string
                  - 'null'
                description: The Client, on a `client` or `instance` placement.
              workerName:
                type:
                  - string
                  - 'null'
                description: >-
                  That Worker's display name, when the caller supplied a
                  directory.
              clientName:
                type:
                  - string
                  - 'null'
                description: That Client's name, when the caller supplied a directory.
            required:
              - id
              - tier
              - workerId
              - clientId
              - workerName
              - clientName
            additionalProperties: false
          description: Where it may be used. A connection with none is used by nobody.
        details:
          type: object
          description: >-
            What is known about the account behind the connection, as last
            checked. Never a credential.
          properties:
            authKind:
              type: string
              enum:
                - managed_sign_in
                - own_app_sign_in
                - key
                - dcr
                - no_auth
                - unknown
              description: >-
                How it authenticates: a hosted sign-in on Composio's or Ergoly's
                app; a sign-in on the organization's own OAuth app; a pasted
                key; a sign-in a tool server registered dynamically; no
                credential at all; or not checked yet.
            authKindLabel:
              type: string
              description: '`authKind`, in words.'
            identity:
              type:
                - object
                - 'null'
              description: >-
                The account that signed in, where the app reported one. Null
                when it did not.
              properties:
                kind:
                  type: string
                  enum:
                    - email
                    - login
                    - name
                    - workspace
                    - account
                  description: What kind of identifier `label` is.
                label:
                  type: string
                  description: Who signed in, as the app reported it.
              required:
                - kind
                - label
              additionalProperties: false
            scopes:
              type: array
              items:
                type: string
                description: A scope, as the app spells it.
              description: What the app granted, where the record carried it.
            composioStatus:
              type:
                - string
                - 'null'
              description: >-
                Composio's own status word for the account (`ACTIVE`, `EXPIRED`,
                …).
            statusReason:
              type:
                - string
                - 'null'
              description: Why the status last changed, when Composio said.
            expiresAt:
              type:
                - string
                - 'null'
              format: date-time
              description: When the credential expires or expired, when the app said.
            checkedAt:
              type:
                - string
                - 'null'
              format: date-time
              description: When these facts were last checked. Null when never.
            logo:
              type:
                - string
                - 'null'
              description: The app's brand mark.
            appName:
              type:
                - string
                - 'null'
              description: The app's own name in the catalog.
            category:
              type: string
              description: >-
                The one heading it is grouped under: what Composio files the app
                under, `MCP` for a tool server the organization registered,
                `Other` when nothing is known.
            check:
              type:
                - object
                - 'null'
              description: >-
                What the entry check of a pasted key found. Null when there was
                no key to check (a sign-in, a tool server that takes none) or
                the connection predates the record.
              properties:
                verified:
                  type: boolean
                  description: >-
                    True only when one real call proved the key: the app
                    accepted a test request with it and refused the same request
                    without it.
                reason:
                  type: string
                  enum:
                    - key_proven
                    - no_check_available
                    - endpoint_accepts_anything
                    - test_endpoint_missing
                    - check_got_no_answer
                    - key_rejected
                    - test_request_forbidden
                    - request_refused
                  description: >-
                    What the entry check got. `key_proven` is the only reason
                    behind `verified: true`. `no_check_available`: no test
                    exists for this kind of connection (every tool server's
                    key). `endpoint_accepts_anything`: the app answers the same
                    with and without the key. `test_endpoint_missing`: the app
                    does not serve the address the test went to, a fault in the
                    platform's test. `check_got_no_answer`: a 5xx, a rate limit
                    or no answer at all. `test_request_forbidden`: the app knows
                    the key but refused the test request (403), often because
                    the test asks for more access than the key has; the key is
                    saved unconfirmed. None of these is a rejected key — a
                    rejected (`key_rejected`) or refused (`request_refused`) key
                    fails the create and stores nothing, so neither appears on a
                    listed connection.
                at:
                  type: string
                  format: date-time
                  description: When the check ran.
              required:
                - verified
                - reason
                - at
              additionalProperties: false
          required:
            - authKind
            - authKindLabel
            - identity
            - scopes
            - composioStatus
            - statusReason
            - expiresAt
            - checkedAt
            - logo
            - appName
            - category
            - check
          additionalProperties: false
        createdAt:
          type: string
          format: date-time
          description: When it was created.
        lastUsedAt:
          type:
            - string
            - 'null'
          format: date-time
          description: When a tool last ran through it. Null until one has.
        connectUrl:
          type: string
          description: >-
            The hosted sign-in to open in a browser, on an OAuth create. SHOWN
            ONCE: a replay under the same Idempotency-Key withholds it and names
            it in `secretsWithheld`.
        verified:
          type: boolean
          description: >-
            On a pasted key: whether one real call proved it. `false` means it
            was stored and could not be checked, not that it is wrong.
        sessions:
          type: array
          items:
            type: object
            properties:
              eworkId:
                type: string
                description: The ework whose session this row describes.
              outcome:
                type: string
                enum:
                  - created
                  - patched
                  - unchanged
                  - deleted
                  - none
                  - no_project
                  - not_configured
                  - not_live
                  - failed
                description: >-
                  What happened to its session. `none` means nothing there
                  needed touching; `failed` carries `error`.
              gained:
                type: array
                items:
                  type: string
                  description: A connection id.
                description: Connections it can use now that it could not before.
              lost:
                type: array
                items:
                  type: string
                  description: A connection id.
                description: Connections it could use before and cannot now.
              sessionId:
                type:
                  - string
                  - 'null'
                description: Its session at Composio, when it has one.
              error:
                type: string
                description: Why, when `outcome` is `failed`.
            required:
              - eworkId
              - outcome
              - gained
              - lost
              - sessionId
            additionalProperties: false
          description: One report per ework the placement reached.
      required:
        - id
        - app
        - kind
        - baseUrl
        - name
        - description
        - status
        - managed
        - placements
        - details
        - createdAt
        - lastUsedAt
        - sessions
      additionalProperties: false
    Error:
      type: object
      description: The one error shape. Every refusal in this namespace has it.
      properties:
        error:
          type: object
          description: The refusal.
          properties:
            code:
              type: string
              enum:
                - unauthorized
                - not_found
                - invalid_request
                - conflict
                - quota_exceeded
                - rate_limited
                - upstream_error
                - gone
              description: The machine-stable half. Branch on this, never on `message`.
            message:
              type: string
              description: Prose for a human reading a terminal.
            docs:
              type: string
              description: >-
                Where to learn how to authenticate. Present on `unauthorized`
                and on nothing else — it is the one refusal a caller can reach
                with no credential at all, so it is the one that has to say
                where the way in is.
          required:
            - code
            - message
          additionalProperties: false
      required:
        - error
      additionalProperties: false
  securitySchemes:
    orgApiKey:
      type: http
      scheme: bearer
      description: >-
        An organization-scoped API key, minted in control under Settings → API
        keys. Every failure to authenticate answers one identical 401: "Provide
        a valid organization-scoped API key or an active session." Each
        operation names the one scope it is filed under,
        `<resource>:<read|write>`, where the resource is the first path segment
        after `/api/v1/`; a key minted today carries all 22 of them. Scopes are
        RECORDED — on the key and on every audit row — and are not yet enforced:
        no request is refused for want of one.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.