Primitive Domains API

Claim, verify, and manage email domains

OpenAPI Specification

primitive-domains-api-openapi.yml Raw ↑
openapi: 3.1.0
info:
  title: Primitive Account Domains 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: Domains
  description: Claim, verify, and manage email domains
paths:
  /domains:
    get:
      operationId: listDomains
      summary: List all domains
      description: 'Returns all verified and unverified domains for your organization,

        sorted by creation date (newest first). Each domain includes a

        `verified` boolean to distinguish between the two states.

        '
      tags:
      - Domains
      responses:
        '200':
          description: List of domains
          content:
            application/json:
              schema:
                allOf:
                - type: object
                  properties:
                    success:
                      type: boolean
                      const: true
                  required:
                  - success
                  - data
                - type: object
                  properties:
                    data:
                      type: array
                      items:
                        description: 'A domain can be either verified or unverified. Verified domains have

                          `is_active` and `spam_threshold` fields. Unverified domains have a

                          `verification_token` and `dns_records` for DNS setup.

                          '
                        oneOf:
                        - type: object
                          properties:
                            id:
                              type: string
                              format: uuid
                            org_id:
                              type: string
                              format: uuid
                            domain:
                              type: string
                            verified:
                              type: boolean
                              const: true
                            is_active:
                              type: boolean
                            spam_threshold:
                              type:
                              - number
                              - 'null'
                              minimum: 0
                              maximum: 15
                            verification_token:
                              type:
                              - string
                              - 'null'
                            created_at:
                              type: string
                              format: date-time
                          required:
                          - id
                          - org_id
                          - domain
                          - verified
                          - is_active
                          - created_at
                        - type: object
                          properties:
                            id:
                              type: string
                              format: uuid
                            org_id:
                              type: string
                              format: uuid
                            domain:
                              type: string
                            verified:
                              type: boolean
                              const: false
                            verification_token:
                              type: string
                              description: Add this value as a TXT record to verify ownership
                            dns_records:
                              type: array
                              description: Exact DNS records to publish for a pending domain claim or verification attempt.
                              items:
                                type: object
                                additionalProperties: false
                                properties:
                                  type:
                                    type: string
                                    enum:
                                    - MX
                                    - TXT
                                    description: DNS record type.
                                  name:
                                    type: string
                                    description: DNS-provider host/name value relative to the managed root zone.
                                  fqdn:
                                    type: string
                                    description: Fully-qualified DNS record name.
                                  value:
                                    type: string
                                    description: Exact value to publish.
                                  priority:
                                    type: integer
                                    description: MX priority. Present only for MX records.
                                  ttl:
                                    type: integer
                                    description: Suggested TTL in seconds when the API can provide one.
                                  required:
                                    type: boolean
                                    const: true
                                  purpose:
                                    type: string
                                    enum:
                                    - inbound_mx
                                    - ownership_verification
                                    - spf
                                    - dkim
                                    - dmarc
                                    - tls_reporting
                                  status:
                                    type: string
                                    enum:
                                    - pending
                                    - found
                                    - missing
                                    - incorrect
                                  message:
                                    type: string
                                    description: Short explanation of why this record is needed.
                                required:
                                - type
                                - name
                                - fqdn
                                - value
                                - required
                                - purpose
                                - status
                            created_at:
                              type: string
                              format: date-time
                          required:
                          - id
                          - org_id
                          - domain
                          - verified
                          - verification_token
                          - created_at
          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
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: Invalid or missing API key
      security:
      - BearerAuth: []
    post:
      operationId: addDomain
      summary: Claim a new domain
      description: 'Creates an unverified domain claim and returns the exact

        DNS records to publish in `dns_records`. Publish those

        records before calling the verify endpoint. To give users

        an importable DNS file, call `downloadDomainZoneFile` or run

        `primitive domains zone-file --id <domain-id>`.

        '
      tags:
      - Domains
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                domain:
                  type: string
                  minLength: 1
                  maxLength: 253
                  description: The domain name to claim (e.g. "example.com")
                confirmed:
                  type: boolean
                  description: Set to true to confirm replacing an existing mailbox provider after an mx_conflict response.
                outbound:
                  type: boolean
                  deprecated: true
                  description: Deprecated and ignored. Outbound DNS is provisioned for every new domain claim.
              required:
              - domain
      responses:
        '201':
          description: Domain claim created
          content:
            application/json:
              schema:
                allOf:
                - type: object
                  properties:
                    success:
                      type: boolean
                      const: true
                  required:
                  - success
                  - data
                - type: object
                  properties:
                    data:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                        org_id:
                          type: string
                          format: uuid
                        domain:
                          type: string
                        verified:
                          type: boolean
                          const: false
                        verification_token:
                          type: string
                          description: Add this value as a TXT record to verify ownership
                        dns_records:
                          type: array
                          description: Exact DNS records to publish for a pending domain claim or verification attempt.
                          items:
                            type: object
                            additionalProperties: false
                            properties:
                              type:
                                type: string
                                enum:
                                - MX
                                - TXT
                                description: DNS record type.
                              name:
                                type: string
                                description: DNS-provider host/name value relative to the managed root zone.
                              fqdn:
                                type: string
                                description: Fully-qualified DNS record name.
                              value:
                                type: string
                                description: Exact value to publish.
                              priority:
                                type: integer
                                description: MX priority. Present only for MX records.
                              ttl:
                                type: integer
                                description: Suggested TTL in seconds when the API can provide one.
                              required:
                                type: boolean
                                const: true
                              purpose:
                                type: string
                                enum:
                                - inbound_mx
                                - ownership_verification
                                - spf
                                - dkim
                                - dmarc
                                - tls_reporting
                              status:
                                type: string
                                enum:
                                - pending
                                - found
                                - missing
                                - incorrect
                              message:
                                type: string
                                description: Short explanation of why this record is needed.
                            required:
                            - type
                            - name
                            - fqdn
                            - value
                            - required
                            - purpose
                            - status
                        created_at:
                          type: string
                          format: date-time
                      required:
                      - id
                      - org_id
                      - domain
                      - verified
                      - verification_token
                      - created_at
        '400':
          $ref: '#/components/responses/ValidationError'
          description: Invalid request parameters
        '401':
          $ref: '#/components/responses/Unauthorized'
          description: Invalid or missing API key
        '409':
          $ref: '#/components/responses/Conflict'
          description: "Domain claim conflicts with existing state. Two error codes\nare possible:\n  * `mx_conflict`: the domain's current MX records point at\n    another mailbox provider. The response includes\n    `error.details.mx_conflict` with the detected provider\n    and a suggested subdomain.\n  * `conflict`: the domain is already claimed by another\n    org, or a pending claim exists for another user.\n"
      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
  /domains/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: Resource UUID
    patch:
      operationId: updateDomain
      summary: Update domain settings
      description: 'Update a verified domain''s settings. Only verified domains can be

        updated. Per-domain spam thresholds require a Pro plan.

        '
      tags:
      - Domains
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                is_active:
                  type: boolean
                  description: Whether the domain accepts incoming emails
                spam_threshold:
                  type:
                  - number
                  - 'null'
                  minimum: 0
                  maximum: 15
                  description: Per-domain spam threshold override (Pro plan required)
              minProperties: 1
      responses:
        '200':
          description: Updated domain
          content:
            application/json:
              schema:
                allOf:
                - type: object
                  properties:
                    success:
                      type: boolean
                      const: true
                  required:
                  - success
                  - data
                - type: object
                  properties:
                    data:
                      type: object
                      properties:
                        id:
                          type: string
                          format: uuid
                        org_id:
                          type: string
                          format: uuid
                        domain:
                          type: string
                        verified:
                          type: boolean
                          const: true
                        is_active:
                          type: boolean
                        spam_threshold:
                          type:
                          - number
                          - 'null'
                          minimum: 0
                          maximum: 15
                        verification_token:
                          type:
                          - string
                          - 'null'
                        created_at:
                          type: string
                          format: date-time
                      required:
                      - id
                      - org_id
                      - domain
                      - verified
                      - is_active
                      - created_at
          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
        '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
      security:
      - BearerAuth: []
    delete:
      operationId: deleteDomain
      summary: Delete a domain
      description: Remove a domain from the organization. Inbound mail for its addresses stops being accepted.
      tags:
      - Domains
      responses:
        '200':
          description: Resource deleted
          content:
            application/json:
              schema:
                allOf:
                - type: object
                  properties:
                    success:
                      type: boolean
                      const: true
                  required:
                  - success
                  - data
                - type: object
                  properties:
                    data:
                      type: object
                      properties:
                        deleted:
                          type: boolean
                          const: true
                      required:
                      - deleted
          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
        '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
      security:
      - BearerAuth: []
  /domains/{id}/verify:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: Resource UUID
    post:
      operationId: verifyDomain
      summary: Verify domain ownership
      description: 'Checks DNS records required for inbound routing, ownership,

        and outbound authentication: MX, ownership TXT, SPF, DKIM,

        DMARC, and TLS-RPT.

        On success, the domain is promoted from unverified to verified.

        On failure, returns which checks passed and which failed,

        plus the exact DNS records still expected. To give users

        an importable DNS file for missing records, call

        `downloadDomainZoneFile` or run

        `primitive domains zone-file --id <domain-id>`.

        '
      tags:
      - Domains
      responses:
        '200':
          description: Verification result
          content:
            application/json:
              schema:
                allOf:
                - type: object
                  properties:
                    success:
                      type: boolean
                      const: true
                  required:
                  - success
                  - data
                - type: object
                  properties:
                    data:
                      oneOf:
                      - type: object
                        properties:
                          verified:
                            type: boolean
                            const: true
                          dns_records:
                            type: array
                            description: Exact DNS records to publish for a pending domain claim or verification attempt.
                            items:
                              type: object
                              additionalProperties: false
                              properties:
                                type:
                                  type: string
                                  enum:
                                  - MX
                                  - TXT
                                  description: DNS record type.
                                name:
                                  type: string
                                  description: DNS-provider host/name value relative to the managed root zone.
                                fqdn:
                                  type: string
                                  description: Fully-qualified DNS record name.
                                value:
                                  type: string
                                  description: Exact value to publish.
                                priority:
                                  type: integer
                                  description: MX priority. Present only for MX records.
                                ttl:
                                  type: integer
                                  description: Suggested TTL in seconds when the API can provide one.
                                required:
                                  type: boolean
                                  const: true
                                purpose:
                                  type: string
                                  enum:
                                  - inbound_mx
                                  - ownership_verification
                                  - spf
                                  - dkim
                                  - dmarc
                                  - tls_reporting
                                status:
                                  type: string
                                  enum:
                                  - pending
                                  - found
                                  - missing
                                  - incorrect
                                message:
                                  type: string
                                  description: Short explanation of why this record is needed.
                              required:
                              - type
                              - name
                              - fqdn
                              - value
                              - required
                              - purpose
                              - status
                        required:
                        - verified
                      - type: object
                        properties:
                          verified:
                            type: boolean
                            const: false
                          mxFound:
                            type: boolean
                            description: Whether MX records point to Primitive
                          txtFound:
                            type: boolean
                            description: Whether the TXT verification record was found
                          spfFound:
                            type: boolean
                            description: Whether the SPF record includes Primitive.
                          dkimFound:
                            type: boolean
                            description: Whether the DKIM public key record was found.
                          dmarcFound:
                            type: boolean
                            description: Whether the

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