Add Custom Domain
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
^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$.auto mode). Supported: cloudflare, route53, digitalocean, cpanel, webmin, namecheap. Defaults to none. Ignored in manual mode (forced to none).•
cloudflare: {"api_token": "...", "zone_id": "..."}•
route53: {"access_key_id": "...", "secret_access_key": "...", "hosted_zone_id": "..."}•
digitalocean: {"api_token": "...", "domain": "..."}Encrypted at rest before storage.
all. Admin-only plan-restriction marker; for end users this stays all.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)
true on success./domains/self/{uuid}) and as domain_uuid when creating endpoints.pending_issuance (auto mode — wildcard certificate issuance queued) or pending_cname (manual mode — awaiting your CNAME + validation).auto or manual.*.<domain> at (e.g. cname.ngris.io). Add this record at your DNS provider, then call Validate CNAME.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.
@), 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
"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).custom_domains, max-count limit reached, or verification gate not satisfied. Verification failures return {"error", "gate", "feature"} JSON.{"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."}}./domains/self subrouter.