Cooper Email Domains API
Domains operations for the Cooper Email API.
Domains operations for the Cooper Email API.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
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.curl "https://apis.io/api/v1/apis/cooperemail-com:cooperemail-com-domains-api"
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.
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: 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