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

# Register an AI agent's own Hookie account

> Public: no cookie, no key. Creates, all or nothing: the agent's identity in WorkOS (a User Management user with a non-deliverable, verified address on agents.hookie.ai, external_id = the account id; no WorkOS organization), its Hookie user, a workspace on the **Free plan** with its Default project and the agent as owner, an admin API key, and a one-time claim token. The key and the claim token are in this response and nowhere else: Hookie stores only their hashes, never logs them, and never returns them again.

**Controls, in order:** a per-IP rate limit (an IPv6 address by its /64); the operator's kill switch; body validation including `accept_terms`; the proof of work (signed, unexpired, solved; harder as the day's cap fills); a platform-wide daily cap (default 200 a day, UTC) of which one network (an IPv4 address or an IPv6 /48) may take a tenth, both taken in the same statement that spends the challenge and before WorkOS is called. Free-plan quotas apply in full.

**Idempotent.** A challenge registers at most one account. Sending the same request again — the same challenge, or the same `Idempotency-Key` header with a new, unexpired and solved challenge — never creates a second account: it answers 409 `already_registered` with the `account_id` and NO credential (the credential is returned once; if it was lost, register again with a new challenge and a new Idempotency-Key). A key is scoped to the request: the same key with a different `agent_name` or `operator_contact` is a different registration. While the first request is still running the answer is 409 `registration_in_progress`; a request that died part-way is finished by a retry of the SAME request (its original challenge) after a minute, adopting the WorkOS user it may already have made — a key with a new challenge never finishes it. One never retried is abandoned after an hour: its WorkOS user is deleted and the retry gets `challenge_spent`. An attempt that failed (502) releases its key, so a new challenge with the same key registers.

