API reference

The DNS by echo reply REST API is offered with the Enterprise plan. It uses the same users, organizations, roles, and permissions as the web application. Contact info@echo-reply.net to discuss access.

This page is a practical introduction to the DNS endpoints. The application’s generated documentation is the canonical contract for every field and route:

Base URL and versioning

The production base URL already includes the current API version:

https://dns.echo-reply.net/api/v1/

Append paths such as orgs/{org_slug}/zones/ to that URL. Do not add a second /v1. Paths in this reference include their trailing slash because the API’s routes are defined that way.

Authentication and access

Send a user token with the Token authentication scheme:

Authorization: Token YOUR_TOKEN

This is not a Bearer token. A token belongs to one user and inherits that user’s organization memberships and role permissions. Tokens do not have independent zone scopes. For example, listing zones requires the user’s can_view_domains permission, while creating or changing one requires can_edit_domains.

Create, rotate, or revoke a token

While signed in with a browser session, create a token with:

POST https://dns.echo-reply.net/api/v1/auth/token/
{
  "token": "0123456789abcdef0123456789abcdef01234567",
  "created": "2026-09-14T18:20:00Z"
}

Calling the same endpoint again while authenticated—by session or by the existing token—rotates the token and immediately revokes the old one. Revoke the current user’s token with:

DELETE https://dns.echo-reply.net/api/v1/auth/token/

A successful revocation returns 204 No Content. Store tokens like passwords, never put them in URLs or client-side code, and rotate them if exposed.

Organization scoping

Most resources are scoped by the organization slug:

https://dns.echo-reply.net/api/v1/orgs/{org_slug}/...

The organization must exist, the authenticated user must be a member, and the user’s role must allow the requested action. A token cannot be used to cross those tenant or role boundaries.

Zones

List zones

GET https://dns.echo-reply.net/api/v1/orgs/acme-network/zones/?page=1&ordering=name
Authorization: Token YOUR_TOKEN
Accept: application/json
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 42,
      "name": "example.dk",
      "type": "MASTER",
      "serial": "2026091401",
      "disabled": false,
      "dnssec_enabled": true,
      "created": "2026-09-14T18:20:00Z",
      "modified": "2026-09-14T18:22:00Z"
    }
  ]
}

Supported filters are name (case-insensitive contains), type, disabled, and dnssec. Supported ordering fields are name, created, and modified. prefix a field with - for descending order.

Retrieve a zone

GET https://dns.echo-reply.net/api/v1/orgs/acme-network/zones/42/
Authorization: Token YOUR_TOKEN

The detail response includes the zone’s SOA information, DS data when DNSSEC is enabled, and its record count in addition to the list fields.

Create a zone

POST https://dns.echo-reply.net/api/v1/orgs/acme-network/zones/
Authorization: Token YOUR_TOKEN
Content-Type: application/json

{
  "name": "example.dk"
}

name is required. The application defaults a directly created served zone to MASTER and uses the organization email as the SOA administrator address when admin_email is omitted. See the OpenAPI schema for optional secondary-zone, automatic PTR, and disabled-state fields.

An authorized direct change returns 201 Created with the created zone. If the user may propose changes but cannot self-approve them, the same request returns 202 Accepted instead:

{
  "detail": "Your changes have been submitted for admin approval.",
  "stashed_change": {
    "id": 317,
    "change_type": "CREATE",
    "status": "PENDING",
    "content_type": "domain",
    "object_id": null,
    "object_repr": "example.dk",
    "changes": {
      "name": "example.dk",
      "organization": 12,
      "admin_email": "dns-admin@example.dk"
    },
    "created_by": "operator@example.dk",
    "created_at": "2026-09-14T18:20:00Z",
    "reviewed_by": null,
    "reviewed_at": null,
    "review_notes": ""
  }
}

Update or delete a zone

Use PATCH for a partial update, PUT for a complete update, and DELETE to remove a zone:

PATCH https://dns.echo-reply.net/api/v1/orgs/acme-network/zones/42/
Authorization: Token YOUR_TOKEN
Content-Type: application/json

{
  "disabled": true
}
DELETE https://dns.echo-reply.net/api/v1/orgs/acme-network/zones/42/
Authorization: Token YOUR_TOKEN

A direct delete returns 204 No Content. A change requiring approval returns 202 Accepted with a pending stashed_change, as creation does. Deletion is destructive once applied.

DNSSEC has explicit actions at POST .../zones/{zone_id}/enable-dnssec/ and POST .../zones/{zone_id}/disable-dnssec/.

Records

Record names are fully qualified and do not end in a dot. For example, use www.example.dk, not www or www.example.dk..

List records in a zone

GET https://dns.echo-reply.net/api/v1/orgs/acme-network/zones/42/records/?type=A&page=1
Authorization: Token YOUR_TOKEN
Accept: application/json

