Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Customdomain Ai Agents API
version: '1.0'
license:
name: Apache-2.0
identifier: Apache-2.0
description: 'Operations tagged Agents across 2 of this provider''s published API definitions: customdomain-ai-openapi-original.json, customdomain-ai-openapi.yml. Each path carries the servers of the definition it was published in.'
servers:
- url: https://api.customdomain.ai/v1
description: Hosted control-plane
- url: http://localhost:8080/v1
description: Local control-plane
security:
- apiKey: []
tags:
- name: Agents
description: AI assistants acting for a workspace. An agent registers, a person approves what it may do in the console, and it works with a short lived token until the grant is revoked. The hosted MCP server accepts these tokens. The authorization server metadata (RFC 8414) and the signing keys (RFC 7517) are served on the API origin at `/.well-known/oauth-authorization-server` and `/.well-known/jwks.json`.
paths:
/oauth/agent/register:
servers:
- url: https://api.customdomain.ai
description: Hosted control-plane (the origin, not /v1)
post:
operationId: registerAgentClient
summary: Register an agent as an OAuth client
tags:
- Agents
description: 'Dynamic Client Registration (RFC 7591). An AI assistant registers itself as a public client (PKCE, no secret) and can do nothing until a person approves a grant for it in the console. Registration is open on purpose, so it is bounded: a per-IP hourly budget on top of the global limit, and a client that nobody ever approves into a workspace is deleted after a while. Other client metadata (`client_uri`, `logo_uri`, `scope`, `contacts`, ...) is accepted and ignored. A `client_name` that claims a well known brand (Claude, ChatGPT, ...) is refused unless every redirect host belongs to that brand or is a loopback address. Errors use the RFC 7591 envelope.'
security: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AgentClientRegistration'
responses:
'201':
description: The registered client
content:
application/json:
schema:
$ref: '#/components/schemas/AgentClient'
'400':
description: '`invalid_client_metadata` (a bad or impersonating name, grant or response types without the authorization code grant) or `invalid_redirect_uri` (none, more than 10, a fragment, user information, or http on a host that is not loopback).'
content:
application/json:
schema:
$ref: '#/components/schemas/OAuthError'
'429':
$ref: '#/components/responses/Error'
'503':
$ref: '#/components/responses/Error'
/oauth/agent/authorize:
servers:
- url: https://api.customdomain.ai
description: Hosted control-plane (the origin, not /v1)
get:
operationId: authorizeAgent
summary: Start an agent authorization (code with PKCE)
tags:
- Agents
description: The authorization endpoint. A browser is sent here by an agent; the request is validated (the client, the exact redirect URI, S256 PKCE, the scopes, the resource) and the browser is redirected to the console, where a signed in owner or admin sees what the agent asks for and where its code would go, and approves or denies. The authorization code goes to the redirect URI and is exchanged at `POST /oauth/agent/token`. A request with an unregistered or mismatched redirect URI is refused without redirecting. `client_id` is a registered client or an https URL of a Client ID Metadata Document.
security: []
parameters:
- name: client_id
in: query
required: true
schema:
type: string
- name: redirect_uri
in: query
required: true
schema:
type: string
format: uri
- name: response_type
in: query
required: true
schema:
type: string
enum:
- code
- name: code_challenge
in: query
required: true
description: BASE64URL(SHA-256(code_verifier)).
schema:
type: string
- name: code_challenge_method
in: query
required: true
schema:
type: string
enum:
- S256
- name: scope
in: query
required: true
description: Space separated scopes from `domains:read`, `domains:connect`, `domains:disconnect` and `domains:purchase`. The person may approve fewer.
schema:
type: string
example: domains:read domains:connect
- name: state
in: query
required: false
schema:
type: string
- name: resource
in: query
required: false
description: 'RFC 8707. The resource the token is for: the MCP endpoint (`https://mcp.customdomain.ai/mcp`, the default) or this API''s origin. Anything else is `invalid_target`. The access token carries it as `aud`.'
schema:
type: string
format: uri
responses:
'302':
description: Redirect to the console consent screen
headers:
Location:
schema:
type: string
format: uri
'400':
$ref: '#/components/responses/Error'
'503':
$ref: '#/components/responses/Error'
/oauth/agent/token:
servers:
- url: https://api.customdomain.ai
description: Hosted control-plane (the origin, not /v1)
post:
operationId: exchangeAgentToken
summary: Exchange an authorization code or a refresh token for an access token
tags:
- Agents
description: '`grant_type=authorization_code` (with the PKCE `code_verifier`) or `grant_type=refresh_token`. The access token is an ES256 JWT that lives 15 minutes and is verified locally against `/.well-known/jwks.json`; it carries the tenant, the application the grant is bound to, the grant, the scopes and `aud`. A refresh token rotates: every refresh returns a new one and retires the old, and presenting a retired one again revokes the whole grant (a client racing itself within ten seconds is only refused). Errors use the RFC 6749 envelope.'
security: []
requestBody:
required: true
content:
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/AgentTokenRequest'
application/json:
schema:
$ref: '#/components/schemas/AgentTokenRequest'
responses:
'200':
description: The tokens
content:
application/json:
schema:
$ref: '#/components/schemas/AgentTokenResponse'
'400':
description: '`invalid_request`, `invalid_grant` (a code or refresh token that is invalid, expired, spent or revoked, a failed PKCE check, a grant that is no longer active), `invalid_target` (a resource that is not the one approved) or `unsupported_grant_type`.'
content:
application/json:
schema:
$ref: '#/components/schemas/OAuthError'
'503':
description: Agent access is not enabled on this deployment (`temporarily_unavailable`).
content:
application/json:
schema:
$ref: '#/components/schemas/OAuthError'
/oauth/agent/revoke:
servers:
- url: https://api.customdomain.ai
description: Hosted control-plane (the origin, not /v1)
post:
operationId: revokeAgentToken
summary: Revoke an agent's access with its refresh token
tags:
- Agents
description: RFC 7009. Revokes the grant behind a refresh token, so the agent's access ends within one access token lifetime (15 minutes at most). It answers `200` for a token it does not know, so it cannot be used to find out which tokens exist. The workspace's owners and admins are told once.
security: []
requestBody:
required: true
content:
application/x-www-form-urlencoded:
schema:
type: object
required:
- token
properties:
token:
type: string
description: The refresh token.
application/json:
schema:
type: object
required:
- token
properties:
token:
type: string
description: The refresh token.
responses:
'200':
description: Revoked, or the token was not known
'400':
description: '`invalid_request`: no token.'
content:
application/json:
schema:
$ref: '#/components/schemas/OAuthError'
'503':
description: Agent access is not enabled on this deployment.
content:
application/json:
schema:
$ref: '#/components/schemas/OAuthError'
/agent/consent-request/{id}:
get:
operationId: getAgentConsentRequest
summary: Read a pending agent authorization, for the consent screen
tags:
- Agents
description: 'What the console shows a signed in owner before they approve an agent: the client''s name, where its authorization code would go (`redirect_host`), whether that destination is a known assistant''s own (`verified`, `verified_as`) or the person''s own computer (`local`), whether the name imitates a brand it does not belong to (`imitates`; such a request cannot be approved), how the client identified itself, and the scopes asked for. The unguessable request id is the capability: it needs no other credential and reveals only this.'
security: []
parameters:
- $ref: '#/components/parameters/Id'
responses:
'200':
description: The pending request
content:
application/json:
schema:
$ref: '#/components/schemas/AgentConsentRequest'
'404':
$ref: '#/components/responses/Error'
'503':
$ref: '#/components/responses/Error'
delete:
operationId: cancelAgentConsentRequest
summary: Deny a pending agent authorization
tags:
- Agents
description: 'Consumes the request so it can no longer be approved, from this tab or any other. Idempotent, and like the read it needs no credential: the request id is the capability, and cancelling is all it allows.'
security: []
parameters:
- $ref: '#/components/parameters/Id'
responses:
'204':
description: Denied
'503':
$ref: '#/components/responses/Error'
servers:
- url: https://api.customdomain.ai/v1
description: Hosted control-plane
- url: http://localhost:8080/v1
description: Local control-plane
/agent/consent:
post:
operationId: approveAgent
summary: Approve an agent and mint its authorization code
tags:
- Agents
description: 'Called by the console''s server after a person signs in and approves, with that member''s tenant API key (a widget token is refused, because it lives in browsers). The key''s member needs `agent:manage` (owners and admins), and `member_id` must belong to the workspace. The approved scopes are a subset of what was requested. The grant is bound to one `application_id` of the workspace. An agent whose code goes to an unverified host needs `acknowledged_unverified: true`, and one whose name imitates a brand is refused (`403`). `domains:purchase` also needs a `spend_cap_cents` and a saved card. Persistent access is the default; `persist: false` ends it after `expires_days`. The authorization code is single use and is exchanged at `POST /oauth/agent/token`.'
security:
- apiKey: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AgentConsentApproval'
responses:
'200':
description: The redirect for the agent, with its code
content:
application/json:
schema:
$ref: '#/components/schemas/AgentConsentResult'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Error'
'403':
$ref: '#/components/responses/Error'
'409':
$ref: '#/components/responses/Error'
'503':
$ref: '#/components/responses/Error'
servers:
- url: https://api.customdomain.ai/v1
description: Hosted control-plane
- url: http://localhost:8080/v1
description: Local control-plane
/agent/grants:
get:
operationId: listAgentGrants
summary: List the workspace's agent grants
tags:
- Agents
description: Every grant of the workspace with the current month's spend, for the console's manage screen. Needs a tenant API key held by an owner or admin (`agent:manage`). Refresh tokens are never returned.
security:
- apiKey: []
responses:
'200':
description: The grants
content:
application/json:
schema:
$ref: '#/components/schemas/AgentGrantList'
'401':
$ref: '#/components/responses/Error'
'403':
$ref: '#/components/responses/Error'
'503':
$ref: '#/components/responses/Error'
servers:
- url: https://api.customdomain.ai/v1
description: Hosted control-plane
- url: http://localhost:8080/v1
description: Local control-plane
/agent/grants/{id}:
delete:
operationId: revokeAgentGrant
summary: Revoke an agent's access
tags:
- Agents
description: Ends the grant and kills its refresh tokens; the agent's access token stops working within its 15 minute life. A grant of another workspace answers `404`. The workspace's owners and admins are told once (a repeat is quiet), and the audit log records `agent.revoked`. Needs `agent:manage`.
security:
- apiKey: []
parameters:
- $ref: '#/components/parameters/Id'
responses:
'204':
description: Revoked
'401':
$ref: '#/components/responses/Error'
'403':
$ref: '#/components/responses/Error'
'404':
$ref: '#/components/responses/Error'
'503':
$ref: '#/components/responses/Error'
servers:
- url: https://api.customdomain.ai/v1
description: Hosted control-plane
- url: http://localhost:8080/v1
description: Local control-plane
components:
schemas:
AgentClient:
type: object
required:
- client_id
- client_id_issued_at
- client_name
- redirect_uris
- token_endpoint_auth_method
- grant_types
- response_types
properties:
client_id:
type: string
description: Starts with `agc_`.
client_id_issued_at:
type: integer
format: int64
description: Unix time of the registration.
client_name:
type: string
redirect_uris:
type: array
items:
type: string
description: The registered URIs, normalized.
token_endpoint_auth_method:
type: string
enum:
- none
grant_types:
type: array
items:
type: string
examples:
- - authorization_code
- refresh_token
response_types:
type: array
items:
type: string
examples:
- - code
AgentConsentResult:
type: object
required:
- redirect_uri
- code
- grant_id
properties:
redirect_uri:
type: string
description: 'Where to send the person: the agent''s redirect URI with `code` and `state` added.'
code:
type: string
description: The single use authorization code. It lives 5 minutes.
state:
type: string
grant_id:
type: string
OAuthError:
type: object
required:
- error
description: The RFC 6749 and RFC 7591 error envelope the token, revoke and register endpoints answer with (the `{code, title, details}` envelope of the rest of the API is not used there).
properties:
error:
type: string
description: '`invalid_request`, `invalid_grant`, `invalid_target`, `unsupported_grant_type`, `invalid_client_metadata`, `invalid_redirect_uri`, `server_error` or `temporarily_unavailable`.'
error_description:
type: string
AgentScope:
type: string
enum:
- domains:read
- domains:connect
- domains:disconnect
- domains:purchase
description: 'What an agent grant may do. `domains:read` looks: availability, connections, records, certificates, diagnoses, orders and the billing standing. `domains:connect` changes: connect a domain, recheck it, reapply its records, start a one click setup. `domains:disconnect` removes a connected domain, and the call must name the domain. `domains:purchase` spends, up to the grant''s monthly cap.'
AgentTokenResponse:
type: object
required:
- access_token
- token_type
- expires_in
properties:
access_token:
type: string
description: An ES256 JWT. Verify it against `/.well-known/jwks.json`.
token_type:
type: string
enum:
- Bearer
expires_in:
type: integer
description: Seconds the access token lives (900).
refresh_token:
type: string
description: A new refresh token. The one just used no longer works.
scope:
type: string
description: The scopes the grant carries, separated by spaces.
Error:
type: object
required:
- code
- title
properties:
code:
type: string
description: stable machine code, e.g. invalid_request, unauthorized, not_found, service_unavailable, internal_error
title:
type: string
description: human-readable summary
details:
type: string
description: optional extra context
AgentConsentApproval:
type: object
required:
- request
- application_id
- scopes
description: The body is decoded with unknown fields rejected.
properties:
request:
type: string
description: The consent request id the browser arrived with (`aqr_...`).
member_id:
type: string
description: The member who approves, recorded as the grant's approver, the email recipient and the audit actor. It must belong to the workspace. With a member's own key it must be that member (`403` otherwise) and may be left out; with any other key it is required.
application_id:
type: string
description: The one application of the workspace the agent will act within.
scopes:
type: array
minItems: 1
items:
$ref: '#/components/schemas/AgentScope'
description: What is approved, a subset of what the agent asked for.
can_purchase:
type: boolean
deprecated: true
description: Ignored. Purchase is allowed exactly when `domains:purchase` is among the scopes.
spend_cap_cents:
type: integer
minimum: 0
description: The monthly spend cap in US cents. Required, and above 0, with `domains:purchase`, which also needs a saved card (`409` without one).
persist:
type: boolean
default: true
description: True (the default) keeps the access until it is revoked. False ends it after `expires_days`.
expires_days:
type: integer
minimum: 0
default: 30
description: 'With `persist: false`, days until the grant expires.'
acknowledged_unverified:
type: boolean
default: false
description: The approver confirmed they recognize the host the authorization code goes to. Required (`true`) when that host is neither a known assistant's (`verified`) nor a loopback address (`local`) in `GET /agent/consent-request/{id}`; without it such a consent is refused with `400`.
AgentGrant:
type: object
required:
- id
- tenant_id
- agent_client_id
- agent_name
- scopes
- can_purchase
- spend_cap_cents
- created_at
- spend_this_period_cents
- period
properties:
id:
type: string
description: Starts with `grt_`.
tenant_id:
type: string
agent_client_id:
type: string
application_id:
type: string
description: The application the grant is bound to.
authorized_by:
type: string
description: The member who approved it.
agent_name:
type: string
scopes:
type: array
items:
$ref: '#/components/schemas/AgentScope'
can_purchase:
type: boolean
description: True when the grant carries `domains:purchase`.
spend_cap_cents:
type: integer
description: The monthly spend cap in US cents (0 without purchase access).
expires_at:
type: string
format: date-time
description: Absent for persistent access.
created_at:
type: string
format: date-time
revoked_at:
type: string
format: date-time
description: Present once the access was ended.
spend_this_period_cents:
type: integer
description: Spent against the cap this month.
period:
type: string
description: The month `spend_this_period_cents` counts, `YYYY-MM` (UTC).
AgentClientRegistration:
type: object
required:
- client_name
- redirect_uris
description: RFC 7591 client metadata. Anything else a client sends (`client_uri`, `logo_uri`, `scope`, `contacts` and so on) is accepted and ignored.
properties:
client_name:
type: string
maxLength: 100
description: Shown to the person who approves the agent. At most 100 characters, with no control or invisible characters and no Latin letters mixed with look-alike letters from another alphabet. A name that claims a well known brand is refused unless every redirect host belongs to that brand or is a loopback address.
redirect_uris:
type: array
minItems: 1
maxItems: 10
items:
type: string
format: uri
maxLength: 2000
description: Where the authorization code may be sent. `https`, or `http` on a loopback host (`localhost`, `127.0.0.1`, `::1`) for a program on the person's own computer. No fragment and no user information. A redirect URI is matched exactly later, except for the port of a loopback one.
grant_types:
type: array
items:
type: string
description: Optional. When sent it must include `authorization_code`; other values are ignored. The client is registered for `authorization_code` and `refresh_token`.
response_types:
type: array
items:
type: string
description: Optional. When sent it must include `code`.
token_endpoint_auth_method:
type: string
description: Ignored. Every agent client is a public client (`none`) and PKCE protects its code.
AgentGrantList:
type: object
required:
- grants
properties:
grants:
type: array
items:
$ref: '#/components/schemas/AgentGrant'
AgentTokenRequest:
type: object
required:
- grant_type
- client_id
properties:
grant_type:
type: string
enum:
- authorization_code
- refresh_token
client_id:
type: string
code:
type: string
description: 'With `authorization_code`: the single use code from the redirect. It lives 5 minutes. Presenting a code that already issued tokens is `invalid_grant` and revokes the grant they came from (RFC 6749 §4.1.2).'
code_verifier:
type: string
description: 'With `authorization_code`: the PKCE verifier for the `code_challenge` sent to authorize.'
redirect_uri:
type: string
format: uri
description: 'With `authorization_code`: exactly the `redirect_uri` sent to authorize.'
refresh_token:
type: string
description: 'With `refresh_token`: the latest refresh token. It is good for one exchange.'
resource:
description: RFC 8707. The resource the token is for. With `authorization_code` it must be the one the person approved; with `refresh_token` it picks the audience of the new access token (the MCP endpoint when omitted). In a form body repeat the field; in JSON send a string or a list.
oneOf:
- type: string
format: uri
- type: array
items:
type: string
format: uri
AgentConsentRequest:
type: object
required:
- client_id
- client_name
- redirect_uri
- scopes
- redirect_host
- verified
- local
- registration
properties:
client_id:
type: string
client_name:
type: string
redirect_uri:
type: string
description: Where the authorization code goes if the person approves.
scopes:
type: array
items:
$ref: '#/components/schemas/AgentScope'
description: What the agent asked for.
state:
type: string
description: The agent's own state value, echoed back to it.
redirect_host:
type: string
description: The host of `redirect_uri`. The client cannot choose it freely the way it chooses its name, so show it.
verified:
type: boolean
description: True when `redirect_host` is a known assistant's own callback host or a loopback address, and the name imitates no brand.
verified_as:
type: string
description: The known assistant `redirect_host` belongs to.
local:
type: boolean
description: True when the code goes to a program on the approver's own computer (a loopback redirect).
imitates:
type: string
description: The brand the name claims without owning the redirect host. Such a request cannot be approved.
registration:
type: string
enum:
- dynamic
- metadata_document
description: How the client identified itself, by RFC 7591 registration or by a Client ID Metadata Document.
registered_at:
type: string
format: date-time
description: When a dynamically registered client registered.
client_id_host:
type: string
description: The host that publishes a metadata document client's document.
responses:
Error:
description: 'Error envelope: {code, title, details}.'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
parameters:
Id:
name: id
in: path
required: true
schema:
type: string
securitySchemes:
managementKey:
type: http
scheme: bearer
description: Operator management key. Gates provisioning + reads across tenants.
provisionSecret:
type: apiKey
in: header
name: X-Provision-Secret
description: Narrow secret held by the customer console's server, for signup provisioning and its other server to server calls (also accepted as a Bearer token, except on POST /audit, where a Bearer is always a tenant credential).
apiKey:
type: http
scheme: bearer
description: Long-lived tenant API key, cd_live_… (production) or cd_test_… (staging, development), stored hashed. Keys created before the switch to cd_ start with sk_live_… / sk_test_… and are still accepted. Scoped to the owning application's tenant.
widgetJWT:
type: http
scheme: bearer
bearerFormat: JWT
description: Short-lived widget JWT minted server-side via POST /v1/tokens. Scoped to one application (and optionally one domain).
browserSession:
type: http
scheme: bearer
description: The session token of one cloud browser sign in, returned by `POST /browser-sessions:redeem`. It binds one session, one connection and one workspace, lives at most as long as the session, and stops working the moment the session ends. It lives in the popup's memory only.
agentToken:
type: oauth2
description: 'A delegated agent access token: an ES256 JWT that lives 15 minutes, sent as a bearer token. A person approves an agent in the console and the agent exchanges its authorization code (with PKCE) at the token endpoint. The token carries the workspace, the one application the grant is bound to, the grant, the approved scopes and its audience, and is verified against `/.well-known/jwks.json`. A route that accepts it lists the scope it needs; a token without the scope is refused with `403`, and a connection or order of another application answers `404`.'
flows:
authorizationCode:
authorizationUrl: https://api.customdomain.ai/oauth/agent/authorize
tokenUrl: https://api.customdomain.ai/oauth/agent/token
refreshUrl: https://api.customdomain.ai/oauth/agent/token
scopes:
domains:read: Look at availability, connections, records, certificates, diagnoses, orders and the billing standing.
domains:connect: Connect a domain, recheck it, reapply its records and start a one click setup.
domains:disconnect: Remove a connected domain. The call must name the domain.
domains:purchase: Buy a domain, up to the grant's monthly spend cap.
x-refined-from:
- customdomain-ai-openapi-original.json
- customdomain-ai-openapi.yml