OpusDNS API
Authentication
OpusDNS supports API authentication in two ways:
- Direct API key authentication using the
X-Api-Keyheader - OAuth token authentication using the
/v1/auth/tokenendpoint
Creating an API key in the Dashboard
- Create a new API key from the OpusDNS Dashboard under API Credentials
- Store the generated key securely when it is shown to you.
Using the API key
Option 1: X-Api-Key header
Send your full OpusDNS API key in the X-Api-Key header on each request:
GET /v1/domains HTTP/1.1
Host: sandbox.opusdns.com
X-Api-Key: opk_your_full_api_key_here
This is the most direct way to authenticate.
Option 2: OAuth token flow
OpusDNS also supports retrieving a bearer token from the token endpoint. The endpoint accepts both application/x-www-form-urlencoded (per the OAuth 2.0 spec) and application/json:
POST /v1/auth/token HTTP/1.1
Host: sandbox.opusdns.com
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=organization_...&client_secret=...
Or with JSON:
POST /v1/auth/token HTTP/1.1
Host: sandbox.opusdns.com
Content-Type: application/json
{"grant_type": "client_credentials", "client_id": "organization_...", "client_secret": "..."}
Successful responses return a bearer token and expiry:
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 3600
}
You can then use that token on subsequent requests:
GET /v1/domains HTTP/1.1
Host: sandbox.opusdns.com
Authorization: Bearer eyJ...
You can use this method when:
- Your platform prefers standard OAuth-style bearer tokens
- You want short-lived access tokens instead of sending the API key on every request
- Your HTTP tooling already expects
Authorization: Bearer ...
Resource IDs
The API uses extensively Type IDs: type-safe, K-sortable, globally unique identifier inspired by Stripe IDs. They can be easily identified with a format like prefix_01jxe1nzrmf78scaqbkjx0va0f. The prefix gives context to the ID - some examples include user, organization, domain. The rest of the ID is a 128-bit UUIDv7 encoded as a 26-character string using a modified base32 encoding. See formal specification for details.
This approach allows using unique IDs (UUIDv7), preventing iteration attacks, while also easily identifying the "namespace" of the ID. 01975c1f-a15a-7f6c-a5ce-b75fe33de079 is hardly distuingishable from 01975c1f-f120-7874-8dc2-de7d728bf261 by humans on first glance. However, when represented as Type IDs, they could be represented as user_01jxe1z8atfxpabknqbzhkvr3s and domain_01jxe1zw90f1t8vgpyfns8qwk1, immediately making it easier to differentiate between them.
Additionally, this gives type safety and additional validation that can be done. There's libraries available for many languages to make handling Type IDs easier. We're using them ourselves on the backend to quickly catch mistakes like passing the wrong Type ID (passing a user ID like user_01jxe1z8atfxpabknqbzhkvr3s where a domain ID domain_01jxe1zw90f1t8vgpyfns8qwk1 was expected).
Sandbox Environment
We provide a free sandbox environment where you can test and make sure your integration with OpusDNS runs smoothly. The sandbox is completely isolated, meaning all domains, configurations, and actions inside this environment have no effect on your live data or production systems. It is a full test system for safe experimentation and completely free to use.
Please register a new sandbox account at https://app.sandbox.opusdns.com and create a new API key there.
This sandbox provides a separate dashboard at app.sandbox.opusdns.com.
The sandbox API Base URL is https://sandbox.opusdns.com.
If you have any questions about the API or run into issues, please open a ticket at support@opusdns.com. We are happy to help!
Open-source SDKs and libraries
We're constantly building new features and services from SDKs and plugins to other exciting tools. Check out our GitHub to see the latest developments and try them out yourself!
OpusDNS Go Client
We offer a client implementation for go that supports all the features of our API.
Check out the client here!
Production environment
Authentication (Collapsed)
Endpoints for authentication.
Users (Collapsed)
Some requirements for users:
- A user must belong to one and only one organization.
- Both their email address and username must be unique within the whole system.
Organizations (Collapsed)
Endpoints for creating and managing organizations.
- get/v1/organizations
- post/v1/organizations
- get/v1/organizations/attributes
- patch/v1/organizations/attributes
- get/v1/organizations/ip-restrictions
- post/v1/organizations/ip-restrictions
- delete/v1/organizations/ip-restrictions/{ip_restriction_id}
- get/v1/organizations/ip-restrictions/{ip_restriction_id}
- patch/v1/organizations/ip-restrictions/{ip_restriction_id}
- get/v1/organizations/product-waitlist
- post/v1/organizations/product-waitlist/{product}/apply
- get/v1/organizations/role-permissions
- get/v1/organizations/roles
- post/v1/organizations/roles
- delete/v1/organizations/roles/{label}
- get/v1/organizations/roles/{label}
- patch/v1/organizations/roles/{label}
- get/v1/organizations/users
- delete/v1/organizations/{organization_id}
- get/v1/organizations/{organization_id}
- patch/v1/organizations/{organization_id}
- get/v1/organizations/{organization_id}/attributes
- patch/v1/organizations/{organization_id}/attributes
- get/v1/organizations/{organization_id}/billing/invoices
- get/v1/organizations/{organization_id}/billing/receipts
- get/v1/organizations/{organization_id}/pricing/product-type/{product_type}
- get/v1/organizations/{organization_id}/transactions
- get/v1/organizations/{organization_id}/transactions/{transaction_id}
- get/v1/organizations/{organization_id}/usage/{product}
- get/v1/organizations/{organization_id}/usage/{product}/summary
Product waitlist (Collapsed)
Endpoints for products that are not generally available yet and are reached through a waitlist.
A waitlisted product is invitation-only: OpusDNS invites an organization, its members apply, and OpusDNS decides on each application. Until your organization is invited, the product does not exist as far as this API is concerned - it is left out of the listing, and applying for it answers 404. Once you have applied, the product stays in your listing whatever happens to the invite afterwards.
Endpoints
| Method | Path | Description |
|---|---|---|
GET |
/v1/organizations/product-waitlist |
The waitlisted products you may see, and where your own application stands on each |
POST |
/v1/organizations/product-waitlist/{product}/apply |
Apply for one product's waitlist |
Both routes act on your own organization - the one you authenticated as - even when an X-Organization-Context header names one of its sub-organizations. Neither takes an organization in the path.
Product state
Each entry in the listing describes one product and your own standing with it. Applications are per user, so two members of one organization can see different states for the same product:
| Field | Description |
|---|---|
product |
The product key, used as {product} in the apply path |
display_name |
Human-readable product name |
description |
What the product does |
status |
pending, granted or rejected, or null while you have not applied |
applied_on |
When you applied, or null |
decided_on |
When OpusDNS decided, or null |
can_apply |
Whether an apply from you would be accepted right now |
An API key has no application of its own: it sees the products your organization is invited to, with status null and can_apply false.
Applying
POST /v1/organizations/product-waitlist/{product}/apply records your application as pending. Any member of an invited organization may apply. It requires a user token: an API-key token has no user to record.
The body is optional. Send {"note": "..."} to say what you would use the product for - it is the one thing OpusDNS has to go on when deciding, so it is worth filling in. A note is trimmed and may be up to 1000 characters; a blank or whitespace-only one is rejected rather than stored. Posting no body at all applies without a note.
One application per user per product. A second apply answers 409, whatever state the first one reached - a rejection is reconsidered by OpusDNS approving it after all, not by applying again. A colleague's application neither stands in for yours nor blocks it. can_apply in the listing tells you in advance whether an apply would be accepted.
Being granted a product means you, the user who applied, gain access to it. Your colleagues apply on their own, or ask OpusDNS if the whole organization should have it.
A decision is not final: OpusDNS can approve an application it rejected, and can withdraw a product it granted, which puts the application back to rejected.
Usage (Collapsed)
Endpoints for reporting your organization's usage of metered products.
These endpoints expose the usage your organization has incurred for a metered product, so you can monitor consumption over time and in total. Usage is reported as quantities only; billing costs are never included.
Endpoints
| Method | Path | Description |
|---|---|---|
GET |
/v1/organizations/{organization_id}/usage/{product} |
Usage as a time series, bucketed by granularity and grouped per sub-metric |
GET |
/v1/organizations/{organization_id}/usage/{product}/summary |
Usage totals over a date range, grouped per sub-metric |
{product} selects the metered product. Currently supported: ai_inference (AI token consumption from OpusDNS AI features). The response carries a top-level product field identifying the reported product.
Query Parameters
| Parameter | Description |
|---|---|
start_date |
Inclusive start date (YYYY-MM-DD) |
end_date |
Inclusive end date (YYYY-MM-DD) |
granularity |
Time-bucket size for the series endpoint: day (default), week, or month |
start_date must be on or before end_date, otherwise the request is rejected with 422 Unprocessable Content.
Reported Metrics
ai_inference
Each group reports token counts and request volume for one AI model:
| Field | Description |
|---|---|
model |
The AI model the usage was recorded against |
input_tokens |
Prompt (input) tokens consumed |
output_tokens |
Generated (output) tokens |
cache_read_tokens |
Tokens served from prompt cache |
cache_write_tokens |
Tokens written to prompt cache |
request_count |
Number of requests |
Usage is scoped to your billing organization and requires the organization view permission.
Domain management (Collapsed)
Endpoints for creating and managing domains.
Domain References
Most domain endpoints accept a domain_reference path parameter. This can be either the domain ID (e.g., domain_01jt7deb8mftf8261a54v6m3ey) or the domain name (e.g., example.com). Both are interchangeable wherever domain_reference appears.
Transfer Lock
The transfer_lock field on a domain response reflects whether the domain has the clientTransferProhibited status. Setting or removing clientTransferProhibited via the statuses or status_changes fields will automatically update transfer_lock accordingly. The transfer_lock field itself is read-only.
DNS Zone Creation
When creating or transferring a domain, you can set create_zone to true to automatically provision a DNS zone on OpusDNS nameserver infrastructure. Zone creation and nameserver assignment are handled asynchronously after the domain operation completes, so there may be a short delay before the zone is created and nameservers are active.
Updating Domain Statuses
PATCH /v1/domains/{domain_reference} supports two mutually exclusive approaches for modifying client statuses. You cannot use both in the same request.
Option 1: statuses (absolute / declarative)
Provide the complete list of client statuses the domain should have. The API computes the diff against the current state and adds or removes statuses accordingly.
{
"statuses": ["clientTransferProhibited", "clientDeleteProhibited"]
}
Use this when you know exactly which statuses the domain should end up with. Any current client statuses not in the list will be removed.
Option 2: status_changes (relative / delta)
Specify which statuses to add and/or remove relative to the domain's current state. Statuses not mentioned are left unchanged.
{
"status_changes": {
"add": ["clientTransferProhibited"],
"remove": ["clientHold"]
}
}
Use this when you want to make a targeted change without needing to know the domain's full current status set. This is especially useful in bulk operations where different domains may have different existing statuses.
Validation rules
- At least one of
addorremovemust contain a value - A status cannot appear in both
addandremove - Only client statuses are accepted — server statuses cannot be modified
When to use which
| Scenario | Recommended approach |
|---|---|
| Setting up a domain's statuses for the first time | statuses |
| Locking many domains for transfer without touching other statuses | status_changes with add |
| Releasing a hold on a specific domain | status_changes with remove |
| Replacing all statuses as part of a known workflow | statuses |
| Applying the same change across a batch of domains with varying existing statuses | status_changes via domain_update_bulk |
Blocking statuses
Some statuses prevent domain updates entirely. If the domain has any of the following, the update will be rejected:
clientUpdateProhibitedserverUpdateProhibitedpendingTransferpendingRestorependingDeleteredemptionPeriod
To update a domain that has clientUpdateProhibited, you can remove that status in a request that makes no other changes, or remove it first and then make additional changes in a follow-up request.
- get/v1/domains
- post/v1/domains
- get/v1/domains/check
- post/v1/domains/claims-notices
- get/v1/domains/statistics
- get/v1/domains/summary
- post/v1/domains/tld-specific/at/{domain_reference}/withdraw
- post/v1/domains/tld-specific/be/{domain_reference}/auth_code/request
- post/v1/domains/tld-specific/cymru/{domain_reference}/auth_code/request
- post/v1/domains/tld-specific/cz/{domain_reference}/auth_code/request
- post/v1/domains/tld-specific/de/{domain_reference}/transit
- post/v1/domains/tld-specific/dk/{domain_reference}/auth_code/request
- post/v1/domains/tld-specific/eu/{domain_reference}/auth_code/request
- post/v1/domains/tld-specific/lt/{domain_reference}/auth_code/request
- get/v1/domains/tld-specific/no/applicant-declaration
- put/v1/domains/tld-specific/no/applicant-declaration
- post/v1/domains/tld-specific/no/{domain_reference}/applicant-declaration
- post/v1/domains/tld-specific/no/{domain_reference}/resend-declaration-email
- post/v1/domains/tld-specific/nu/{domain_reference}/auth_code/request
- post/v1/domains/tld-specific/se/{domain_reference}/auth_code/request
- post/v1/domains/tld-specific/wales/{domain_reference}/auth_code/request
- post/v1/domains/transfer
- delete/v1/domains/{domain_reference}
- get/v1/domains/{domain_reference}
- patch/v1/domains/{domain_reference}
- delete/v1/domains/{domain_reference}/dnssec
- get/v1/domains/{domain_reference}/dnssec
- put/v1/domains/{domain_reference}/dnssec
- post/v1/domains/{domain_reference}/dnssec/disable
- post/v1/domains/{domain_reference}/dnssec/enable
- post/v1/domains/{domain_reference}/renew
- post/v1/domains/{domain_reference}/restore
- delete/v1/domains/{domain_reference}/transfer
- post/v1/domains/{domain_reference}/transfer/outbound
TLD specific domain management (Collapsed)
- post/v1/domains/tld-specific/at/{domain_reference}/withdraw
- post/v1/domains/tld-specific/be/{domain_reference}/auth_code/request
- post/v1/domains/tld-specific/cymru/{domain_reference}/auth_code/request
- post/v1/domains/tld-specific/cz/{domain_reference}/auth_code/request
- post/v1/domains/tld-specific/de/{domain_reference}/transit
- post/v1/domains/tld-specific/dk/{domain_reference}/auth_code/request
- post/v1/domains/tld-specific/eu/{domain_reference}/auth_code/request
- post/v1/domains/tld-specific/lt/{domain_reference}/auth_code/request
- get/v1/domains/tld-specific/no/applicant-declaration
- put/v1/domains/tld-specific/no/applicant-declaration
- post/v1/domains/tld-specific/no/{domain_reference}/applicant-declaration
- post/v1/domains/tld-specific/no/{domain_reference}/resend-declaration-email
- post/v1/domains/tld-specific/nu/{domain_reference}/auth_code/request
- post/v1/domains/tld-specific/se/{domain_reference}/auth_code/request
- post/v1/domains/tld-specific/wales/{domain_reference}/auth_code/request
TLD specification (Collapsed)
Endpoints for retrieving TLD specifications.
Hosts (Collapsed)
Endpoints for managing host objects (nameserver glue records).
What is a host object?
A host object represents a nameserver hostname registered at a registry. When a nameserver hostname is subordinate to a domain in your portfolio (e.g. ns1.example.com under example.com), the registry requires a host object carrying one or more IP addresses — commonly known as glue records — so that resolvers can reach the nameserver even though its address lives inside the zone it serves.
These endpoints manage subordinate host objects: the parent domain of the hostname must be managed in your OpusDNS account. Hostnames whose parent domain is not in your account are rejected with ERROR_PARENT_DOMAIN_NOT_FOUND. External nameservers (e.g. your DNS provider's hostnames) do not need host objects — simply reference them by hostname when setting a domain's nameservers.
Referencing a host
GET, PUT and DELETE accept either the host ID (host_...) or the hostname as the {host_reference} path parameter.
Lifecycle
- Create —
POST /v1/hostswith the hostname and at least one IP address. The host object is created at the registry of the parent domain. Creating a host that already exists returnsERROR_HOST_ALREADY_EXISTS. - Update —
PUT /v1/hosts/{host_reference}replaces the host's IP addresses with the provided list. If the provided addresses match the current state at the registry, the request succeeds without performing a registry update. - Delete —
DELETE /v1/hosts/{host_reference}removes the host object. A host that is still in use as a nameserver by one or more domains cannot be deleted and returnsERROR_HOST_IN_USE.
Not all TLDs use host objects; for TLDs without host object support, creation is rejected with ERROR_HOST_OBJECTS_NOT_SUPPORTED.
Contacts (Collapsed)
Endpoints for creating and managing contacts.
Contact Verification Attestation
POST /v1/contacts/{contact_id}/verifications/attest submits attestations for a contact. The request is forwarded synchronously to the contact-verification service, which is the source of truth for the response. When DENIC is among the registries that flagged the contact for verification, OpusDNS additionally queues a registry-side attest job behind the scenes.
Workflow
When a registry signals that a contact requires verification (e.g. DENIC's contactVerificationRequired notification), OpusDNS marks each affected domain with the VERIFICATION_REQUIRED status tag. Submit attestations via this endpoint and the response reports the resulting per-claim verification state.
Request format
The request body wraps the attestations under attestations. Fields are snake_case and enum values are UPPERCASE.
{
"attestations": [
{
"claim": "NAME",
"method": "AUTH",
"proof": "IDCARD",
"attestation_reference": "ticket-12345"
}
]
}
| Field | Type | Notes |
|---|---|---|
claim |
enum | NAME, ADDRESS, EMAIL, PHONE, LEGAL_ENTITY |
method |
enum | AUTH, VDIG, ELECTRONIC_DOCUMENT, PHYSICAL_DOCUMENT, BVR, PVR, DATA, REACHABILITY |
proof |
enum | IDCARD, PASSPORT, POPULATION_REGISTER, RESIDENCE_PERMIT, PROOF_OF_ARRIVAL, DRIVERS_LICENCE, COMPANY_REGISTER, COMPANY_STATEMENT, BANK_ACCOUNT, ONLINE_PAYMENT_ACCOUNT, UTILITY_ACCOUNT, BANK_STATEMENT, TAX_STATEMENT, WRITTEN_ATTESTATION, DIGITAL_ATTESTATION, POSTAL_VER_TRANSACTION_LOG, EMAIL_VER_TRANSACTION_LOG, PHONE_VER_TRANSACTION_LOG, ADDRESS_DATABASE |
attestation_reference |
string | Caller-supplied reference (e.g. internal ticket ID, document hash). Up to 255 chars. |
DENIC-only constraint: when the contact has DENIC verification pending, all items in a single request must share the same method, proof, and attestation_reference. DENIC's contactUPDATE carries one verification block per call; the API enforces this only when DENIC is in scope so callers see a synchronous 400 instead of a partial-success. Contacts not pending DENIC verification accept heterogeneous batches.
Response
The endpoint returns HTTP 200 OK with an object containing the per-claim verification state from the contact-verification service:
{
"verifications": [
{
"claim": "NAME",
"state": "VERIFIED",
"method": "AUTH",
"proof": "IDCARD",
"attestation_reference": "ticket-12345",
"verified_on": "2026-05-15T12:00:00Z",
"expires_on": null
}
]
}
| Field | Type | Notes |
|---|---|---|
claim |
enum | The claim this entry describes. |
state |
enum | UNVERIFIED, VERIFIED, IN_PROGRESS, EXPIRED. |
method |
enum or null | The method that produced the current state. |
proof |
enum or null | The proof that produced the current state. |
attestation_reference |
string or null | Reference recorded with the current state. |
verified_on |
RFC3339 datetime or null | When the claim entered the VERIFIED state. |
expires_on |
RFC3339 datetime or null | When the current verification expires. |
When DENIC verification was pending for the contact, the registry-side contactUPDATE is queued behind the scenes and emits a CONTACT/VERIFICATION/SUCCESS or CONTACT/VERIFICATION/FAILURE poll-message event keyed on contact_id. The VERIFICATION_REQUIRED status tag on the affected domains is cleared on success.
Error codes
| Code | Status | Meaning |
|---|---|---|
ERROR_DOMAIN_VERIFICATION_INCONSISTENT_METHOD_PROOF |
400 | DENIC verification is pending for this contact and items in the request disagree on method, proof, or attestation_reference. |
ERROR_DOMAIN_VERIFICATION_INVALID_COMBINATION |
(async) | The registry rejected the DENIC attestation as an invalid method/proof/reference combination. Surfaces as a CONTACT/VERIFICATION/FAILURE poll-message event. |
ERROR_DOMAIN_VERIFICATION_REGISTRY_REJECTED |
(async) | DENIC accepted the submission at the protocol level but indicated the verification attempt itself was not successful. Surfaces as a CONTACT/VERIFICATION/FAILURE poll-message event. |
- get/v1/contacts
- post/v1/contacts
- get/v1/contacts/attribute-sets
- post/v1/contacts/attribute-sets
- delete/v1/contacts/attribute-sets/{contact_attribute_set_id}
- get/v1/contacts/attribute-sets/{contact_attribute_set_id}
- patch/v1/contacts/attribute-sets/{contact_attribute_set_id}
- get/v1/contacts/verification
- put/v1/contacts/verification
- get/v1/contacts/verify
- delete/v1/contacts/{contact_id}
- get/v1/contacts/{contact_id}
- patch/v1/contacts/{contact_id}/link/{contact_attribute_set_id}
- get/v1/contacts/{contact_id}/verification
- put/v1/contacts/{contact_id}/verification
- post/v1/contacts/{contact_id}/verification
- delete/v1/contacts/{contact_id}/verification
- get/v1/contacts/{contact_id}/verifications
- post/v1/contacts/{contact_id}/verifications/attest
Event handling (Collapsed)
Endpoints for interacting with events.
Availability (Collapsed)
Endpoints for checking domain availability.
Tags (Collapsed)
Endpoints for creating new Tags
DNS Management (Collapsed)
Endpoints for managing DNS zones and records.
Zone, RRset, and Record
OpusDNS uses standard DNS terminology, and the distinction matters when choosing which endpoint to call:
- Zone — a DNS zone (e.g.,
example.com). Contains many rrsets. - RRset (Resource Record Set) — all records that share the same
(name, type)tuple within a zone (e.g., the set of allArecords forwww.example.com). An rrset is the smallest unit DNS resolvers return in a query response. - Record — a single
rdatavalue inside an rrset (e.g.,203.0.113.10). An rrset contains one or more records.
Three different update endpoints let you operate at different levels of granularity:
| Endpoint | Scope | Semantics |
|---|---|---|
PUT /v1/dns/{zone_name}/rrsets |
Whole zone | Replaces all user-managed rrsets in the zone |
PATCH /v1/dns/{zone_name}/rrsets |
RRset-level | Upserts or removes entire rrsets by (name, type) |
PATCH /v1/dns/{zone_name}/records |
Record-level | Upserts or removes individual rdata values inside an rrset |
All three endpoints refuse writes targeting rrsets you cannot manage. SOA, DNSKEY, DS, and the NS rrset at the zone apex are system-managed by OpusDNS: a payload rrset or patch op targeting one — upsert or remove — is rejected with a 409 protected-rrset error (these rrsets are reported with "protected": true in zone responses). To change the nameservers a domain delegates to, update the nameservers on the domain instead.
NS rrsets below the apex delegate a subdomain to other nameservers and are managed like any other record type, on all three endpoints and at zone creation. Two caveats: records (including domain/email forwards and parking) at or below a delegated name stop resolving publicly, because the delegated nameservers are authoritative for that subtree; and delegations from DNSSEC-signed zones are insecure delegations — child DS records are not supported, so the delegated subtree is not covered by the parent's chain of trust.
PUT /v1/dns/{zone_name}/rrsets — Replace all rrsets
Supply the complete set of rrsets the zone should contain. Any existing user-managed rrset not in the request is removed. System-managed rrsets cannot be set or removed via this endpoint; omit them from the payload.
Use this when you have authoritative, end-state data for a zone and want to reset it to that state.
PATCH /v1/dns/{zone_name}/rrsets — Upsert or remove whole rrsets
Submit an ops[] array where each op targets an rrset by (name, type):
op: "upsert"— create or replace the rrset at(name, type)with the records you provide. Any records that previously existed at that(name, type)are discarded.op: "remove"— delete the entire rrset at(name, type)(passrecords: []).
{
"ops": [
{
"op": "upsert",
"rrset": {
"name": "www",
"type": "A",
"ttl": 300,
"records": [
{"rdata": "203.0.113.10"},
{"rdata": "203.0.113.11"}
]
}
},
{
"op": "remove",
"rrset": {"name": "old", "type": "CNAME", "ttl": 300, "records": []}
}
]
}
Use this when you want to define the complete set of records for a given (name, type) — e.g., "www should have exactly these two A records". Any other A records for www that currently exist will be removed.
PATCH /v1/dns/{zone_name}/records — Upsert or remove individual records
Submit an ops[] array where each op targets a single rdata value inside an rrset. Note the op payload uses a single rdata field, not records[].
op: "upsert"— add or update that one rdata value. The containing rrset is created if it doesn't exist; sibling records in the rrset are left alone.op: "remove"— remove just that one rdata value. Other records in the same rrset remain.
{
"ops": [
{
"op": "upsert",
"record": {
"name": "@", "type": "MX", "ttl": 300,
"rdata": "5 new-mail.example.com."
}
},
{
"op": "remove",
"record": {
"name": "@", "type": "MX", "ttl": 300,
"rdata": "10 old-mail.example.com."
}
}
]
}
Use this when you need to surgically add or remove an individual record without disturbing sibling records in the same rrset — e.g., "add one more MX target", "rotate one A record IP out of a pool".
Choosing the right endpoint
| Scenario | Endpoint |
|---|---|
| Migrating a zone from another provider; want to set it to a known state | PUT /rrsets |
| Replacing all records of a given name+type (e.g., the full MX set) | PATCH /rrsets with upsert |
| Adding or removing a single record without touching its siblings | PATCH /records |
| Swapping a name from CNAME to A | PATCH /rrsets with remove + upsert |
For bulk variants that operate across many zones in a single request, see the Jobs documentation.
- get/v1/dns
- post/v1/dns
- get/v1/dns/domain-forwards
- get/v1/dns/email-forwards
- get/v1/dns/summary
- delete/v1/dns/{zone_name}
- get/v1/dns/{zone_name}
- post/v1/dns/{zone_name}/dnssec/disable
- post/v1/dns/{zone_name}/dnssec/enable
- get/v1/dns/{zone_name}/domain-forwards
- get/v1/dns/{zone_name}/email-forwards
- patch/v1/dns/{zone_name}/records
- patch/v1/dns/{zone_name}/rrsets
- put/v1/dns/{zone_name}/rrsets
- patch/v1/dns/{zone_name}/vanity-set
Vanity Nameservers (Collapsed)
Endpoints for managing nameservers.
- get/v1/vanity-nameserver-sets
- post/v1/vanity-nameserver-sets
- post/v1/vanity-nameserver-sets/check
- delete/v1/vanity-nameserver-sets/default
- get/v1/vanity-nameserver-sets/{set_id}
- delete/v1/vanity-nameserver-sets/{set_id}
- patch/v1/vanity-nameserver-sets/{set_id}
- patch/v1/vanity-nameserver-sets/{set_id}/default
- post/v1/vanity-nameserver-sets/{set_id}/restore
- post/v1/vanity-nameserver-sets/{set_id}/retry
- get/v1/vanity-nameserver-sets/{set_id}/zones
Whitelabel Branding (Collapsed)
Endpoints for managing an organization's whitelabel branding configuration.
An organization has one whitelabel config, on one tier at a time.
GET /v1/whitelabel-branding returns it, or 404 when nothing is set up.
The base tier serves the customer on a subdomain of a zone OpusDNS owns, so it needs nothing from them but a name. The plus tier serves them on their own domain for the fully unbranded experience.
POST /v1/whitelabel-branding buys a whitelabel on the chosen tier: {"tier": "base", "label": "acme", "period": {"value": 1, "unit": "y"}}, or for plus {"tier": "plus", "label": "acme", "hostname": "customer.com", "dashboard_subdomain": "dash", "auth_subdomain": "auth", "period": {...}}.
A label is required for both tiers: every whitelabel keeps a managed subdomain, composed as app.<label>.<base-tier suffix> (dashboard) and auth.<label>.<base-tier suffix> (login).
For base, that composed pair is what gets served: onboarding skips domain verification (the zone is OpusDNS-owned) but still points each host at the whitelabel edge, which issues a per-host certificate on demand.
For plus, the customer's own domain is served instead (omitting dashboard_subdomain serves the dashboard on the apex); the domain must be an OpusDNS-hosted zone owned by the organization, which onboarding verifies before pointing both hosts at the whitelabel edge. Set create_zone: true to have onboarding stand the zone up if it does not already exist. The managed base pair is published alongside the custom domain as a backup address.
Labels are validated on the way in, and branding-service additionally rejects reserved ones (that rejection surfaces as a 400).
Because every whitelabel composes its label, both tiers are only purchasable in environments configured with a base-tier zone; elsewhere create answers 403.
Both tiers are paid, and a whitelabel is a single subscription: the tiers are alternatives, not add-ons.
POST /v1/whitelabel-branding/tier upgrades a base whitelabel to plus in place, with the plus create's domain fields (hostname, dashboard_subdomain, auth_subdomain - no tier, no period).
The subscription keeps its term and renewal date and simply renews at the plus price; a yearly term additionally settles a one-time prorated fee for the remaining whole months now, a monthly term pays nothing now.
The managed subdomain label is kept.
Downgrades are not offered (cancel and rebook instead), so a whitelabel already on plus answers 409.
POST /v1/whitelabel-branding/recheck re-runs onboarding - the customer's retry after fixing their DNS setup.
A plus recheck may also carry a corrected hostname plus subdomains to re-point the config before re-running, which is accepted only before provisioning has started.
A base whitelabel takes no repoint: its hostnames come from its label, so it is moved with PATCH instead.
A disabled whitelabel answers 409 to both /recheck and /tier: re-enable it first (PATCH {"enabled": true} alone re-runs onboarding).
PATCH /v1/whitelabel-branding changes the config without touching its subscription or price.
{"label": "newname"} moves the managed subdomain to a different label at any time, including while the whitelabel is active. On base this changes the served host; on plus it moves the backup pair, leaving the custom domain unchanged (re-point that through recheck).
{"enabled": false} stops serving the whitelabel: its login client is disabled and OpusDNS withdraws its routing - the edge records for the served hosts are removed and the managed subdomain unpublished (a plus host whose DNS is still pointed at the edge keeps reaching the branded dashboard, but its login stays disabled).
The configuration, the branding document and the provisioning history are kept, so {"enabled": true} brings it back without re-provisioning.
Like the other write routes this is asynchronous: the response is 202 once the change is recorded, and a job converges the login client and the routing to it shortly after.
Disabling a whitelabel does not cancel its subscription; billing continues until the subscription itself is cancelled.
- get/v1/whitelabel-branding
- post/v1/whitelabel-branding
- patch/v1/whitelabel-branding
- get/v1/whitelabel-branding/assets
- post/v1/whitelabel-branding/assets
- delete/v1/whitelabel-branding/assets/{asset_id}
- get/v1/whitelabel-branding/document
- post/v1/whitelabel-branding/document
- put/v1/whitelabel-branding/document
- post/v1/whitelabel-branding/email/preview
- get/v1/whitelabel-branding/email/templates
- post/v1/whitelabel-branding/recheck
- post/v1/whitelabel-branding/restore
- post/v1/whitelabel-branding/tier
Email forwards (Collapsed)
Endpoints for creating and managing email forwards.
- get/v1/email-forwards
- post/v1/email-forwards
- delete/v1/email-forwards/{email_forward_id}
- get/v1/email-forwards/{email_forward_id}
- post/v1/email-forwards/{email_forward_id}/aliases
- delete/v1/email-forwards/{email_forward_id}/aliases/{alias_id}
- put/v1/email-forwards/{email_forward_id}/aliases/{alias_id}
- patch/v1/email-forwards/{email_forward_id}/disable
- patch/v1/email-forwards/{email_forward_id}/enable
- get/v1/email-forwards/{email_forward_id}/metrics
Domain forwards (Collapsed)
Endpoints for creating and managing domain forwards.
- get/v1/domain-forwards
- patch/v1/domain-forwards
- post/v1/domain-forwards
- get/v1/domain-forwards/metrics
- get/v1/domain-forwards/metrics/browser
- get/v1/domain-forwards/metrics/geo
- get/v1/domain-forwards/metrics/platform
- get/v1/domain-forwards/metrics/referrer
- get/v1/domain-forwards/metrics/status-code
- get/v1/domain-forwards/metrics/time-series
- get/v1/domain-forwards/metrics/user-agent
- get/v1/domain-forwards/metrics/visits-by-key
- delete/v1/domain-forwards/{hostname}
- get/v1/domain-forwards/{hostname}
- post/v1/domain-forwards/{hostname}
- patch/v1/domain-forwards/{hostname}/disable
- patch/v1/domain-forwards/{hostname}/enable
- delete/v1/domain-forwards/{hostname}/{protocol}
- get/v1/domain-forwards/{hostname}/{protocol}
- put/v1/domain-forwards/{hostname}/{protocol}
Domain parking (Collapsed)
Endpoints for managing domain parking.
Domain search (Collapsed)
Endpoints for searching domains.
Keyword queries vs. full domain names
GET /v1/domain-search/suggest behaves differently depending on the shape of the query value:
- Keyword or phrase (e.g.
bluewidgets) — every result is a name produced by our suggestion engine, which generates candidates and ranks them by relevance. No particular name is guaranteed to appear. - Full domain name (e.g.
bluewidgets.de) — the queried domain is always returned as the first result, and its availability is checked directly against the registry rather than coming from the suggestion engine.
Use a full domain name whenever you need a definitive answer about one specific domain. To check several specific domains at once, use GET /v1/availability instead.
tlds restricts results, it does not guarantee them
The tlds parameter is a filter: results are limited to the TLDs you list, but a TLD you list may still be absent from the response. Suggestions are ranked by relevance across the whole requested set, so a keyword whose strongest candidates are .com/.net/.org names can return no .de results even though available .de names exist for that keyword.
This is consistent for a given keyword rather than random — the same query returns the same TLD mix each time — and raising limit does not change it.
To guarantee that a TLD is represented, query it on its own:
GET /v1/domain-search/suggest?query=bluewidgets&tlds=de&limit=5
Issuing one request per TLD is the reliable way to build a per-TLD view in a search interface.
Availability of suggestions
The available flag on a generated suggestion is best-effort. Our suggestion engine's data is not authoritative for every TLD, and it is less accurate for some ccTLDs, so a suggestion can occasionally be shown as available when it is not.
Availability is authoritative in two cases: when the query is a full domain name (the first result), and for domains checked via GET /v1/availability. Always confirm with one of those before presenting a domain as purchasable.
Archive (Collapsed)
Endpoints for fetching historical data for objects and requests
Reports (Collapsed)
Endpoints for generating and downloading reports.
Reports provide exportable snapshots of your domain, DNS, and billing data. Reports are generated asynchronously and can be downloaded as ZIP files once complete.
Report Types
| Type | Description |
|---|---|
domain_inventory |
Full inventory of all domains in the organization |
dns_zone_summary |
Summary of all DNS zones and their configuration |
dns_zone_records |
Detailed export of all DNS records across zones |
billing_transactions |
Billing transactions for the current month to date |
billing_transactions_monthly |
Billing transactions for the previous calendar month |
registrar_portfolio_pdf |
Credential-scoped registrar portfolio report as a PDF inside the ZIP download |
Billing Transaction Reports
Billing transaction reports export all completed transactions as a CSV with the following columns: product_reference, action, period, product_type, amount, currency, completed_on.
There are two billing report types:
billing_transactions— On-demand report covering the current month to date (1st of the month through the time of the request). Request this via the API like any other report type.billing_transactions_monthly— Automatically generated on the 1st of each month at 5 AM UTC, covering the full previous calendar month. These are scheduled reports and do not need to be manually requested.
Both types maintain their own retention of 30 reports each, so requesting on-demand reports will not displace the monthly archive.
Registrar Portfolio PDF Reports
Registrar portfolio reports are requested with POST /v1/reports and require registrar_credential_id:
{
"report_type": "registrar_portfolio_pdf",
"registrar_credential_id": "registrar_credential_..."
}
The report uses registrar data from the registrar-elements OpenSearch index, populated by the scheduled daily registrar syncs. Report generation does not trigger a sync; it uses the most recently synced data as-is. If the credential has no portfolio data yet (for example, a credential created since the last sync), the report ends as empty rather than failed: there is nothing to report on yet, and the next run after a sync produces the real report.
The downloaded ZIP contains registrar-portfolio-{report_typeid}.pdf. Before each PDF generation, the report service loads the organization's current available TLD list from the backend and uses it to mark TLDs in the portfolio as available or unavailable. The first version includes the sections backed by indexed data: portfolio overview, domains/statuses/TLDs/nameservers, TLD availability, expiry summaries, contact validation summary, and DNS record summaries. Additional sections that require new OpenSearch fields will be added explicitly when those fields are indexed.
Report Lifecycle
A report transitions through the following statuses:
- pending — Report has been queued for generation
- generating — Report is being built
- completed — Report is ready for download
- failed — Generation failed
- empty — The organization had no data for this report type (for example no domains, zones or forwards). No file is produced and download returns
409 Conflict.record_countis0.
completed, failed and empty are terminal.
Usage Pattern
- Request a report —
POST /v1/reportswith the desiredreport_type - Poll for completion —
GET /v1/reports/{report_id}untilstatusis terminal (completed,emptyorfailed) - Download —
GET /v1/reports/{report_id}/downloadreturns the report as a streamed ZIP file
Downloading Reports
GET /v1/reports/{report_id}/download streams the report file directly to the client. The report must have status completed before it can be downloaded — calling this endpoint on a report that is still pending or generating, or that ended empty, returns 409 Conflict.
Response details:
| Header | Value |
|---|---|
Content-Type |
application/zip |
Content-Disposition |
attachment; filename={report_type}-{report_id}.zip |
Content-Length |
File size in bytes (included when known) |
The response body is streamed in chunks, so clients should read the body incrementally rather than buffering it entirely in memory. When Content-Length is present, it can be used to display download progress.
Trigger Types
| Type | Description |
|---|---|
on_demand |
Manually requested via the API |
scheduled |
Automatically generated on a recurring schedule |
Rate Limiting
Report creation is rate-limited at two levels:
- Cooldown — After creating a report, you must wait 5 minutes before requesting another report of the same type. Requests during the cooldown return
429 Too Many Requestswith aRetry-Afterheader. - Hourly limit — Only one report per type per organization per clock hour (UTC) can be generated. Duplicate requests within the same hour will not produce an additional report.
Retention
The 30 most recent reports of each type are kept per organization. Older reports and their associated files are automatically deleted when a new report is generated. Of the empty reports of a type, only the most recent one is kept, so scheduled runs for an organization without data do not fill the list with identical entries. registrar_portfolio_pdf is exempt from that rule, because its reports are per registrar credential and an empty result for one credential says nothing about another.
Listing Reports
Use GET /v1/reports to list reports with optional filters:
| Parameter | Description |
|---|---|
report_type |
Filter by report type (repeatable) |
status |
Filter by report status (repeatable) |
trigger_type |
Filter by trigger type |
created_after |
Only reports created after this timestamp |
created_before |
Only reports created before this timestamp |
Jobs (Collapsed)
Endpoints for submitting and tracking batch command execution.
The Jobs API enables asynchronous execution of bulk operations through batches. Submit multiple commands in a single request and poll for completion status.
Supported Commands
Single-resource commands
| Command | Description |
|---|---|
domain_create |
Register a new domain |
domain_update |
Modify domain settings (contacts, nameservers, statuses, renewal mode) |
domain_transfer |
Initiate an inbound domain transfer |
dns_zone_create |
Create a new DNS zone with optional records |
dns_zone_update |
Update an existing DNS zone |
contact_create |
Create a new contact |
Bulk commands (template + instances)
Bulk commands use a template + instances pattern. The template defines shared settings applied to all items, while each instance specifies a target resource and optional per-resource overrides.
| Command | Description |
|---|---|
domain_create_bulk |
Register multiple domains |
domain_update_bulk |
Update multiple domains (statuses, nameservers, contacts, renewal mode) |
domain_transfer_bulk |
Transfer multiple domains |
dns_zone_create_bulk |
Create multiple DNS zones |
dns_zone_update_bulk |
Replace rrsets (and zone attributes like DNSSEC) across multiple DNS zones |
dns_zone_patch_rrsets_bulk |
Upsert or remove entire rrsets (by name + type) across multiple zones |
dns_zone_patch_records_bulk |
Upsert or remove individual rdata values across multiple zones |
contact_create_bulk |
Create multiple contacts |
parking_create_bulk |
Create parking pages for multiple domains |
parking_enable_bulk |
Enable parking on multiple domains |
parking_disable_bulk |
Disable parking on multiple domains |
parking_delete_bulk |
Delete parking pages for multiple domains |
Batch Lifecycle
Each job within a batch transitions through states:
- blocked — Waiting for eligibility (scheduled via
not_before, or awaiting capacity) - queued — Eligible and awaiting processing
- paused — Paused; must be explicitly resumed to continue
- running — Currently being executed
- succeeded — Completed successfully
- failed — Execution failed (check
error_classanderror_message) - canceled — Job was canceled before completion
- dead_letter — Permanently failed after exhausting retries
A batch itself has a status field that is either pending (jobs are still in progress) or complete (all jobs have reached a terminal state).
Usage Pattern
- Submit a batch —
POST /v1/jobswith an array of commands - Poll for status —
GET /v1/jobs/{batch_id}to check progress counts andprogress_percentage - Review results —
GET /v1/jobs/{batch_id}/jobsto see individual job outcomes
Managing Batches
- Pause —
POST /v1/jobs/{batch_id}/pausepauses all eligible jobs in the batch - Resume —
POST /v1/jobs/{batch_id}/resumeresumes all paused jobs - Retry —
POST /v1/jobs/{batch_id}/retryretries failed and dead-lettered jobs in the batch (see Retrying Failed Jobs) - Cancel —
DELETE /v1/jobs/{batch_id}cancels all pending jobs in the batch
Individual jobs can also be paused, resumed, retried, or canceled via the /v1/job/{job_id} endpoints.
Retrying Failed Jobs
If jobs in a batch end up in failed or dead_letter state — for example because the account had insufficient funds at the time the batch was processed — you can re-attempt them without rebuilding the batch from scratch.
- Single job:
POST /v1/job/{job_id}/retry - Whole batch:
POST /v1/jobs/{batch_id}/retry
What gets retried
Only jobs in failed or dead_letter status are eligible. Jobs in any other status are left unchanged:
succeeded— already complete; retrying returns 409 Conflictcanceled— you explicitly canceled these, so the retry won't undo that; returns 409 Conflict (single) or is silently skipped (batch)queued,blocked,paused,running— still in progress; not touched
Calling batch retry on a mixed-state batch returns retried_count (the number of failed / dead_letter jobs that were reset for retry); everything else is left alone. If the batch has no retryable jobs, retried_count is 0. The response also splits that total into queued_count (jobs dispatched immediately) and blocked_count (jobs held behind the topic's rate limit or backlog — these are released automatically as capacity frees up).
What happens to a retried job
Each retried job is reset to a fresh attempt:
status→queued, orblockedif the job's topic is rate-limited and has no capacity (or a higher-priority backlog is waiting) — a blocked job is released toqueuedautomatically as capacity frees upattempts→0not_before→ nowerror_classanderror_message→ cleared- The job is republished for worker pickup once it is
queued
The original payload, idempotency_key, correlation_id (batch id), max_attempts, and backoff configuration are preserved. Each retry is processed under the same worker idempotency guarantees as the original — for billing operations specifically, a retried job will not double-charge an account that was already debited.
Filtering by error type
A batch may contain a mix of failures that you do and don't want to retry. For example, after adding funds to your account, you may want to retry only the jobs that failed with BillingInsufficientFundsError, while leaving alone jobs that failed for other reasons (e.g. an invalid domain name that should not be re-attempted).
Pass one or more error_class query parameters to filter:
POST /v1/jobs/{batch_id}/retry?error_class=BillingInsufficientFundsError
POST /v1/jobs/{batch_id}/retry?error_class=BillingInsufficientFundsError&error_class=DomainRegistryTemporaryError
Multiple values are OR'd — a job is retried if its error_class matches any of the supplied values. Omitting the filter retries all failed and dead_letter jobs in the batch.
The error_class for each failed job is visible on the individual job response (GET /v1/jobs/{batch_id}/jobs), so the typical flow is:
- List the failed jobs in the batch and group by
error_class. - Determine which failures are recoverable.
- Call retry with the relevant
error_classfilter.
Limits
- Maximum 50,000 commands per batch
- Bulk commands support up to 1,000 instances per command
Scheduling
Use not_before to schedule batch execution for a future time (UTC timestamp). If not provided, processing begins as soon as a worker is available.
Idempotency
Each command can include an optional idempotency_key to prevent duplicate execution. If a command with the same idempotency key has already been processed, it will be skipped.
Domain Status Updates in Batches
The domain_update and domain_update_bulk commands support two mutually exclusive approaches for modifying domain statuses. These work the same way as the PATCH /v1/domains/{domain_reference} endpoint — see the Domain management documentation for full details on statuses vs status_changes.
Using status_changes in bulk templates
The domain_update_bulk command is particularly useful with status_changes when you need to apply the same relative status change across many domains. Set status_changes in the template and list the target domains as instances. Each instance identifies a domain by either name or domain_id (but not both):
{
"command": "domain_update_bulk",
"payload": {
"template": {
"status_changes": {
"add": ["clientTransferProhibited"]
}
},
"instances": [
{ "name": "example.com" },
{ "name": "example.net" },
{ "name": "example.org" }
]
}
}
Per-domain overrides
If an instance provides its own statuses or status_changes field, it completely overrides the template's status settings for that domain. This lets you apply a default change to most domains while handling exceptions individually:
{
"command": "domain_update_bulk",
"payload": {
"template": {
"status_changes": {
"add": ["clientTransferProhibited"]
}
},
"instances": [
{ "name": "example.com" },
{ "name": "special-case.com", "status_changes": { "remove": ["clientHold"] } }
]
}
}
DNS Zone Bulk Commands
The three dns_zone_*_bulk commands operate at different levels of granularity. Picking the right one depends on how much of the zone's existing state you want to preserve.
| Command | Operates on | Use when |
|---|---|---|
dns_zone_update_bulk |
Whole zone | Resetting rrsets (or zone attributes like DNSSEC) across many zones |
dns_zone_patch_rrsets_bulk |
Whole rrsets by (name, type) |
Replacing all records of a given type at a given name |
dns_zone_patch_records_bulk |
Individual rdata values | Adding or removing a single record without touching siblings |
These are the bulk equivalents of the single-zone PUT and PATCH DNS endpoints — see the DNS Management documentation for the underlying semantics. The bulk variants accept up to 100 ops per instance for the two patch commands.
dns_zone_update_bulk
Bulk equivalent of PUT /v1/dns/{zone_name}/rrsets plus zone-level attribute updates. For each zone, whatever rrsets you supply become the zone's rrsets — anything not listed is removed. System-managed record types (SOA, DNSKEY, DS) cannot be set via this command. This is also the command to flip DNSSEC on or off across many zones.
Both rrsets and dnssec_status are partial-update fields:
| Value | Meaning |
|---|---|
| omitted (in both template and instance) | leave that field unchanged on the zone |
[] (rrsets only) |
delete all records |
[...] / a status value |
replace |
An instance with neither rrsets nor dnssec_status (after the template is merged in) is rejected as a no-op.
Payload: template (shared rrsets and dnssec_status) + instances[] (per-zone overrides).
{
"command": "dns_zone_update_bulk",
"payload": {
"template": {
"rrsets": [
{"name": "@", "type": "A", "ttl": 300, "records": [{"rdata": "203.0.113.10"}]}
],
"dnssec_status": "enabled"
},
"instances": [
{ "name": "example.com" },
{
"name": "example.net",
"rrsets": [
{"name": "@", "type": "A", "ttl": 300, "records": [{"rdata": "203.0.113.20"}]}
]
}
]
}
}
Bulk-toggle DNSSEC across many zones without touching their records — omit rrsets everywhere:
{
"command": "dns_zone_update_bulk",
"payload": {
"template": { "dnssec_status": "enabled" },
"instances": [
{ "name": "example.com" },
{ "name": "example.net" },
{ "name": "example.org" }
]
}
}
dns_zone_patch_rrsets_bulk
Bulk equivalent of PATCH /v1/dns/{zone_name}/rrsets. Each instance is { zone_name, ops[] }, where each op targets an rrset by (name, type):
op: "upsert"— create or replace the rrset with the records you provide. Any prior records for that(name, type)are discarded.op: "remove"— delete the entire rrset (passrecords: []).
{
"command": "dns_zone_patch_rrsets_bulk",
"payload": {
"instances": [
{
"zone_name": "example.com",
"ops": [
{
"op": "upsert",
"rrset": {
"name": "www", "type": "A", "ttl": 300,
"records": [
{"rdata": "203.0.113.10"},
{"rdata": "203.0.113.11"}
]
}
},
{
"op": "remove",
"rrset": {"name": "old", "type": "CNAME", "ttl": 300, "records": []}
}
]
}
]
}
}
dns_zone_patch_records_bulk
Bulk equivalent of PATCH /v1/dns/{zone_name}/records. Each instance is { zone_name, ops[] }, where each op targets a single rdata value inside an rrset. Note the op payload uses a single rdata field, not records[].
op: "upsert"— add or update that one rdata value (creates the rrset if it doesn't exist). Sibling records in the rrset are untouched.op: "remove"— remove just that one rdata value. Other records in the rrset remain.
{
"command": "dns_zone_patch_records_bulk",
"payload": {
"instances": [
{
"zone_name": "example.com",
"ops": [
{
"op": "upsert",
"record": {
"name": "@", "type": "MX", "ttl": 300,
"rdata": "5 new-mail.example.com."
}
},
{
"op": "remove",
"record": {
"name": "@", "type": "MX", "ttl": 300,
"rdata": "10 old-mail.example.com."
}
}
]
}
]
}
}
- delete/v1/job/{job_id}
- get/v1/job/{job_id}
- post/v1/job/{job_id}/pause
- post/v1/job/{job_id}/resume
- post/v1/job/{job_id}/retry
- get/v1/jobs
- post/v1/jobs
- delete/v1/jobs/{batch_id}
- get/v1/jobs/{batch_id}
- get/v1/jobs/{batch_id}/jobs
- post/v1/jobs/{batch_id}/pause
- post/v1/jobs/{batch_id}/resume
- post/v1/jobs/{batch_id}/retry

