Cooper Email Domains API

Domains operations for the Cooper Email API.

GitHub OpenAPI

Operations 6

GET /api/v1/domains List custom domains and their DNS status #
POST /api/v1/domains Add a custom domain and return SPF, DKIM, DMARC, and MX records. #
GET /api/v1/domains/{id} Get custom domain status #
DELETE /api/v1/domains/{id} Delete a custom domain #
POST /api/v1/domains/{id}/verify Verify a custom domain now #
GET /api/v1/domains/{id}/zone-file Download BIND zone lines for a custom domain #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/cooperemail-com:cooperemail-com-domains-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

cooperemail-com-domains-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Cooperemail Com Domains API
  version: 1.0.0
  contact:
    name: Avatar 8 LLC
    email: ops@avatar33.com
  license:
    name: Proprietary
    url: https://cooperemail.com/terms
  description: 'Operations tagged Domains across 2 of this provider''s published API definitions: cooperemail-com-openapi.json, cooperemail-com-openapi.yml. Each path carries the servers of the definition it was published in.'
servers:
- url: https://cooperemail.com
  description: This deployment
security:
- bearerAuth: []
tags:
- name: Domains
  description: Domains operations for the Cooper Email API.
paths:
  /api/v1/domains:
    get:
      operationId: listDomains
      tags:
      - Domains
      summary: List custom domains and their DNS status
      responses:
        '200':
          description: Success.
        '400':
          description: Client error. The JSON body uses error.code.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    post:
      operationId: createDomain
      tags:
      - Domains
      summary: Add a custom domain and return SPF, DKIM, DMARC, and MX records.
      description: Starter and Pro (plan customDomains, plus extra domain quantity on the subscription). Returns the records to publish and a verify TXT. Each record has host, type, value, priority for MX, ttl when set, status valid, missing, or invalid, found from the last check, and hint. hint is null when the record is valid or no specific mistake was found. Otherwise hint is {code, message, found_at} and says what to change. The domain includes warnings (an empty array when none) plus mail, status_detail, and dns.mode. existing_mail_provider means this hostname already has MX at another provider. spf_dash_all, dmarc_reject, and multiple_spf name a record that would block or split mail. Calling again with the same domain or client_id returns the original domain. dns.mode is automatic when the hostname is already an active zone on the operator Cloudflare account. Cooper writes those records and does not register a domain or create a zone. A zone on that account that is not active stays manual. When custom-domain mail is on and the hostname is not a zone on that account, dns.mode is external and you add the records at your DNS host. The daily digest cron checks DNS and sends domain.verified when the records match. Which records are valid, missing, or invalid is unchanged. Free returns HTTP 402 plan_limit_exceeded. Inboxes on your own domain are included on Starter (15 custom domains) and Pro (200 custom domains). Free includes 0 custom domains and returns HTTP 402 plan_limit_exceeded. When the hostname is already an active zone on the operator Cloudflare account, Cooper writes the DNS records and dns.mode is automatic. Cooper does not register a domain or create a zone. A zone that is on that account but not active stays manual. Publish the routing MX hosts, TXT SPF v=spf1 include:_spf.mx.cloudflare.net ~all, the routing DKIM TXT, the cf-bounce MX, SPF, and DKIM records, a DMARC TXT (an existing DMARC record is left as it is), and the _cooper-verify TXT, or point the nameservers at the ones in the response. mail.receiving stays false until the receiving records check out and the catch-all is live. Receiving goes live within a few minutes. status_detail says so while you wait. POST /api/v1/inboxes with domain then creates username@domain. When the hostname is not a zone on the operator account, dns.mode is external. Cooper does not move nameservers. Each record has host, type, value, priority for MX, and ttl. Publish the receiving MX, the return-path MX and one SPF TXT with a single include, the DKIM TXT, and the _cooper-verify TXT at your own DNS host. An existing SPF is merged into that one record. An apex v=spf1 -all does not reject mail whose envelope-from is the return-path host. Do not add a second SPF TXT. A DMARC p=reject record is left as published and blocks mail until DKIM for that domain verifies. Cooper checks the records with public DNS. mail.receiving becomes true after the receiving MX verifies and RESEND_WEBHOOK_SECRET is set. mail.sending becomes true after the return-path and DKIM records verify. The receiving MX hostname and the DKIM value come from Resend on that response. Do not invent them. DELETE /api/v1/domains/:id puts back the catch-all that was there before, deletes only the DNS records Cooper created, turns Email Routing off only if Cooper turned it on, and removes the sending domain only if Cooper created it. A Resend domain Cooper created is removed the same way. A cleanup that does not finish keeps the row with status cleanup_failed and deleted false so a later delete can finish it.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                domain:
                  type: string
                  description: Hostname, such as agents.example.com
                client_id:
                  type: string
                  description: Optional idempotency key
              required:
              - domain
      responses:
        '200':
          description: Success.
        '400':
          description: Client error. The JSON body uses error.code.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    servers:
    - url: https://cooperemail.com
      description: This deployment
  /api/v1/domains/{id}:
    get:
      operationId: getDomain
      tags:
      - Domains
      summary: Get custom domain status
      description: status is pending until SPF, DKIM, DMARC, MX, and the verify record match, then verified. Includes mail, status_detail, per-record status, found, hint, and warnings. hint is null when that record is valid or no specific mistake was found.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Domain
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Domain'
        '404':
          description: domain_not_found
    delete:
      operationId: deleteDomain
      tags:
      - Domains
      summary: Delete a custom domain
      description: Removes the domain. When dns.mode is automatic, deletes only the DNS records Cooper wrote. When dns.mode is external, also deletes the external sending domain Cooper created. Returns dns_cleanup. deleted false and status cleanup_failed means a later delete can finish the cleanup. Another account's id is 404 domain_not_found.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DomainDelete'
        '404':
          description: domain_not_found
    servers:
    - url: https://cooperemail.com
      description: This deployment
  /api/v1/domains/{id}/verify:
    post:
      operationId: verifyDomain
      tags:
      - Domains
      summary: Verify a custom domain now
      description: Runs the cron DNS check for this domain, stores per-record status, found, and hint, plus warnings, and fires domain.verified once on the pending to verified transition. dns.mode external is checked with public DNS. At most once per 60 seconds. HTTP 429 rate_limited includes Retry-After, and the JSON error includes retry_after_seconds and resets_at with that same delay. Another account's id is 404 domain_not_found. Which records are valid, missing, or invalid is unchanged.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Domain
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Domain'
        '404':
          description: domain_not_found
        '429':
          description: rate_limited
    servers:
    - url: https://cooperemail.com
      description: This deployment
  /api/v1/domains/{id}/zone-file:
    get:
      operationId: domainZoneFile
      tags:
      - Domains
      summary: Download BIND zone lines for a custom domain
      description: text/plain BIND-style lines for every required record. A line includes a TTL when that record has one. Another account's id is 404 domain_not_found.
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Zone file
          content:
            text/plain:
              schema:
                type: string
        '404':
          description: domain_not_found
    servers:
    - url: https://cooperemail.com
      description: This deployment
