Domains β DNS verification & create
Register a tenant domain in IBEx: request a DNS TXT challenge (no auth), publish the record, then call PUT /domains with a user JWT to verify DNS and obtain rpId, apiKey, and primaryRpId.
These endpoints are not under /v1.2 β they use the root path /domains/... on the same API host (e.g. https://passkeys-prat1.ibex.fi).
What you are proving
IBEx stores each partner app as a Domain row keyed by rpId (typically your WebAuthn / app hostname, e.g. app.example.com).
Before the row is trusted for production, you must prove control of DNS for that hostname using a one-time TXT token.
Step 1 β GET /domains/dns-challenge (public)
Auth: none β safe to call from a script or browser without a JWT.
Query
| Parameter | Required | Description |
|---|---|---|
domain | Yes | FQDN to verify (e.g. app.example.com). Lowercased server-side. |
Example
Response (200) β create this DNS TXT record at your DNS provider:
| Field | Meaning |
|---|---|
record | Hostname for the TXT record (e.g. _ibex-verify.app.example.com) |
value | Full TXT value to set (starts with ibex-verify= + random token) |
type | Always TXT |
ttl | Challenge lifetime in seconds (about 1 hour); token is stored in Redis |
The server stores the expected token until it expires. If you skip this step, PUT /domains will fail with βcall GET /domains/dns-challenge firstβ.
Step 2 β Publish the TXT record
- In your DNS zone, add a TXT record:
- Name / host: as returned in
record(often the subdomain_ibex-verify.<your-domain>). - Value: exactly the string in
value(including theibex-verify=prefix).
- Name / host: as returned in
- Wait for DNS propagation (minutes to hours depending on TTL and provider).
- Optionally verify with
dig TXT <record>or an online DNS checker before calling step 3.
Step 3 β PUT /domains (JWT)
Auth: Authorization: Bearer <access_token> for a user who should become admin of the new domain (the API connects that user to the Domain after creation).
Headers
| Header | Value |
|---|---|
Authorization | Bearer <access_token> |
Origin or rpId | Must allow the API to resolve an existing tenant context where applicable (same rules as other authenticated routes). |
Body (JSON)
| Field | Required | Description |
|---|---|---|
domain | Yes | Same FQDN you used in step 1 (after DNS is visible to the API server). |
primaryRpId | No | Omit to link the new domain to the default primary rpId configured on the server. Set to null for a standalone domain (no ROR link). Set to another existing standalone domainβs rpId for ROR (related origins). |
Example (standalone)
What the server does
- Loads the challenge for
domainfrom Redis (must still be valid). - Performs a DNS TXT lookup on
_ibex-verify.<domain>and checks that one of the TXT strings exactly matches the expectedibex-verify=<token>value. - Creates a new
Domain(or upgrades an auto-created unverified domain) withdnsVerified = true, setsprimaryRpIdfrom the rules above, and attaches the authenticated user as domain admin. - Returns:
| Field | Description |
|---|---|
rpId | Tenant identifier (same as domain when created fresh). |
apiKey | Tenant API key β store securely; used as x-api-key on API calls for that rpId. |
primaryRpId | Linked primary for ROR, or null if standalone. |
After creation
- Use
x-api-key: <apiKey>together with correctOrigin/rpIdresolution on versioned routes (/v1.2/...). - Optional:
GET /system/healthwithx-api-keyreturnsrpIdandsignerCountwhen the key is valid β handy to confirm the key without hitting a protected route (see internal system docs).
Common errors
| Symptom | Likely cause |
|---|---|
| No challenge / bad request | Did not call GET /domains/dns-challenge first, or challenge expired (~1 h). |
| DNS TXT not found | Propagation delay, wrong host, or record not created at public DNS. |
| Verification failed | TXT value mismatch (extra quotes, spaces, or wrong token). |
| Domain already exists | Domain row already verified β use admin flows to change settings instead. |
| JWT errors | Missing/expired token on PUT /domains only β step 1 stays public. |