Supported filters are name (case-insensitive contains), type, content (case-insensitive contains), and disabled. Supported ordering fields are name, type, ttl, created, and modified.

For a read-only view across every zone in the organization, use:

GET https://dns.echo-reply.net/api/v1/orgs/acme-network/records/

Create a record

POST https://dns.echo-reply.net/api/v1/orgs/acme-network/zones/42/records/
Authorization: Token YOUR_TOKEN
Content-Type: application/json

{
  "name": "www.example.dk",
  "type": "A",
  "ttl": 300,
  "content": "192.0.2.10"
}

A directly created record returns 201 Created:

{
  "id": 901,
  "name": "www.example.dk",
  "type": "A",
  "content": "192.0.2.10",
  "ttl": 300,
  "prio": null,
  "disabled": false,
  "remarks": null,
  "domain": {
    "id": 42,
    "name": "example.dk"
  },
  "change_date": 1789410000,
  "created": "2026-09-14T18:20:00Z",
  "modified": "2026-09-14T18:20:00Z"
}

If ttl is omitted, the zone’s SOA minimum is used when available. prio is used by record types such as MX. Supported record types and their validation rules are defined in the generated OpenAPI schema.

Update and delete routes are:

PATCH  https://dns.echo-reply.net/api/v1/orgs/{org_slug}/zones/{zone_id}/records/{record_id}/
DELETE https://dns.echo-reply.net/api/v1/orgs/{org_slug}/zones/{zone_id}/records/{record_id}/

As with zones, these operations return the result immediately for users who can self-approve, or 202 Accepted with a pending change where approval is required.

Approvals and asynchronous operations

A 202 Accepted DNS write has not changed live DNS. It created a pending proposal. Read proposals at:

GET  https://dns.echo-reply.net/api/v1/orgs/{org_slug}/changes/
GET  https://dns.echo-reply.net/api/v1/orgs/{org_slug}/changes/{change_id}/
POST https://dns.echo-reply.net/api/v1/orgs/{org_slug}/changes/{change_id}/approve/
POST https://dns.echo-reply.net/api/v1/orgs/{org_slug}/changes/{change_id}/reject/
POST https://dns.echo-reply.net/api/v1/orgs/{org_slug}/changes/{change_id}/cancel/

Approval and rejection accept an optional review_notes string. Role rules still apply: an ordinary proposer cannot approve their own request merely by calling the approval route.

Zone and record CRUD either completes synchronously or enters this approval workflow. Domain registration and registrar operations have separate stateful and asynchronous outcomes. Use the registration and transaction resources in the canonical API documentation, and treat a submitted or processing result as pending until the corresponding transaction reaches a terminal state.

Pagination

List endpoints use page-number pagination with 50 results per page:

GET https://dns.echo-reply.net/api/v1/orgs/acme-network/zones/?page=2

Responses contain count, next, previous, and results. Follow the absolute next and previous URLs returned by the server. A client-selectable page size is not part of the current API contract.

Rate limits

Authenticated requests are limited to 200 requests per minute per user and also to a 20 requests per second burst. Some routes are stricter. For example, domain availability search is limited to 30 requests per minute and organization creation to 5 requests per day.

When a limit is exceeded, the API returns 429 Too Many Requests and a Retry-After header. Wait for that interval before retrying. The API does not promise X-RateLimit-* headers on every response.

Plans and resource limits

API access is commercially packaged with Enterprise, while technical authorization is enforced through the authenticated user and organization RBAC. Enterprise-only resources, such as creating private masters, also have an explicit entitlement check.

Organization resource limits apply equally to the web application and API. Creating a zone when its limit has been reached returns 403 Forbidden. For a direct record write over quota, the record may be created disabled with a quota_warning. A proposed record write can instead return 403 Forbidden. Clients must inspect both the HTTP status and response body.

Errors

Validation errors are keyed by field and contain arrays of messages:

{
  "name": ["This field is required."]
}

Authentication, permission, lookup, throttling, and other API errors normally use detail and may include a machine-readable code:

{
  "detail": "Invalid token.",
  "code": "authentication_failed"
}

Common status codes are:

StatusMeaning
400 Bad RequestInvalid fields or an operation rejected by application rules
401 UnauthorizedMissing, invalid, revoked, or incorrectly prefixed token
403 ForbiddenThe user lacks an organization permission, entitlement, or available resource
404 Not FoundThe organization, zone, record, or other resource is not visible or does not exist
409 ConflictThe resource state conflicts with the action, such as approving a completed proposal
429 Too Many RequestsA request throttle was exceeded. Honor Retry-After
500 Internal Server ErrorUnexpected server failure. Retry cautiously and contact support if it persists

Do not rely on one universal error code or envelope for every validation and application-level response. Preserve the HTTP status and body in client logs, without logging tokens or sensitive record content.