Primitive Agent API

Agent signup and authentication

OpenAPI Specification

primitive-agent-api-openapi.yml Raw ↑
openapi: 3.1.0
info:
  title: Primitive Account Agent API
  version: 1.0.0
  description: "Primitive is email infrastructure for AI agents. The Primitive API lets you manage domains, emails, webhook endpoints,\nfilters, and account settings programmatically.\n\n## Authentication\n\nMost endpoints require a Bearer token in the `Authorization` header:\n\n```\nAuthorization: Bearer prim_<your_api_key>\nAuthorization: Bearer prim_oat_<oauth_access_token>\n```\n\nAPI keys and OAuth access tokens are org-scoped. Create and manage them in your dashboard\nunder Settings > API Keys. CLI login plus CLI/agent signup endpoints\nexplicitly declare `security: []`; they do not require an API key because\nthey are used to create OAuth CLI sessions.\n\n## Rate Limiting\n\nThe API enforces a sliding window rate limit of **120 requests per\n60 seconds** per organization. When exceeded, the API returns `429`\nwith a `Retry-After` header indicating how many seconds to wait.\n\n## Pagination\n\nList endpoints use cursor-based pagination. Responses include a\n`meta` object with `total`, `limit`, and `cursor` fields. Pass the\n`cursor` value as a query parameter to fetch the next page. When\n`cursor` is `null`, there are no more results.\n\n## Response Format\n\nAll responses use a consistent envelope:\n\n```json\n{\n  \"success\": true,\n  \"data\": { ... },\n  \"meta\": { \"total\": 42, \"limit\": 50, \"cursor\": \"...\" }\n}\n```\n\nErrors follow the same pattern:\n\n```json\n{\n  \"success\": false,\n  \"error\": { \"code\": \"not_found\", \"message\": \"Email not found\" }\n}\n```\n\n## Webhook signing\n\nOutbound webhook deliveries (configured via the `endpoints` API)\nare signed so receivers can verify they came from Primitive and\nhave not been tampered with in transit. The signing scheme is\ndeliberately simple so it can be reimplemented in any language\nin a few lines. The Node SDK's `verifyWebhookSignature` helper\nis the reference implementation; the wire details below let you\nwrite a verifier in Python, Go, Ruby, etc. without reading our\nsource.\n\n**Header**: `Primitive-Signature: t=<unix-seconds>,v1=<hex>`\n\nA legacy `MyMX-Signature` header is also sent on every delivery\nwith the same value, retained for back-compatibility with\nintegrations written before the rename. New code should read\n`Primitive-Signature`.\n\n**Signed string**: `${timestamp}.${rawBody}` where `timestamp`\nis the Unix-seconds integer from the `t=` parameter and\n`rawBody` is the exact bytes of the HTTP request body BEFORE\nany JSON decoding. Verify against the raw body, not a\nre-serialized parse, or you will silently mismatch on\ninsignificant whitespace.\n\n**Signature**: HMAC-SHA256 of the signed string, hex-encoded\n(lowercase). Use the account's webhook secret as the HMAC key,\nas a UTF-8 byte sequence.\n\n**Secret**: returned by `GET /account/webhook-secret`. The\nstring looks base64-shaped (e.g. `XNHBBW8VqoBjRfNs1tkZj11jTk...`)\nbut is NOT base64; use it AS-IS as a UTF-8 string for the HMAC\nkey. Base64-decoding before HMAC will silently produce\nmismatched signatures.\n\n**Tolerance**: by convention, reject deliveries whose `t=`\ntimestamp is more than 5 minutes off your wall-clock to defend\nagainst replay attacks. The Node SDK's helper enforces this by\ndefault.\n\n**Verification recipe** (any language):\n\n```\n1. Read the raw HTTP body (do not parse).\n2. Read `Primitive-Signature: t=<ts>,v1=<sig>`.\n3. Reject if abs(now - ts) > 300 seconds.\n4. expected = HMAC_SHA256_hex(secret_utf8, f\"{ts}.{rawBody}\")\n5. Constant-time compare expected to sig. Reject if not equal.\n```\n\nFor Node, use `verifyWebhookSignature` from\n`@primitivedotdev/sdk/webhook` (or the higher-level\n`handleWebhook` helper if you want a one-liner). For other\nlanguages, the recipe above is everything you need.\n\nTest deliveries: `POST /endpoints/{id}/test` triggers a fake\ndelivery to your endpoint URL, signed with your real account\nsecret, so you can confirm verification end-to-end without\nneeding real inbound mail. The test response carries the exact\n`signature` header value sent on the wire so you can compare\nstrings directly.\n\n\n## Errors\n\nEvery error response is the same JSON envelope (`{ \"success\": false, \"error\": { \"code\", \"message\" } }`), served as `application/json` with HTTP status codes, following the RFC 7807 problem-details shape. The `error.code` is a stable machine-readable string and `error.message` is human-readable.\n\n## Authorization and roles\n\nAccess is governed by organization role-based access control. Every organization member holds one of three roles — `owner`, `admin`, or `member` — and a credential inherits a role. **API keys** always act at `member` level, regardless of the role of the user who created them, so an API key can never perform owner- or admin-only actions. **OAuth access tokens** act with the authorizing user's current organization role, resolved on each request. Every operation in this spec is part of the member-level surface, so any valid credential can call it. Organization administration that is not part of this API — billing and organization settings — requires an `owner` or `admin` and is performed in the dashboard. Fine-grained per-key scopes (e.g. a send-only or read-only key) are on the roadmap; today the role model is the unit of access control.\n\n## Versioning\n\nThe current stable API is **v1**. All endpoints are served under `/v1/` and are covered by a backward-compatibility guarantee: existing fields and status codes will not change without a deprecation notice.\n\nBreaking changes are announced at least 6 months in advance via changelog and email. Deprecated operations and fields are marked `x-deprecated: true` in the spec and carry a plain-English description of the replacement. The `v1` path prefix is guaranteed stable indefinitely; backward-compatible additions (new optional fields, new endpoints) may be made at any time without a version bump."
  contact:
    name: Primitive
    url: https://primitive.dev
  license:
    name: Proprietary
    url: https://primitive.dev/terms
  x-stability-level: stable
  x-deprecation-policy: 'Breaking changes are announced at least 6 months in advance. Deprecated fields carry x-deprecated: true. The current stable version is v1.'
