Cosmoner Docs
API Reference

Domains

The domains in a project. A domain is registered through Cosmoner, moved in from another registrar, or external — a domain whose DNS you keep hosting elsewhere. An external domain starts `PENDING`, and the project's apps can use it only once you have proven you control it: publish the TXT record Verify ownership of an external domain returns, then call it again until the record is found. Turning email on for the domain and verifying its records proves the same thing. Keep the record published for as long as the domain is used here: it is checked again regularly, and an `ACTIVE` domain whose record stays missing for about a week goes back to `PENDING`, which stops its apps serving it. While the record is missing the domain stays `ACTIVE`, its owners and admins are notified, and calling Verify ownership again clears the warning once the record is back. If another project proves control of the domain while this project's record is missing, this project's domain goes back to `PENDING` at once. Search shows which names can be registered and what each costs; see [Domain pricing](/docs/pricing/domains). Registering a domain and moving one in are done in the control panel. Three calls work with an API key but are not described operation by operation yet, because they answer with the domain's full internal record, which is not a stable contract: listing the project's domains (`GET /v1/projects/{projectId}/domains`, `domains:read`), reading one by id or name (`GET /v1/projects/{projectId}/domains/{domain}`, `domains:read`) and adding an external domain (`POST /v1/projects/{projectId}/domains` with `{ "name": "example.com", "type": "EXTERNAL" }`, `domains:write` — `EXTERNAL` is the only type it takes). Rely only on `id`, `name`, `type`, `status`, `createdAt`, `dnsRecords` and, for an external domain, `verificationRecord` — the TXT record to publish — and `verificationLapsesAt`, which is set while an `ACTIVE` domain's record is missing and gives the earliest time it goes back to `PENDING`, and expect the rest to change. Adding a domain is refused to members with the viewer role, and to a project blocked over an unpaid invoice.

GET
/v1/domains/search

Domain names matching q — the name itself and similar ones — and whether each can be registered through Cosmoner, at what price. Registering one is done in the control panel.

Auth: a project API key carrying domains:read.

Rate limit: this endpoint is rate limited; a 429 means back off and retry.

AuthorizationBearer <token>

A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.

In: header

Query Parameters

q*string
Length1 <= length <= 253

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/v1/domains/search?q=string"
{  "success": true,  "data": {    "results": [      {        "domain": "string",        "available": true,        "price": 0      }    ]  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
DELETE
/v1/projects/{projectId}/domains/{domain}

Removes the domain, and the DNS records stored for it, from the project.

A domain still in use is refused with 409: turn off email for it, move the apps that use it to another domain, and detach it from any web hosting site, first.

Removing a domain that was moved in also stops Cosmoner hosting its DNS. While its nameservers still point at Cosmoner that would take the domain offline, so it is refused with 409 too: set the nameservers of your new DNS host at your registrar first. If the nameservers cannot be looked up at that moment the request fails with 502; retry shortly.

Members with the viewer role are refused, as is a project blocked over an unpaid invoice (403 ACCOUNT_BLOCKED).

Auth: a project API key carrying all of domains:read and domains:write.

AuthorizationBearer <token>

A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.

In: header

Path Parameters

projectId*string
Length1 <= length
domain*string

The domain's id or its name, e.g. example.com.

Length1 <= length

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X DELETE "https://example.com/v1/projects/string/domains/string"
{  "success": true,  "data": {    "id": "string"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
GET
/v1/projects/{projectId}/domains/{domain}/dns

Asks public DNS, live, what records the domain has. Useful for an external domain, whose DNS is hosted elsewhere and so has no records stored here. Read-only: nothing is changed.

Public DNS cannot list a whole zone, so only the apex, a fixed set of common subdomains (www, mail, …) and _dmarc are looked up.

Auth: a project API key carrying domains:read.

AuthorizationBearer <token>

A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.

In: header

Path Parameters

projectId*string
Length1 <= length
domain*string

The domain's id or its name, e.g. example.com.

Length1 <= length

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/v1/projects/string/domains/string/dns"
{  "success": true,  "data": {    "records": [      {        "type": "string",        "name": "string",        "value": "string",        "ttl": -9007199254740991,        "priority": -9007199254740991      }    ]  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
POST
/v1/projects/{projectId}/domains/{domain}/verify

Checks public DNS for the TXT record that proves you control an external domain — one whose DNS you host elsewhere. Publish record at your DNS host, then call this. Once the record is found the domain becomes ACTIVE and can be used by the project's apps; until then it stays PENDING, and error says what was found instead. DNS changes take a while to propagate, so retrying a few times is normal. The response always carries the record to publish, so calling this before publishing anything tells you what to add.

Calling it on a domain that is already ACTIVE answers verified: true without looking again. A domain added before the move to the Cosmoner name may have published a _datablock-challenge record instead; that record is still accepted.

A domain being moved in from another DNS host proves ownership the same way, but finding the record does not make it ACTIVE. Cosmoner starts hosting its DNS and the response carries nameservers, the nameservers to set at your registrar; the domain stays PENDING until the registrar points at them, then becomes ACTIVE on its own. Calling this again returns the same nameservers. A move whose nameservers were not changed within two weeks expires, and is refused with 400 until it is started again from the control panel.

Members with the viewer role are refused, as is a project blocked over an unpaid invoice (403 ACCOUNT_BLOCKED).

Auth: a project API key carrying all of domains:read and domains:write.

Rate limit: this endpoint is rate limited; a 429 means back off and retry.

AuthorizationBearer <token>

A project API key. X-API-Key: <key> is accepted as an alternative to the Authorization header.

In: header

Path Parameters

projectId*string
Length1 <= length
domain*string

The domain's id or its name, e.g. example.com.

Length1 <= length

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/v1/projects/string/domains/string/verify"
{  "success": true,  "data": {    "status": "ACTIVE",    "verified": true,    "record": {      "type": "TXT",      "name": "string",      "value": "string"    },    "error": "string",    "nameservers": [      "string"    ]  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}
{  "success": false,  "error": {    "code": "string",    "message": "string",    "details": {      "property1": null,      "property2": null    },    "issues": null,    "docsUrl": "http://example.com"  }}

SSH keys

Public keys that authorise access to the servers in a project. A key is written to a server by cloud-init at first boot, so it must be registered *before* the server that should trust it is created.

Email

Sending email from your own domains, over SMTP or through Send an email. Turn email on for a domain in the project, publish the DKIM, SPF and DMARC records it returns, and verify them; then create an SMTP credential for each sender address. [SMTP Setup](/docs/guides/smtp-setup) walks through it and has the server settings. Send an email takes its content inline or from a stored template; see [Email templates](/docs/api/email-templates) and the [Email Templates guide](/docs/guides/email-templates). Every email sent is billed on the project's subscription, each recipient counting as one email, and there is no free allowance. Projects start on pay as you go, with no monthly fee; a plan includes a number of emails each month for a flat price and bills the emails beyond it at a lower rate. Change the email plan moves between them. See [Email pricing](/docs/pricing/email). Sending is held under three ceilings, all shown by Get send limits. The **monthly cap** is your budget — the most the project will send, and pay for, in one quota month — and you set it. The **hourly** and **daily limits** are burst guards that stop a runaway loop from spending the month in an afternoon; they are raised on request, after review. Reaching any of them pauses sending. An hourly or daily pause lifts on its own as the window moves on; a monthly one lifts when the next quota month begins, or as soon as the cap is raised above what has already been sent. The project's biller is emailed whenever sending is paused. Adding a domain whose DNS is hosted elsewhere for email, and turning email off for a domain, are done in the control panel for now.

On this page