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/domainee-dev:domainee-dev-domains-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"
Discovery needs no key. Ratings and market analysis are Pro.
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: 3.2.0
info:
title: Domainee Domains API
version: 1.0.0
summary: Custom domains, domain registration, and DNS diagnostics for SaaS products.
description: 'Domainee lets your product attach your customers'' own domains to their accounts:
one call to connect a hostname, automatic SSL issuance and renewal, DNS health
monitoring, webhooks, and domain registration across 500+ TLDs.
## Authentication
Every `/v1` endpoint except `/v1/tools/*` takes a Bearer token in the
`Authorization` header. Two kinds work interchangeably:
- **API keys** (`sk_live_…`), created at https://domainee.dev/developers
- **OAuth access tokens** (`dmn_oat_…`), issued by the flow described in
the `/.well-known/oauth-authorization-server` document.
Both are workspace-scoped: a token only ever sees its own workspace''s data.
## Rate limits
Authenticated endpoints allow 60 requests per minute per key and answer `429`
with `{"error":"rate_limited"}` beyond that. The free tools are limited per IP
at 30/minute and 500/day.
## Idempotency
Send an `Idempotency-Key` header on any POST to make a retry safe: the first
response for a given key is replayed instead of performing the work twice.
## Errors
Failures return the same envelope at every status code:
`{ "error": "not_found", "message": "Domain not found" }`.
Full guides: https://domainee.dev/docs. Machine-readable index: https://domainee.dev/llms.txt.'
termsOfService: https://domainee.dev/terms
contact:
name: Domainee support
email: hello@domainee.dev
url: https://domainee.dev/contact
servers:
- url: https://api.domainee.dev
description: Production
security:
- bearerAuth: []
tags:
- name: Domains
description: Connect and verify your customers' custom domains.
paths:
/v1/domains:
get:
operationId: listDomains
summary: List connected domains
description: Cursor-paginated and workspace-scoped. Pass `hostname` for an exact-match lookup instead of paging the whole workspace.
tags:
- Domains
parameters:
- name: hostname
in: query
required: false
description: Exact hostname to look up. Returns at most one domain and a null `nextCursor`.
schema:
type: string
example: app.acme.com
- name: status
in: query
required: false
description: Only return domains in this verification state.
schema:
type: string
enum:
- pending
- verified
- failed
- expired
- name: cursor
in: query
required: false
description: The `nextCursor` from a previous page.
schema:
type: string
- name: limit
in: query
required: false
description: Page size.
schema:
type: integer
minimum: 1
maximum: 100
default: 25
responses:
'200':
description: A page of domains.
content:
application/json:
schema:
type: object
properties:
domains:
type: array
items:
$ref: '#/components/schemas/Domain'
nextCursor:
type:
- string
- 'null'
description: Null on the last page.
required:
- domains
- nextCursor
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/RateLimited'
post:
operationId: createDomain
summary: Connect a custom domain
description: 'Registers the hostname, runs a preflight check, and returns the DNS records
the domain owner has to create. The domain stays `pending` until those records
resolve; SSL is issued automatically once they do.
Requires an active subscription on the workspace even while inside the free
tier, so a card must be on file.'
tags:
- Domains
parameters:
- $ref: '#/components/parameters/IdempotencyKey'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
hostname:
type: string
minLength: 1
maxLength: 253
description: The customer's hostname. Normalized and validated server-side.
example: app.acme.com
originUrl:
type: string
format: uri
description: Where verified traffic should go.
example: https://acme.vercel.app
metadata:
type: object
additionalProperties: true
mode:
type: string
enum:
- proxy
- redirect
default: proxy
keepHost:
type: boolean
redirectWww:
type: boolean
redirectStatus:
type: integer
enum:
- 301
- 302
required:
- hostname
- originUrl
responses:
'201':
description: Domain created. Follow `dnsRecords` to finish verification.
content:
application/json:
schema:
type: object
properties:
domain:
$ref: '#/components/schemas/Domain'
warnings:
type: array
items:
$ref: '#/components/schemas/PreflightWarning'
description: Non-blocking preflight notes, e.g. an existing CAA record.
required:
- domain
'400':
description: Invalid hostname, reserved hostname, or a blocking preflight failure.
content:
application/json:
schema:
$ref: '#/components/schemas/PreflightError'
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/BillingRequired'
'409':
description: That hostname is already connected.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
$ref: '#/components/responses/RateLimited'
/v1/domains/{id}:
parameters:
- $ref: '#/components/parameters/DomainId'
get:
operationId: getDomain
summary: Retrieve one domain
tags:
- Domains
responses:
'200':
$ref: '#/components/responses/DomainEnvelope'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
patch:
operationId: updateDomain
summary: Update a domain's origin or routing options
description: Only the fields you send change. The hostname itself is immutable.
tags:
- Domains
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
originUrl:
type: string
format: uri
metadata:
type: object
additionalProperties: true
mode:
type: string
enum:
- proxy
- redirect
keepHost:
type: boolean
redirectWww:
type: boolean
redirectStatus:
type: integer
enum:
- 301
- 302
minProperties: 1
responses:
'200':
$ref: '#/components/responses/DomainEnvelope'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
delete:
operationId: deleteDomain
summary: Disconnect a domain
description: Removes the edge configuration and the certificate. Not reversible.
tags:
- Domains
responses:
'200':
$ref: '#/components/responses/Deleted'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
/v1/domains/{id}/check:
parameters:
- $ref: '#/components/parameters/DomainId'
post:
operationId: checkDomain
summary: Re-run DNS and SSL checks now
description: Forces the monitor to refresh instead of waiting for its next pass. Use it right after telling a customer to add their records.
tags:
- Domains
responses:
'200':
$ref: '#/components/responses/DomainEnvelope'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
/v1/domains/{id}/instructions:
parameters:
- $ref: '#/components/parameters/DomainId'
get:
operationId: getConnectInstructions
parameters:
- name: redirect_uri
in: query
required: false
schema:
type: string
description: Where the customer lands after approving a Domain Connect flow at their provider, with state appended to its query string. Cloudflare accepts any URL; other providers may require the domainee.dev redirect domain declared in the templates, so when opening applyUrl in a popup you can omit it and call POST /v1/domains/{id}/check when the customer returns.
- name: state
in: query
required: false
schema:
type: string
description: Opaque value round-tripped through the Domain Connect redirect.
summary: DNS steps for the customer's actual DNS provider
description: 'Fingerprints the DNS provider from the zone''s public NS records and returns steps written for it: the right record type for this position and provider, the record name written the way that provider''s form wants it (@ vs blank vs the full domain), where the record editor lives in its UI, and the provider-specific gotchas that silently break a connection. Also reports whether the provider supports one-click Domain Connect, in which case domainConnect.applyUrl sends the customer to approve the change at their own provider. Does live NS and Domain Connect lookups, so it is a separate endpoint rather than a field on the domain resource. Never fails: an unrecognised provider returns provider.detected=false with correct generic steps.'
tags:
- Domains
responses:
'200':
description: Provider-aware connect instructions.
content:
application/json:
schema:
type: object
properties:
instructions:
type: object
properties:
hostname:
type: string
provider:
type: object
properties:
id:
type: string
example: cloudflare
name:
type: string
example: Cloudflare
detected:
type: boolean
apex:
type: boolean
method:
type: string
enum:
- a
- cname
methodReason:
type: string
records:
type: array
items:
type: object
properties:
type:
type: string
enum:
- A
- CNAME
name:
type: string
value:
type: string
ttl:
type: integer
steps:
type: array
items:
type: string
quirks:
type: array
items:
type: string
dashboardUrl:
type: string
domainConnect:
type: object
description: One-click setup via Domain Connect when the customer's provider supports it and has adopted Domainee's template. supported=false carries a reason instead; always render steps regardless, since coverage is far from universal.
properties:
supported:
type: boolean
reason:
type: string
enum:
- no_discovery_record
- settings_unavailable
- no_sync_support
- template_not_adopted
- lookup_failed
providerId:
type: string
providerName:
type: string
providerDisplayName:
type: string
description: The provider's own display name when it publishes one. Prefer it over providerName in UI.
applyUrl:
type: string
description: Send the customer here to approve the change at their own DNS provider.
width:
type: integer
height:
type: integer
urlControlPanel:
type: string
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
/v1/domains/{id}/connect-session:
parameters:
- $ref: '#/components/parameters/DomainId'
post:
operationId: createConnectSession
summary: Mint a browser-safe token for the embeddable widget
description: 'Returns a short-lived token scoped to this one domain, safe to hand to a browser. Call it from your server with your API key, then pass the token to Domainee.mount(). The token can only read the domain, read its connect instructions, force a DNS check, and apply records with a customer-supplied DNS token; everything else returns 403, including other domains and minting another session. Tokens are signed rather than stored, so they cannot be revoked before expiry: keep ttlSeconds short. Default 3600, maximum 86400. Never send an sk_live_ key to a browser.'
tags:
- Domains
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
ttlSeconds:
type: integer
minimum: 60
maximum: 86400
default: 3600
responses:
'200':
description: A scoped, short-lived widget token.
content:
application/json:
schema:
type: object
properties:
connectSession:
type: object
properties:
token:
type: string
expiresAt:
type: string
format: date-time
domainId:
type: string
hostname:
type: string
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
/v1/domains/{id}/dns/apply:
parameters:
- $ref: '#/components/parameters/DomainId'
post:
operationId: applyDnsWithToken
summary: Point a domain using the customer's own scoped DNS token
description: 'Writes the records at the customer''s DNS provider using a scoped API token they supply. The token is used for the request and discarded: never stored, never logged, never returned. Send it in the body, not the query string, so it stays out of access logs. Touches only conflicting A/AAAA/CNAME/ALIAS records at the hostname being pointed, leaving MX, TXT and every other name intact. On Cloudflare, records are created with proxy disabled, since an orange-clouded record prevents certificate issuance. Supported providers: cloudflare, digitalocean.'
tags:
- Domains
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
provider:
type: string
enum:
- cloudflare
- digitalocean
token:
type: string
description: The customer's scoped DNS API token. Cloudflare needs Zone:Zone:Read and Zone:DNS:Edit; DigitalOcean needs write scope on Domains.
required:
- provider
- token
responses:
'200':
description: Records written and the domain re-probed.
content:
application/json:
schema:
type: object
properties:
applied:
type: object
properties:
provider:
type: string
written:
type: array
items:
type: object
removed:
type: integer
description: Conflicting records deleted to make room.
domain:
$ref: '#/components/schemas/Domain'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'422':
description: The provider refused the write, or we can't write to it.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
Domain:
type: object
description: A customer hostname connected to one of your origins.
properties:
id:
type: string
description: Domainee identifier.
example: dom_9f2c
workspaceId:
type: string
description: Workspace the domain belongs to.
hostname:
type: string
example: app.acme.com
originUrl:
type: string
format: uri
description: Where verified traffic is sent.
example: https://acme.vercel.app
metadata:
type: object
additionalProperties: true
description: Opaque key/value bag echoed back on reads and webhooks.
mode:
type: string
enum:
- proxy
- redirect
description: '`proxy` serves the origin under the custom hostname; `redirect` sends a 301/302.'
keepHost:
type: boolean
description: Forward the original Host header to the origin instead of rewriting it.
redirectWww:
type: boolean
description: Also answer for the www. variant.
redirectStatus:
type: integer
enum:
- 301
- 302
status:
type: string
enum:
- pending
- verified
- failed
- expired
description: Verification state. Traffic only flows once `verified`.
dnsRecords:
type: array
description: The records the domain owner must create for verification to pass. Always the single CNAME. Unchanged and safe to keep rendering as-is; read `dnsRecordOptions` if you want to offer the apex A-record path.
items:
$ref: '#/components/schemas/DnsRecordInstruction'
dnsRecordOptions:
type: array
description: 'Every supported way to point this hostname at the edge, recommended first — take `[0]` to get the right one. For a subdomain that is the `cname` set. For an apex it is the `a` set: a root domain cannot hold a CNAME, and the ALIAS/ANAME/flattening substitutes all pin the name to a single region with no failover, whereas the anycast A records keep every region.'
items:
$ref: '#/components/schemas/DnsRecordSet'
verificationToken:
type: string
verifiedAt:
type:
- string
- 'null'
format: date-time
lastCheckedAt:
type:
- string
- 'null'
format: date-time
isResolving:
type: boolean
description: The hostname resolves in public DNS.
pointsToEdge:
type: boolean
description: It resolves to Domainee's edge.
dnsPointedAt:
type: array
items:
type: string
description: What the hostname currently resolves to.
sslActiveFrom:
type:
- string
- 'null'
format: date-time
sslActiveUntil:
type:
- string
- 'null'
format: date-time
monitorStatus:
type: string
enum:
- unknown
- dns_not_resolving
- dns_incorrect
- pending_ssl
- active_ssl
- target_not_loading
- ssl_failed
- ssl_expired
description: Live health, refreshed by the DNS monitor and by POST /v1/domains/{id}/check.
monitorMessage:
type:
- string
- 'null'
lastMonitoredAt:
type:
- string
- 'null'
format: date-time
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
required:
- id
- workspaceId
- hostname
- originUrl
- status
- createdAt
PreflightError:
type: object
description: A rejected domain creation. `warnings` lists the specific DNS or CAA problems found.
properties:
error:
type: string
example: preflight_failed
message:
type: string
warnings:
type: array
items:
$ref: '#/components/schemas/PreflightWarning'
required:
- error
- message
DnsRecordInstruction:
type: object
description: A record the domain owner must create for verification to pass.
properties:
name:
type: string
example: app.acme.com
value:
type: string
example: edge.domainee.dev
type:
type: string
example: CNAME
purpose:
type: string
example: Traffic Routing
required:
- name
- value
- type
Error:
type: object
description: The error envelope used by every authenticated endpoint.
properties:
error:
type: string
description: Stable machine-readable code.
example: not_found
message:
type: string
description: Human-readable explanation.
required:
- error
- message
PreflightWarning:
type: object
properties:
code:
type: string
enum:
- caa_blocks_lets_encrypt
- dns_unresolvable
- origin_unreachable
- origin_ssrf_blocked
message:
type: string
caaRecords:
type: array
items:
type: string
description: Present on `caa_blocks_lets_encrypt`.
details:
type: string
description: May be present on `origin_unreachable`.
resolvedIps:
type: array
items:
type: string
description: Present on `origin_ssrf_blocked`.
required:
- code
- message
DnsRecordSet:
type: object
description: One way of pointing the hostname at the edge. Every record in `records` must be created for the set to work.
properties:
method:
type: string
enum:
- cname
- a
description: '`cname` works wherever the provider allows a CNAME at this position. `a` is for apex/root domains on providers with no ALIAS or ANAME support.'
label:
type: string
example: A records
recommended:
type: boolean
note:
type: string
records:
type: array
items:
$ref: '#/components/schemas/DnsRecordInstruction'
required:
- method
- label
- recommended
- records
parameters:
DomainId:
name: id
in: path
required: true
description: Domain identifier returned by `createDomain`.
schema:
type: string
IdempotencyKey:
name: Idempotency-Key
in: header
required: false
description: Any unique string. Retrying with the same key replays the first response instead of repeating the work.
schema:
type: string
responses:
NotFound:
description: No such object in this workspace.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
RateLimited:
description: More than 60 requests in a minute for this key.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
DomainEnvelope:
description: The domain.
content:
application/json:
schema:
type: object
properties:
domain:
$ref: '#/components/schemas/Domain'
required:
- domain
Unauthorized:
description: Missing, malformed, revoked, or expired Bearer token.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Deleted:
description: Deleted.
content:
application/json:
schema:
type: object
properties:
deleted:
type: boolean
required:
- deleted
BillingRequired:
description: The workspace has no active subscription or the charge failed.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
BadRequest:
description: The request body or query failed validation.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: An API key (`sk_live_…`) from https://domainee.dev/developers or an OAuth access token (`dmn_oat_…`). Both are workspace-scoped.
externalDocs:
description: Domainee documentation
url: https://domainee.dev/docs