components:
  schemas:
    DomainRecord:
      type: object
      properties:
        role:
          type: string
          enum:
          - mx
          - spf
          - dkim
          - dmarc
          - verify
          - cf_bounce_mx
          - cf_bounce_spf
          - cf_bounce_dkim
          - return_path_mx
          - return_path_spf
          - return_path_cname
        type:
          type: string
          enum:
          - MX
          - TXT
          - CNAME
        host:
          type: string
        value:
          type: string
        priority:
          type: integer
          description: MX priority. Copy this with the host and value.
        ttl:
          type: integer
          description: Suggested TTL in seconds. Present on external-DNS records.
        status:
          type: string
          enum:
          - valid
          - missing
          - invalid
        found:
          type: array
          items:
            type: string
        hint:
          description: Null when this record is valid or no specific mistake was found. Otherwise what to change. Validity is unchanged.
          oneOf:
          - $ref: '#/components/schemas/DomainRecordHint'
          - type: 'null'
      required:
      - role
      - type
      - host
      - value
      - status
      - found
      - hint
    McpMessageReceivedCallback:
      type: object
      description: Body Cooper POSTs to an MCP event callback for message.received. Metadata only. The subject, body, attachments, and headers are not included.
      properties:
        eventId:
          type: string
          description: Matches the webhook-id header. Stable across retries.
        name:
          const: message.received
        timestamp:
          type: string
          description: ISO 8601.
        cursor:
          type: 'null'
        data:
          type: object
          properties:
            inbox_id:
              type: string
            inbox:
              type: string
              description: Inbox address.
            message_id:
              type: string
            thread_id:
              type: string
            timestamp:
              type: string
              description: ISO 8601 time the message was stored.
            from:
              type: string
            to:
              type: array
              items:
                type: string
            cc:
              type: array
              items:
                type: string
            labels:
              type: array
              items:
                type: string
            channel:
              type: string
              enum:
              - email
              - sms
              - voicemail
              - voice
              description: 'channel is the channel the message arrived on: email, sms, voicemail, or voice.'
          required:
          - inbox_id
          - inbox
          - message_id
          - thread_id
          - timestamp
          - from
          - to
          - cc
          - labels
          - channel
      required:
      - eventId
      - name
      - timestamp
      - data
      - cursor
    NextSteps:
      type: object
      properties:
        instruction:
          type: string
        docs_url:
          type: string
        recommended:
          type: string
          description: Suggests adding a human owner email. A recommendation, not a requirement.
        snippets:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              title:
                type: string
              url:
                type: string
            required:
            - id
            - title
            - url
      required:
      - instruction
      - docs_url
      - recommended
      - snippets
    SendingStatus:
      type: object
      description: Trailing 7-day send health for this account. Counts match GET /api/v1/metrics/rates with no inbox id. Warning does not block. Paused blocks agent email until the rate recovers.
      properties:
        object:
          type: string
          enum:
          - sending_status
        status:
          type: string
          enum:
          - active
          - warning
          - paused
        reason:
          type:
          - string
          - 'null'
          enum:
          - bounce_rate
          - complaint_rate
          - null
        window_days:
          type: integer
          enum:
          - 7
        sent:
          type: integer
        bounced:
          type: integer
        complained:
          type: integer
        bounce_rate:
          type: number
        complaint_rate:
          type: number
        thresholds:
          type: object
          properties:
            min_sent:
              type: integer
            warning_bounce_rate:
              type: number
            warning_complaint_rate:
              type: number
            pause_bounce_rate:
              type: number
            pause_min_bounced:
              type: integer
            pause_complaint_rate:
              type: number
            pause_min_complained:
              type: integer
          required:
          - min_sent
          - warning_bounce_rate
          - warning_complaint_rate
          - pause_bounce_rate
          - pause_min_bounced
          - pause_complaint_rate
          - pause_min_complained
        paused_at:
          type:
          - string
          - 'null'
          format: date-time
        evaluated_at:
          type: string
          format: date-time
        resumes:
          type: string
      required:
      - object
      - status
      - reason
      - window_days
      - sent
      - bounced
      - complained
      - bounce_rate
      - complaint_rate
      - thresholds
      - paused_at
      - evaluated_at
      - resumes
    DomainDelete:
      type: object
      properties:
        id:
          type: string
        deleted:
          type: boolean
          const: true
        dns_cleanup:
          type: object
          properties:
            removed:
              type: array
              items:
                type: string
            errors:
              type: array
              items:
                type: string
          required:
          - removed
          - errors
      required:
      - id
      - deleted
      - dns_cleanup
    DomainWarning:
      type: object
      properties:
        code:
          type: string
          enum:
          - existing_mail_provider
          - spf_dash_all
          - dmarc_reject
          - multiple_spf
        message:
          type: string
        provider:
          type: string
          description: Present when the existing MX host is a recognized provider.
      required:
      - code
      - message
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            type:
              type: string
            code:
              type: string
            message:
              type: string
            param:
              type: string
            retry_after_seconds:
              type: integer
              description: On HTTP 429, and on HTTP 503 when a retry time is known. The same integer as the Retry-After header. Omitted when that header is absent.
            resets_at:
              type: string
              format: date-time
              description: UTC time when the caller may retry, now plus retry_after_seconds. Omitted together with that field when no retry time is known.
            docs_url:
              type: string
            sending:
              $ref: '#/components/schemas/SendingStatus'
              description: Present on HTTP 403 sending_paused. Same object as GET /api/v1/sending-status.
          required:
          - type
          - code
          - message
        related_schemas:
          description: Not returned on the wire. Points at component schemas that the operation descriptions name.
          type: object
          properties:
            McpMessageReceivedCallback:
              $ref: '#/components/schemas/McpMessageReceivedCallback'
            NextSteps:
              $ref: '#/components/schemas/NextSteps'
            Inbox:
              $ref: '#/components/schemas/Inbox'
            Thread:
              $ref: '#/components/schemas/Thread'
            ListEntry:
              $ref: '#/components/schemas/ListEntry'
    Thread:
      type: object
      properties:
        object:
          const: thread
        id:
          type: string
          description: Thread id (thr_…).
        inbox_id:
          type: string
        subject:
          type: string
          description: Subject of the earliest message.
        preview:
          type: string
          description: Preview of the latest message.
        senders:
          type: array
          items:
            type: string
          description: Distinct From values.
        recipients:
          type: array
          items:
            type: string
          description: Distinct To and Cc values.
        message_count:
          type: integer
        last_message_id:
          type: string
        labels:
          type: array
          items:
            type: string
          description: Union of message labels.
        attachment_count:
          type: integer
        received_at:
          type:
          - string
          - 'null'
          format: date-time
        sent_at:
          type:
          - string
          - 'null'
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        folder:
          type: string
          enum:
          - inbox
          - archive
          - spam
          - trash
          description: inbox when no folder row exists. archive, spam, or trash otherwise.
        purge_after:
          type:
          - string
          - 'null'
          format: date-time
          description: Set only while the thread is in trash. The daily cron hard-deletes the thread after this time.
        assignment:
          description: Null when the thread has no assignment.
          oneOf:
          - $ref: '#/components/schemas/ThreadAssignment'
          - type: 'null'
      required:
      - object
      - id
      - inbox_id
      - subject
      - message_count
      - updated_at
    Domain:
      type: object
      properties:
        object:
          type: string
          const: domain
        id:
          type: string
        domain:
          type: string
        status:
          type: string
          enum:
          - pending
          - verified
          - cleanup_failed
        status_detail:
          type: string
        mail:
          type: object
          properties:
            receiving:
              type: boolean
            sending:
              type: boolean
            inboxes:
              type: boolean
          required:
          - receiving
          - sending
          - inboxes
        records:
          type: array
          items:
            $ref: '#/components/schemas/DomainRecord'
        warnings:
          type: array
          description: Empty when nothing else is wrong. existing_mail_provider means this hostname already has MX records that are not the records in this response. spf_dash_all means an SPF record ends in -all and does not name the sender. dmarc_reject means DMARC p=reject will block mail until alignment succeeds. multiple_spf means more than one SPF TXT is published on that host.
          items:
            $ref: '#/components/schemas/DomainWarning'
        zone_status:
          type: string
          enum:
          - onboarded
          - domain_zone_not_onboarded
          - external
          description: Present when custom-domain mail is on for this account.
        nameservers:
          type: array
          items:
            type: string
        nameserver_instructions:
          type:
          - string
          - 'null'
        dns:
          type: object
          properties:
            mode:
              type: string
              enum:
              - manual
              - automatic
              - external
            zone_name:
              type:
              - string
              - 'null'
            written:
              type: array
              items:
                type: string
            present:
              type: array
              items:
                type: string
            conflicts:
              type: array
              items:
                type: string
            error:
              type: string
          required:
          - mode
        created_at:
          type: string
          format: date-time
        verified_at:
          type:
          - string
          - 'null'
          format: date-time
        last_checked_at:
          type:
          - string
          - 'null'
          format: date-time
      required:
      - id
      - domain
      - status
      - status_detail
      - mail
      - records
      - warnings
    DomainRecordHint:
      type: object
      description: What to change for a record that is missing or invalid. Null on the record when it is valid or no specific mistake was found.
      properties:
        code:
          type: string
          enum:
          - duplicated_suffix
          - wrong_host
          - wrong_type
          - wrong_token
          - value_typo
          - multiple_spf
          - mx_priority
          - other_provider_mx
        message:
          type: string
          description: One short sentence describing what to change.
        found_at:
          type: string
          description: Host where the value was found, when that host is not the one Cooper asked for.
      required:
      - code
      - message
    ListEntry:
      type: object
      properties:
        object:
          type: string
          enum:
          - list_entry
        direction:
          type: string
          enum:
          - send
          - receive
          - reply
        type:
          type: string
          enum:
          - allow
          - block
        entry:
          type: string
          description: Lowercase email address or domain.
        entry_type:
          type: string
          enum:
          - email
          - domain
        inbox_id:
          type:
          - string
          - 'null'
          description: Null when the entry is account-wide.
        reason:
          type:
          - string
          - 'null'
        created_at:
          type: string
          format: date-time
      required:
      - object
      - direction
      - type
      - entry
      - entry_type
      - created_at
    Inbox:
      type: object
      properties:
        id:
          type: string
        username:
          type: string
        email:
          type: string
        display_name:
          type:
          - string
          - 'null'
        created_at:
          type: string
          format: date-time
        require_sender_auth:
          type: boolean
          description: Default true. A verified owner or allowlisted sender creates a task only when DMARC=pass or DKIM=pass aligns with the From domain. Set false to opt this inbox out.
        labels:
          type: array
          items:
            type: string
          description: Labels set at create time. Filter with GET /api/v1/inboxes?label=
        expires_at:
          type:
          - string
          - 'null'
          format: date-time
          description: When set, the inbox is temporary. The daily cron deletes it at this time and it stops counting toward the inbox limit.
        status:
          type: string
          enum:
          - active
          - paused
          description: active sends and stores inbound mail. paused keeps the address and history. A paused inbox still counts toward the plan inbox limit.
        paused_at:
          type:
          - string
          - 'null'
          format: date-time
          description: When the inbox was paused. Null while active. Unchanged if you pause an inbox that is already paused.
        metadata:
          type: object
          additionalProperties: true
          description: Caller data. At most 256 keys. Each key is at most 256 characters. Values are a string, number, or boolean. The JSON form is at most 16 KB. A null value in a PATCH removes that key. Anything else is 400 invalid_metadata.
        client_id:
          type:
          - string
          - 'null'
          description: Idempotency key from POST /api/v1/inboxes, or null. The same client_id on this account returns the original inbox.
        screening:
          type: string
          enum:
          - 'off'
          - observe
          - hold
          description: Inbound prompt-injection screen. Default observe. off skips scoring and records why. observe adds the suspicious label. hold stores suspicious mail with the held label and fires message.held instead of message.received. The screen is a heuristic, not a guarantee. It does not drop mail.
        send_mode:
          type: string
          enum:
          - direct
          - approval
          description: direct sends immediately. approval turns POST /messages, reply, reply-all, and forward into a pending draft and emails verified owners Approve and Reject links. Leaving approval for direct waits for a verified owner. Default direct.
        signature:
          type:
          - object
          - 'null'
          description: Inbox signature applied to later sends, or null when unset. text is plain text. html is null when only text is set.
          properties:
            text:
              type: string
              description: Plain-text signature. At most 2000 characters.
            html:
              type:
              - string
              - 'null'
              description: 'HTML signature, or null. At most 10000 characters. Script tags, on* handlers, and javascript: URLs are removed.'
          required:
          - text
        idempotent:
          type: boolean
          description: True when POST /api/v1/inboxes replayed an existing client_id and returned the original inbox.
    ThreadAssignment:
      type: object
      properties:
        object:
          const: thread_assignment
        inbox_id:
          type: string
        thread_id:
          type: string
        assignee:
          type: string
          description: '1–64 characters: letters, digits, and . _ @ + : -.'
        note:
          type:
          - string
          - 'null'
          description: At most 500 characters.
        claimed_until:
          type:
          - string
          - 'null'
          format: date-time
          description: Null on a plain assignment. An exclusive claim is active while this is in the future.
        claim_active:
          type: boolean
        assigned_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
      - object
      - inbox_id
      - thread_id
      - assignee
      - claim_active
      - assigned_at
      - updated_at
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API key
x-refined-from:
- cooperemail-com-openapi.json
- cooperemail-com-openapi.yml