**What the key may do.** Everything an admin can — projects, endpoints, rules, destinations, workflows, ingest keys, the rest — through /admin/api/*, the hosted MCP server at /mcp and the CLI. Billing, SSO and organization requests, invites, Google Workspace linking, platform administration, creating more API keys and revealing destination secrets answer 403 `agent_not_permitted` (with `claim_required: true`, except platform administration) until a person claims the workspace (and billing stays a person's in the console after that). Audited as `agent_registered`.



## OpenAPI

````yaml /openapi.json post /v1/agents/register
openapi: 3.1.0
info:
  title: Hookie API
  version: 1.0.0
  summary: Capture, route and deliver webhooks.
  description: >-
    Hookie exposes three HTTP surfaces.


    **Ingest + streaming** is the public plane. A path-form endpoint URL
    authenticates by its own high-entropy slug — there is no key, token or
    cookie — and an `ik_live_…` ingest key authenticates the `/v1/*` routes. An
    endpoint URL files each payload into the endpoint's own dataset through the
    endpoint's own criteria and mappings (none means the whole payload, as one
    record); `/v1/ingest/{ingest_key}/{dataset}` stores the payload whole into
    the named dataset. Project mapping rules are evaluated on exactly one shape,
    `POST /v1/ingest/{ingest_key}` with no dataset segment, and never on an
    endpoint URL.


    **The customer portal API** is a separate, token-authed surface for your end
    customers, scoped to one portal and one customer.


    **The admin API** drives the console. It accepts either a browser session or
    an OAuth bearer token from a connected coding agent — the same routes, the
    same validation, the same audit trail. An agent's authority is its user's
    workspace role narrowed to the scopes granted in the console. It also
    accepts an **admin API key** (`hk_…`, Settings → API keys) for CI,
    infrastructure-as-code and vendor backends: the key acts as the member who
    created it, capped at the key's role (viewer, developer or admin — never
    billing or platform administration), optionally limited to one project, with
    an optional expiry and IP allowlist. Keys are stored as a SHA-256 hash and
    shown once; if their creator leaves the workspace they stop working. The
    same key works on the hosted MCP server at `/mcp`.


    **A refused agent is told how to proceed.** An agent's grant is one rung of
    a ladder — `hookie:read` < `hookie:write` < `hookie:manage` — and a call
    above it answers 403 with `code: "agent_scope_insufficient"`, the
    `required_scope`, the `granted_scopes` and a `manage_url` to the console
    page where its user widens it. When the user's own role is the limit the
    code is `insufficient_role` and there is no link, because no grant can
    exceed the user; billing, platform admin, managing connected agents and
    destination signing secrets answer `agent_not_permitted` at every scope. A
    human in the console still reads `Insufficient role`.


    **A suspended workspace** (an operator action, for abuse or non-payment)
    answers every surface with 403 and `reason: "workspace_suspended"`: ingest
    (endpoint URLs and ingest keys), `/v1/data` and `/v1/stream` refuse before
    anything is stored or counted, and every admin API write — console, OAuth
    agent or hosted MCP alike — is refused except billing, platform admin,
    connected-agent management, revoking an API key, revoking an invite,
    removing a member or leaving, accepting an invite to another workspace,
    switching workspaces and the POST-bodied searches. The customer portal API
    is read-only in the same way: destination writes answer 403
    `workspace_suspended`. Reads keep working. Nothing is sent while it is
    suspended: pending deliveries are paused, workflow runs already under way
    are paused, open `/v1/stream` connections are closed, and cron triggers,
    database sources and WebSocket listeners stop. Reinstating sends the paused
    deliveries, resumes the paused workflow runs where they stopped, and resumes
    the rest; no stored data is lost.


    **Rate limits.** `/admin/api/*` and `/mcp` allow about 100 requests every 10
    seconds per credential — per API key, per connected agent, or per signed-in
    person. Over that, the answer is 429 with `Retry-After` (seconds). Like
    every burst limit here, ingest's included, it is a best-effort guard rather
    than an exact count: it is counted separately at each Cloudflare location
    and catches up within seconds, so a short burst can get through above it.
    The monthly event quota is the exact count.


    **Idempotency.** A `POST` to `/admin/api/*` may carry an `Idempotency-Key`
    header (1–255 printable characters, e.g. a UUID). The first request runs and
    its response is stored for 24 hours; a retry with the same key from the same
    credential, with the same path and body, gets that response back with
    `Idempotent-Replayed: true` and runs nothing. The same key with a different
    path, body or credential is refused with 422; a retry while the first
    request is still running gets 409. 5xx responses are not stored. `POST
    /admin/api/api-keys` refuses the header, because its response is a secret
    shown once and is never stored.


    **Versioning.** Every `/admin/api/*` and `/mcp` response carries
    `Hookie-Version` (currently `2026-09-29`). Additive changes — a new route, a
    new response field, a new optional parameter — ship under the current
    version, so clients must ignore fields they do not know. A breaking change
    gets a new dated version, announced in the changelog, and the previous
    version stays available for at least 12 months; a client pins one by sending
    `Hookie-Version` on its requests. A version this deployment does not serve
    is refused with 400 and the list of `supported_versions`.


    **Agent self-registration.** An AI agent can create its own account with no
    person involved (#331): `GET /v1/agents/register/challenge` returns a signed
    proof-of-work challenge, and `POST /v1/agents/register` with the solution,
    the agent's name, an optional operator contact and `accept_terms` set to the
    current Terms version creates a WorkOS identity for the agent, a workspace
    on the Free plan with its Default project, and an admin API key (`hk_…`,
    role admin) returned once, with a one-time claim token a person uses later
    to take ownership (`POST /admin/api/agent-accounts/claim`). The key works on
    `/admin/api/*`, on the hosted MCP server at `/mcp` and with the CLI
    (`HOOKIE_TOKEN`). Until a person claims the workspace, billing, SSO and
    organization requests, invites, Google Workspace linking and creating more
    API keys are refused to it (`agent_not_permitted`), and Free-plan quotas
    apply in full. The registration is rate limited per IP (an IPv6 address by
    its /64), capped per day across the platform with no one network taking more
    than a tenth of the cap, asks for more proof of work as the day's cap fills,
    and can be switched off by the operator.


    This document is generated from the request handlers and adversarially
    verified against them.
  contact:
    name: Hookie
    url: https://hookie.ai
  license:
    name: Proprietary
    url: https://hookie.ai/legal/terms
servers:
  - url: https://app.hookie.ai
    description: Production
  - url: https://app.preview.hookie.ai
    description: Preview
security: []
tags:
  - name: Ingest
    description: Send events to Hookie.
  - name: Streaming
    description: Tail events in real time over SSE or WebSocket.
  - name: Portal
    description: Token-authed surface for your end customers.
  - name: Projects
    description: Projects and their settings.
  - name: Routing
    description: Rules, endpoints, datasets and records.
  - name: Delivery
    description: Destinations, deliveries and replay.
  - name: Workflows
    description: Multi-step workflows, triggers and AI agents.
  - name: Observability
    description: Search, correlation, stats and the audit log.
  - name: Workspace
    description: >-
      The workspace itself: its name and slug, its members and their roles,
      shareable-link invites, ownership transfer, the switcher between the
      workspaces a person belongs to, admin API keys, data export and closure.
  - name: Data API
    description: >-
      Read-only access to the datasets and columns a project exposes, with
      per-project keys.
  - name: Broadcast Logs
    description: >-
      Forward a project's logs and traces over OTLP/HTTP (JSON) to Datadog,
      Grafana Cloud, an OpenTelemetry Collector, PostHog or Sentry.
  - name: Status
    description: >-
      Whether the API can answer at all: the public health check that
      hookie.ai/status reads.
  - name: Agents
    description: >-
      Agent self-registration (#331): an AI agent creates its own Hookie
      account, with no person involved, and gets an admin API key for the admin
      API, the hosted MCP server and the CLI. The account starts on the Free
      plan; a person can later claim it with the one-time claim token. See
      docs.hookie.ai → Agents and the design note
      docs/design/agent-self-registration.md.
paths:
  /v1/agents/register:
    post:
      tags:
        - Agents
      summary: Register an AI agent's own Hookie account
      description: >-
        Public: no cookie, no key. Creates, all or nothing: the agent's identity
        in WorkOS (a User Management user with a non-deliverable, verified
        address on agents.hookie.ai, external_id = the account id; no WorkOS
        organization), its Hookie user, a workspace on the **Free plan** with
        its Default project and the agent as owner, an admin API key, and a
        one-time claim token. The key and the claim token are in this response
        and nowhere else: Hookie stores only their hashes, never logs them, and
        never returns them again.


        **Controls, in order:** a per-IP rate limit (an IPv6 address by its
        /64); the operator's kill switch; body validation including
        `accept_terms`; the proof of work (signed, unexpired, solved; harder as
        the day's cap fills); a platform-wide daily cap (default 200 a day, UTC)
        of which one network (an IPv4 address or an IPv6 /48) may take a tenth,
        both taken in the same statement that spends the challenge and before
        WorkOS is called. Free-plan quotas apply in full.


        **Idempotent.** A challenge registers at most one account. Sending the
        same request again — the same challenge, or the same `Idempotency-Key`
        header with a new, unexpired and solved challenge — never creates a
        second account: it answers 409 `already_registered` with the
        `account_id` and NO credential (the credential is returned once; if it
        was lost, register again with a new challenge and a new
        Idempotency-Key). A key is scoped to the request: the same key with a
        different `agent_name` or `operator_contact` is a different
        registration. While the first request is still running the answer is 409
        `registration_in_progress`; a request that died part-way is finished by
        a retry of the SAME request (its original challenge) after a minute,
        adopting the WorkOS user it may already have made — a key with a new
        challenge never finishes it. One never retried is abandoned after an
        hour: its WorkOS user is deleted and the retry gets `challenge_spent`.
        An attempt that failed (502) releases its key, so a new challenge with
        the same key registers.


        **What the key may do.** Everything an admin can — projects, endpoints,
        rules, destinations, workflows, ingest keys, the rest — through
        /admin/api/*, the hosted MCP server at /mcp and the CLI. Billing, SSO
        and organization requests, invites, Google Workspace linking, platform
        administration, creating more API keys and revealing destination secrets
        answer 403 `agent_not_permitted` (with `claim_required: true`, except
        platform administration) until a person claims the workspace (and
        billing stays a person's in the console after that). Audited as
        `agent_registered`.
      operationId: registerAgent
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: >-
            Optional, 1–255 printable characters. A retry carrying the same key
            (with the same `agent_name` and `operator_contact`, and a live
            solved challenge) is answered 409 with the account it created, never
            a second account. A key whose attempt failed is released. Use a
            random value such as a UUID.
          schema:
            type: string
            maxLength: 255
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - agent_name
                - accept_terms
                - pow
              properties:
                agent_name:
                  type: string
                  minLength: 1
                  maxLength: 80
                  description: >-
                    What the agent calls itself. Printable characters only.
                    Names the workspace (`<agent_name>'s workspace`).
                operator_contact:
                  type:
                    - string
                    - 'null'
                  maxLength: 254
                  description: >-
                    Optional: the email address or https URL of the person or
                    organization responsible for the agent. Stored on the
                    account and shown to the operator.
                accept_terms:
                  type: string
                  description: >-
                    Must equal the current Terms of Use version, `2026-10-01`
                    (also in the challenge response as `terms_version`). By
                    sending it the agent accepts the Terms at
                    https://hookie.ai/legal/terms for the person or organization
                    it acts for; the version is recorded on the account.
                pow:
                  type: object
                  required:
                    - challenge
                    - nonce
                  properties:
                    challenge:
                      type: string
                      description: From GET /v1/agents/register/challenge, unchanged.
                    nonce:
                      type: string
                      pattern: ^[0-9]{1,32}$
                      description: The solution.
      responses:
        '201':
          description: >-
            Registered. Store `api_key.key` and `claim.token` now: they are
            never shown again.
          content:
            application/json:
              schema:
                type: object
                required:
                  - account_id
                  - agent_name
                  - user_id
                  - workspace
                  - project
                  - api_key
                  - claim
                  - terms_version
                  - endpoints
                  - notice
                properties:
                  account_id:
                    type: string
                    description: '`agt_…`. The agent account.'
                  agent_name:
                    type: string
                  user_id:
                    type: string
                    description: The agent's WorkOS user id, also its Hookie user id.
                  workspace:
                    type: object
                    required:
                      - id
                      - name
                      - slug
                      - plan
                    properties:
                      id:
                        type: string
                      name:
                        type: string
                      slug:
                        type: string
                      plan:
                        type: string
                        enum:
                          - free
                  project:
                    type: object
                    required:
                      - id
                      - name
                      - slug
                    properties:
                      id:
                        type: string
                      name:
                        type: string
                      slug:
                        type: string
                  api_key:
                    type: object
                    required:
                      - id
                      - key
                      - key_prefix
                      - role
                    description: >-
                      The agent's credential: an admin API key, shown here ONCE
                      and stored only as its SHA-256. Send it as `Authorization:
                      Bearer <key>` to /admin/api/* and /mcp, or set
                      HOOKIE_TOKEN for the CLI. Listed under Settings → API keys
                      as "Agent registration key".
                    properties:
                      id:
                        type: string
                      key:
                        type: string
                        description: '`hk_…`'
                      key_prefix:
                        type: string
                      role:
                        type: string
                        enum:
                          - admin
                  claim:
                    type: object
                    required:
                      - token
                      - url
                      - note
                    description: >-
                      A one-time token for the person responsible for the agent,
                      shown here ONCE and stored only as its SHA-256. Signed in
                      to Hookie, they open `url` (or POST the token to
                      /admin/api/agent-accounts/claim) to become the workspace's
                      owner — which they need to upgrade it.
                    properties:
                      token:
                        type: string
                        description: '`hkc_…`'
                      url:
                        type: string
                        format: uri
                      note:
                        type: string
                  terms_version:
                    type: string
                  endpoints:
                    type: object
                    properties:
                      api:
                        type: string
                        format: uri
                      mcp:
                        type: string
                        format: uri
                      openapi:
                        type: string
                        format: uri
                      docs:
                        type: string
                        format: uri
                  usage:
                    type: string
                  notice:
                    type: string
        '400':
          description: >-
            The body is wrong (`invalid_body`, `invalid_agent_name`,
            `invalid_operator_contact`), the Terms version is missing or old
            (`terms_not_accepted`, with the current `terms_version`), the proof
            of work is missing, forged, expired or wrong
            (`proof_of_work_required`, `invalid_challenge`, `challenge_expired`,
            `invalid_proof_of_work`), or the Idempotency-Key is malformed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          description: Only POST.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: >-
            A retry: `already_registered` (with `account_id` and `workspace_id`,
            no credential), `registration_in_progress` (retry the original
            request, with its original challenge, after `retry_after`), or
            `challenge_spent` (an earlier attempt with this challenge failed;
            get a new one).
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/Error'
                  - type: object
                    properties:
                      account_id:
                        type: string
                      workspace_id:
                        type:
                          - string
                          - 'null'
        '413':
          description: The body is over 4 KB.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: >-
            Too many requests from this address (`reason: rate_limited`, the
            AGENT_RL limiter: about 5 a minute per IP, an IPv6 address counted
            by its /64, best-effort and counted per Cloudflare location), or, on
            POST, today's platform-wide registration cap is reached (`reason:
            daily_cap_reached`) or this network (an IPv4 address or an IPv6 /48)
            has made its share of it, a tenth (`reason: source_daily_limit`);
            both retry after midnight UTC.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers:
            Retry-After:
              description: >-
                Seconds to wait before retrying. Same value as retry_after in
                the body.
              schema:
                type: integer
        '502':
          description: >-
            `identity_provider_error`: WorkOS could not create the identity.
            Nothing was registered and the challenge is spent; get a new one and
            try again.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          description: >-
            `agent_registration_disabled`: the operator has switched agent
            self-registration off. `agent_registration_unavailable`: this
            environment has no WorkOS API key, so the agent's identity cannot be
            made. On POST also `registration_incomplete`: retry the SAME request
            after `retry_after` seconds; it finishes the registration rather
            than making another.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security: []
components:
  schemas:
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: string
          description: Human-readable reason.
        retry_after:
          type: integer
          description: Seconds to wait. Present on 429.
        upgrade_url:
          type: string
          format: uri
          description: >-
            Present on a monthly-quota 429, and on a `member_limit_reached` 403
            (the console's Settings → Plan & billing).
        reason:
          type: string
          description: >-
            Machine-readable reason, where the status alone is ambiguous.
            `workspace_suspended` (403): the workspace is suspended;
            `delivery_allowance_exhausted` (429): a replay was refused because
            the monthly delivery allowance is used. `endpoint_disabled` (503):
            the endpoint is switched off; `handshake_failed`: a provider
            URL-verification challenge could not be answered (#193).
            `missing_header`, `bad_signature`, `stale_timestamp` (401): an
            endpoint's provider signature check failed; `scheme` names the
            check. `portal_customer_mismatch` (409): a portal token was
            requested for, or the vendor switched on the endpoint of, a customer
            other than the one the portal now serves (PORT-2g); `customer_id`
            and `customer_name` name that customer. Agent self-registration
            (#331), on /v1/agents/register: `rate_limited` (429),
            `agent_registration_disabled` and `agent_registration_unavailable`
            (503), `invalid_body`, `invalid_agent_name`,
            `invalid_operator_contact`, `terms_not_accepted` (with
            `terms_version`), `proof_of_work_required`, `invalid_challenge`,
            `challenge_expired`, `invalid_proof_of_work`,
            `invalid_idempotency_key` (400), `body_too_large` (413),
            `already_registered`, `registration_in_progress`, `challenge_spent`
            (409, with `account_id`), `daily_cap_reached`, `source_daily_limit`
            (429), `identity_provider_error` (502), `registration_incomplete`
            (503).
        code:
          type: string
          enum:
            - agent_scope_insufficient
            - insufficient_role
            - agent_not_permitted
            - member_limit_reached
          description: >-
            Present on a 403 to an OAuth-connected agent, and on a member-limit
            403 to anyone. `agent_scope_insufficient`: the agent's grant is
            below what the call needs, and widening it would let the call
            through (see `required_scope` and `manage_url`).
            `insufficient_role`: the user's OWN workspace role does not allow
            the call, so no grant can — a workspace owner or admin has to change
            the user's role first, in the console under Settings → Members.
            `agent_not_permitted`: no connected agent or API key may do this at
            any scope (billing, platform admin, managing connected agents, a
            destination's signing secret, invites of any kind, changing a role,
            removing a member, transferring ownership, listing or switching a
            person's workspaces, and creating API keys). To a self-registered
            agent account nobody has claimed yet (#331) it also carries
            `claim_required: true`: a person must claim the workspace with the
            claim link from registration before anyone can do it.
            `member_limit_reached` (#308): the workspace already has as many
            members as its plan allows, so an invite cannot be created or
            accepted; `max_members`, `members` and `upgrade_url` say how many
            and where to upgrade.
        required_scope:
          type: string
          enum:
            - hookie:read
            - hookie:write
            - hookie:manage
          description: 'With `code`: the scope the refused call needs.'
        granted_scopes:
          type: array
          items:
            type: string
          description: 'With `code`: the scopes the agent''s grant holds now.'
        manage_url:
          type: string
          format: uri
          description: >-
            With `code: "agent_scope_insufficient"` only: the console's Settings
            → Connected agents page, opened on this agent
            (`…/#/settings/agents?client=<client_id>`), where its user widens
            the grant. The change applies on the agent's next request.
        scheme:
          type: string
          description: 'Present on a signature 401: the endpoint''s verification scheme.'
        max_members:
          type: integer
          description: 'With `code: "member_limit_reached"`: the plan''s member limit.'
        members:
          type: integer
          description: 'With `code: "member_limit_reached"`: the workspace''s members now.'
        customer_id:
          type:
            - string
            - 'null'
          description: >-
            On a `portal_customer_mismatch` 409: the customer the portal already
            serves.
        customer_name:
          type:
            - string
            - 'null'
          description: >-
            On a `portal_customer_mismatch` 409: that customer's display name,
            if one was given.
        account_id:
          type: string
          description: >-
            On a 409 from POST /v1/agents/register: the agent account the
            earlier request created (or is creating). Never accompanied by a
            credential.
        terms_version:
          type: string
          description: >-
            On a `terms_not_accepted` 400: the Terms version `accept_terms` must
            equal.
        claim_required:
          type: boolean
          description: >-
            With `code: "agent_not_permitted"`, to a self-registered agent
            account nobody has claimed (#331): the refused action needs a
            person, who first claims the workspace with the claim link from the
            registration response (`claim.url`).

````

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