servers:
- url: https://api.primitive.dev/v1
  description: Canonical API host (PRIMITIVE_API_BASE_URL). Carries every public API operation.
tags:
- name: Agent
  description: Agent signup and authentication
paths:
  /agent/signup/start:
    post:
      operationId: startAgentSignup
      summary: Start agent account signup
      description: 'Starts an agent-native signup session. `signup_code` is optional;

        omit it to sign up without one. The API creates a pending signup

        session, sends an email verification code, and returns an opaque

        signup token used by the resend and verify steps. This endpoint

        does not require an API key.

        '
      tags:
      - Agent
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                email:
                  type: string
                  format: email
                  maxLength: 254
                signup_code:
                  type: string
                  minLength: 1
                  maxLength: 128
                  description: Optional signup code. Omit if you do not have one.
                terms_accepted:
                  type: boolean
                  const: true
                  description: Must be true to confirm acceptance of Primitive's Terms of Service and Privacy Policy
                device_name:
                  type: string
                  minLength: 1
                  maxLength: 80
                  description: Human-readable device name used for the created agent OAuth session
                metadata:
                  type: object
                  additionalProperties: true
                  description: Optional client metadata stored with the signup session; serialized JSON must be 2048 bytes or fewer
              required:
              - email
              - terms_accepted
      responses:
        '201':
          description: Agent signup session created and verification email sent
          headers:
            Cache-Control:
              schema:
                type: string
              description: Always `no-store`
          content:
            application/json:
              schema:
                allOf:
                - type: object
                  properties:
                    success:
                      type: boolean
                      const: true
                  required:
                  - success
                  - data
                - type: object
                  properties:
                    data:
                      type: object
                      properties:
                        signup_token:
                          type: string
                          description: Opaque token used to verify or resend the pending agent signup
                        email:
                          type: string
                          format: email
                        expires_in:
                          type: integer
                          description: Seconds until the pending signup expires
                        resend_after:
                          type: integer
                          description: Minimum seconds before requesting another verification email
                        verification_code_length:
                          type: integer
                          description: Number of digits in the emailed verification code
                      required:
                      - signup_token
                      - email
                      - expires_in
                      - resend_after
                      - verification_code_length
        '400':
          $ref: '#/components/responses/ValidationError'
          description: Invalid request parameters
        '429':
          $ref: '#/components/responses/RateLimited'
          description: Rate limit exceeded
      parameters:
      - name: Idempotency-Key
        in: header
        required: false
        description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
        schema:
          type: string
          minLength: 1
          maxLength: 255
  /agent/signup/resend:
    post:
      operationId: resendAgentSignupVerification
      summary: Resend agent signup verification code
      description: 'Sends a new email verification code for a pending agent signup session.

        This endpoint does not require an API key.

        '
      tags:
      - Agent
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                signup_token:
                  type: string
                  minLength: 1
              required:
              - signup_token
      responses:
        '200':
          description: Verification email resent
          headers:
            Cache-Control:
              schema:
                type: string
              description: Always `no-store`
          content:
            application/json:
              schema:
                allOf:
                - type: object
                  properties:
                    success:
                      type: boolean
                      const: true
                  required:
                  - success
                  - data
                - type: object
                  properties:
                    data:
                      type: object
                      properties:
                        email:
                          type: string
                          format: email
                        expires_in:
                          type: integer
                          description: Seconds until the pending signup expires
                        resend_after:
                          type: integer
                          description: Minimum seconds before requesting another verification email
                        verification_code_length:
                          type: integer
                          description: Number of digits in the emailed verification code
                      required:
                      - email
                      - expires_in
                      - resend_after
                      - verification_code_length
        '400':
          $ref: '#/components/responses/ValidationError'
          description: Invalid token or expired token
        '429':
          $ref: '#/components/responses/RateLimited'
          description: Global rate limit exceeded or resend requested too quickly
      parameters:
      - name: Idempotency-Key
        in: header
        required: false
        description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
        schema:
          type: string
          minLength: 1
          maxLength: 255
  /agent/signup/verify:
    post:
      operationId: verifyAgentSignup
      summary: Verify agent signup and create OAuth tokens
      description: 'Verifies the email code for an agent signup session and creates

        the account when needed. When the session was started with a

        `signup_code`, the reserved code is redeemed; sessions started

        without a code skip the redemption step. An org-scoped OAuth

        session for CLI authentication is minted and the raw tokens are

        returned exactly once. For existing users, the optional `org_id`

        selects which accessible workspace should receive the new

        session (no signup-code redemption is performed for existing

        users regardless of how the session was started).

        '
      tags:
      - Agent
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                signup_token:
                  type: string
                  minLength: 1
                verification_code:
                  type: string
                  minLength: 1
                  maxLength: 32
                org_id:
                  type: string
                  format: uuid
                  description: Optional workspace id to target when the verified email already belongs to multiple workspaces
              required:
              - signup_token
              - verification_code
      responses:
        '200':
          description: Agent signup verified and OAuth tokens created
          headers:
            Cache-Control:
              schema:
                type: string
              description: Always `no-store`
          content:
            application/json:
              schema:
                allOf:
                - type: object
                  properties:
                    success:
                      type: boolean
                      const: true
                  required:
                  - success
                  - data
                - type: object
                  properties:
                    data:
                      type: object
                      properties:
                        api_key:
                          type: string
                          description: Legacy alias for access_token. New CLI builds should persist access_token and refresh_token.
                        key_id:
                          type: string
                          format: uuid
                          description: Legacy alias for oauth_grant_id
                        key_prefix:
                          type: string
                          description: Legacy display prefix derived from access_token
                        access_token:
                          type: string
                          description: OAuth access token for CLI API authentication
                        refresh_token:
                          type: string
                          description: OAuth refresh token used by the CLI to renew access
                        token_type:
                          type: string
                          enum:
                          - Bearer
                        expires_in:
                          type: integer
                          description: Seconds until access_token expires
                        auth_method:
                          type: string
                          enum:
                          - oauth
                        oauth_grant_id:
                          type: string
                          format: uuid
                        oauth_client_id:
                          type: string
                        org_id:
                          type: string
                          format: uuid
                        org_name:
                          type:
                          - string
                          - 'null'
                        orgs:
                          type: array
                          items:
                            type: object
                            properties:
                              id:
                                type: string
                                format: uuid
                              name:
                                type:
                                - string
                                - 'null'
                            required:
                            - id
                            - name
                          description: Workspaces available to the verified email. The minted session targets `org_id`.
                      required:
                      - api_key
                      - key_id
                      - key_prefix
                      - access_token
                      - refresh_token
                      - token_type
                      - expires_in
                      - auth_method
                      - oauth_grant_id
                      - oauth_client_id
                      - org_id
                      - org_name
                      - orgs
        '400':
          $ref: '#/components/responses/ValidationError'
          description: Invalid request, invalid verification code, expired token, invalid signup code, or account creation failure
        '403':
          $ref: '#/components/responses/Forbidden'
          description: Authenticated caller lacks permission for the operation
        '409':
          $ref: '#/components/responses/Conflict'
          description: Existing account is not in a usable workspace state
        '429':
          $ref: '#/components/responses/RateLimited'
          description: Rate limit exceeded
      parameters:
      - name: Idempotency-Key
        in: header
        required: false
        description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
        schema:
          type: string
          minLength: 1
          maxLength: 255
  /agent/accounts:
    post:
      operationId: createAgentAccount
      summary: Create an emailless agent account
      description: 'Creates an emailless agent account without authentication and returns a

        one-time API key (prefixed `prim_`) plus a provisioned managed inbox.

        The account is on the `agent` plan: reply-only (it can send only to

        addresses that have already sent it authenticated mail) with tight send

        limits. Use the returned `api_key` as a Bearer token on later calls. The

        account can be upgraded to a full developer account by confirming an

        email through the claim flow. This endpoint does not require an API key.

        '
      tags:
      - Agent
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                terms_accepted:
                  type: boolean
                  enum:
                  - true
                  description: Must be true to accept the Terms of Service and Privacy Policy.
                device_name:
                  type: string
                  minLength: 1
                  maxLength: 80
                  description: Optional label for the device or agent creating the account.
              required:
              - terms_accepted
      responses:
        '200':
          description: Agent account created; the API key is returned once
          headers:
            Cache-Control:
              schema:
                type: string
              description: Always `no-store`
          content:
            application/json:
              schema:
                allOf:
                - type: object
                  properties:
                    success:
                      type: boolean
                      const: true
                  required:
                  - success
                  - data
                - type: object
                  properties:
                    data:
                      type: object
                      properties:
                        api_key:
                          type: string
                          description: One-time API key (prefixed `prim_`). Shown once; store it securely.
                        org_id:
                          type: string
                          format: uuid
                        address:
                          type:
                          - string
                          - 'null'
                          description: Provisioned managed inbox FQDN, or null if the inbox publish was deferred.
                        plan:
                          type: string
                          enum:
                          - agent
                        limits:
                          type: object
                          description: Plan-derived quota limits for an account.
                          properties:
                            storage_mb:
                              type: number
                            send_per_hour:
                              type: number
                            send_per_day:
                              type: number
                            api_per_minute:
                              type: number
                            webhooks_max_global:
                              type:
                              - number
                              - 'null'
                            webhooks_per_domain:
                              type: boolean
                            filters_per_domain:
                              type: boolean
                            spam_thresholds_per_domain:
                              type: boolean
                          required:
                          - storage_mb
                          - send_per_hour
                          - send_per_day
                          - api_per_minute
                          - webhooks_max_global
                          - webhooks_per_domain
                          - filters_per_domain
                          - spam_thresholds_per_domain
                        upgrade:
                          type: object
                          description: In-band pointer to the upgrade path for an agent account.
                          properties:
                            plan:
                              type: string
                              enum:
                              - developer
                            description:
                              type: string
                            claim_path:
                              type: string
                          required:
                          - plan
                          - description
                          - claim_path
                      required:
                      - api_key
                      - org_id
                      - address
                      - plan
                      - limits
                      - upgrade
        '400':
          $ref: '#/components/responses/ValidationError'
          description: Invalid request parameters
        '429':
          $ref: '#/components/responses/RateLimited'
          description: Rate limit exceeded
      parameters:
      - name: Idempotency-Key
        in: header
        required: false
        description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
        schema:
          type: string
          minLength: 1
          maxLength: 255
  /agent/claim/start:
    post:
      operationId: startAgentClaim
      summary: Start an agent account email claim
      description: 'Begins upgrading an emailless `agent` account into a full `developer`

        account by confirming an email address. Authenticated by the agent''s own

        API key (the org is taken from the credential). Sends a verification

        code to the supplied email and returns the claim session id plus resend

        timing. Submit the code to `/agent/claim/verify` to complete the

        upgrade. Confirming an email that already belongs to a Primitive account

        is rejected.

        '
      tags:
      - Agent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                email:
                  type: string
                  format: email
                  maxLength: 254
                  description: Email to confirm. Must not already belong to a Primitive account.
              required:
              - email
      responses:
        '200':
          description: Claim started and verification email sent
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
            Cache-Control:
              schema:
                type: string
              description: Always `no-store`
          content:
            application/json:
              schema:
                allOf:
                - type: object
                  properties:
                    success:
                      type: boolean
                      const: true
                  required:
                  - success
                  - data
                - type: object
                  properties:
                    data:
                      type: object
                      properties:
                        claim_session_id:
                          type: string
                        resend_after_seconds:
                          type: integer
                        expires_in_seconds:
                          type: integer
                      required:
                      - claim_session_id
                      - resend_after_seconds
                      - expires_in_seconds
        '400':
          $ref: '#/components/responses/ValidationError'
          description: Invalid request parameters
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: Invalid or missing API key
        '404':
          $ref: '#/components/responses/NotFound'
          description: Resource not found
        '409':
          $ref: '#/components/responses/Conflict'
          description: The email is already in use, or the account is not claimable
        '429':
          $ref: '#/components/responses/RateLimited'
          description: Rate limit exceeded
      security:
      - BearerAuth: []
      parameters:
      - name: Idempotency-Key
        in: header
        required: false
        description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
        schema:
          type: string
          minLength: 1
          maxLength: 255
  /agent/claim/verify:
    post:
      operationId: verifyAgentClaim
      summary: Verify an agent account email claim
      description: 'Confirms the verification code emailed by `/agent/claim/start` and

        upgrades the account to the `developer` plan. The org id, API key, and

        managed inbox all carry over; the send cap lifts. Authenticated by the

        agent''s own API key.

        '
      tags:
      - Agent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                verification_code:
                  type: string
                  minLength: 1
                  maxLength: 32
                  description: The verification code emailed by the claim start step.
              required:
              - verification_code
      responses:
        '200':
          description: Claim verified; account upgraded to developer
          headers:
            ratelimit-limit:
              description: Maximum number of requests allowed in the current window.
              schema:
                type: integer
                minimum: 1
                example: 120
            ratelimit-remaining:
              description: Remaining requests in the current window.
              schema:
                type: integer
                minimum: 0
                example: 118
            ratelimit-reset:
              description: Unix timestamp (seconds) when the current window resets.
              schema:
                type: integer
                example: 1700000060
            ratelimit-policy:
              description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
              schema:
                type: string
                example: 120;w=60
            Cache-Control:
              schema:
                type: string
              description: Always `no-store`
          content:
            application/json:
              schema:
                allOf:
                - type: object
                  properties:
                    success:
                      type: boolean
                      const: true
                  required:
                  - success
                  - data
                - type: object
                  properties:
                    data:
                      type: object
                      properties:
                        org_id:
                          type: string
                          format: uuid
                        plan:
            

# --- truncated at 32 KB (47 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/primitive/refs/heads/main/openapi/primitive-agent-api-openapi.yml