Add Custom Domain

POST /v1/domains/self

Register a domain you own with the platform so you can host endpoints under it (e.g. api.example.com instead of myapp.ngris.io). Endpoints on a custom domain resolve through a single wildcard CNAME — *.<domain> CNAME cname.ngris.io — so once the domain is set up every endpoint subdomain works without a per-record DNS edit.

There are two provisioning_modes. In auto (default) you supply your DNS-provider credentials; the platform writes the CNAME and issues + auto-renews the wildcard HTTPS certificate via DNS-01. In manual you add the wildcard CNAME yourself, call Validate CNAME, and upload your own certificate; no credentials are stored.

Manual mode requires proof of ownership first. GET /v1/domains/self/verification?name=<domain> returns {domain, type:"TXT", name:"_ngris-verify.<domain>", value:"ngris-verify=<token>"} — the token is unique to your account and domain and never changes, so you can publish it and create the domain later. Publish that TXT record at your DNS provider, then call this endpoint with "provisioning_mode": "manual". The record is read from the domain's authoritative nameservers; until it is found the request returns 400 with "code": "domain_txt_not_verified" (and the record to publish in verification), nothing is created and the name is not reserved.

This is a plan-gated feature — your plan must include the custom_domains entitlement, and there's a per-plan max-count cap (-1 means unlimited). A separate verification gate (e.g. payment-method-on-file) may apply.

In auto mode provider credentials are validated before the domain row is created — if the platform can't reach Cloudflare/Route53/etc with the supplied token, you get a 400 with the specific error and nothing is saved. Once stored, credentials are encrypted at rest with the platform's cert encryption key.

Mutation endpoints (POST/PUT/DELETE under /domains/self) are rate-limited to 10/min/user to prevent runaway DNS-provider calls.

Request body

name string Req
Fully-qualified domain. Lowercased + trimmed server-side. Must match ^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$.
provider string
DNS provider slug (auto mode). Supported: cloudflare, route53, digitalocean, cpanel, webmin, namecheap. Defaults to none. Ignored in manual mode (forced to none).
provider_config string (json)
JSON-encoded provider credentials. Schema is provider-specific:
• cloudflare: {"api_token": "...", "zone_id": "..."}
• route53: {"access_key_id": "...", "secret_access_key": "...", "hosted_zone_id": "..."}
• digitalocean: {"api_token": "...", "domain": "..."}
Encrypted at rest before storage.
plan_access string
Default all. Admin-only plan-restriction marker; for end users this stays all.
provisioning_mode string
How the domain's DNS is managed. auto (default) — you supply DNS-provider credentials and the platform provisions the CNAME and issues + auto-renews the HTTPS certificate via DNS-01. manual — you add a wildcard CNAME (*.<domain> CNAME cname.ngris.io) at your own provider, validate it (see Validate CNAME), and upload your own certificate; no credentials are stored and the cert does not auto-renew. In manual mode provider/provider_config are ignored and the domain starts at status pending_cname.

Response (202 Accepted)

ok boolean
Always true on success.
uuid string (uuid)
The domain's uuid: used in URL paths (/domains/self/{uuid}) and as domain_uuid when creating endpoints.
status string
pending_issuance (auto mode — wildcard certificate issuance queued) or pending_cname (manual mode — awaiting your CNAME + validation).
provisioning_mode string
Echo of the resolved mode — auto or manual.
cname_target string
manual mode only — the CNAME target to point *.<domain> at (e.g. cname.ngris.io). Add this record at your DNS provider, then call Validate CNAME.
message string
Human-readable next step for the mode.

Deprecated: until the sunset this response also carries the legacy integer id (and, in auto mode, an internal job_id). Don't use them; read uuid and follow issuance with ACME status.

Apex domains. The wildcard CNAME covers subdomains only — a CNAME is illegal at a bare apex. If you serve the apex (@), add an A record to the apex fallback IP shown in your dashboard instead (auto mode writes it for you when one is configured).

Errors

400 Bad Request
"code": "domain_txt_not_verified" (manual mode) — the ownership TXT record isn't on the domain's authoritative nameservers yet; the body's verification object is the record to publish. Also: invalid JSON, unknown body fields (strict decoding), missing/empty name, malformed domain, or provider-credential validation failure (e.g. wrong Cloudflare token, zone-ID mismatch — exact message returned).
401 Unauthorized
Missing auth.
403 Forbidden
Plan doesn't include custom_domains, max-count limit reached, or verification gate not satisfied. Verification failures return {"error", "gate", "feature"} JSON.
409 Conflict
The domain can't be added: the name is already registered (by any account, including yours — a domain can only be added once across the platform), it is, contains or sits inside a platform domain, or it sits inside another account's domain. Every case returns the same body, which never says who holds the name: {"error": "This domain can't be added. If you own it, contact support.", "fields": {"name": "This domain can't be added. If you own it, contact support."}}.
429 Too Many Requests
10 mutations/min/user limit on the /domains/self subrouter.
502 Bad Gateway
Platform couldn't reach the DNS provider during validation (network failure, not a credentials issue).
Request
curl -X POST "https://api.ngris.com/v1/domains/self" \ -H "X-API-KEY: <your_api_key>" \ -H "Content-Type: application/json" \ -d '{ "name": "example.com", "provider": "cloudflare", "provider_config": "{\"api_token\":\"<cf-token>\",\"zone_id\":\"<zone-id>\"}" }'
Python
import json from ngris import Ngris client = Ngris(api_key="<your_api_key>") domain = client.domains.create({ "name": "example.com", "provider": "cloudflare", "provider_config": json.dumps({ "api_token": "<cf-token>", "zone_id": "<zone-id>", }), }) print("Domain", domain.uuid, "status:", domain.status)
Response — 202 Accepted (auto)
{ "ok": true, "uuid": "019bd08f-98db-71b2-8e54-f518d319f111", "status": "pending_issuance", "provisioning_mode": "auto", "message": "Domain created; certificate issuance in progress." }
Request — manual mode
curl -X POST "https://api.ngris.com/v1/domains/self" \ -H "X-API-KEY: <your_api_key>" \ -H "Content-Type: application/json" \ -d '{ "name": "example.com", "provisioning_mode": "manual" }'
Response — 202 Accepted (manual)
{ "ok": true, "uuid": "019bd35f-0271-794a-8d2a-94ff3278e0a5", "status": "pending_cname", "provisioning_mode": "manual", "cname_target": "cname.ngris.io", "message": "Domain created. Add the CNAME at your DNS provider, then validate it." }
Next — after adding *.example.com CNAME cname.ngris.io at your DNS provider
curl -X POST "https://api.ngris.com/v1/domains/self/019bd35f-0271-794a-8d2a-94ff3278e0a5/validate-cname" \ -H "X-API-KEY: <your_api_key>" \ -H "Content-Type: application/json"
Was this helpful?
On this page
Add Custom Domain
Was this helpful?