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.

This is a plan-gated feature — your plan must include the custom_domains entitlement, and there's a per-plan max-count cap (0 = 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.
id int64
Numeric domain ID. Use as domain_id when creating endpoints.
uuid string
Stable UUID for use in URL paths (/domains/self/{uuid}).
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.
job_id int64
auto mode only — ID of the queued ACME issuance job (present when a job was enqueued).
message string
Human-readable next step for the mode.
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
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
Domain is already registered (by anyone — a single domain can only be owned once across the platform).
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, "id": 42, "uuid": "9f1a3c00-5b7f-4f12-8c46-2c7dab5f1234", "status": "pending_issuance", "provisioning_mode": "auto", "job_id": 1187, "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, "id": 43, "uuid": "b2c4e6a0-1234-4abc-9def-0011deadbeef", "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: add *.example.com CNAME cname.ngris.io at your DNS provider, # then POST /v1/domains/self/{uuid}/validate-cname
On this page
Add Custom Domain
Iris