Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

Bulk Operations

Create, update, or delete many records in a single request. Atomic by default — all succeed or nothing changes.

Base path: /api/v1/domains/{domain_id}/records/bulk* Required permission: records:create, records:delete

Bulk create#

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
curl -X POST https://scaidns.scailabs.ai/api/v1/domains/$DOMAIN_ID/records/bulk \
  -H "X-API-Key: $SCAIDNS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "records": [
      {"name": "app1", "type": "A", "content": "192.0.2.10", "ttl": 300},
      {"name": "app2", "type": "A", "content": "192.0.2.11", "ttl": 300},
      {"name": "app3", "type": "A", "content": "192.0.2.12", "ttl": 300}
    ],
    "continue_on_error": false
  }'
python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
records = [
    {"name": f"app{i}", "type": "A", "content": f"192.0.2.{10+i}", "ttl": 300}
    for i in range(100)
]

resp = httpx.post(
    f"https://scaidns.scailabs.ai/api/v1/domains/{DOMAIN_ID}/records/bulk",
    headers={"X-API-Key": os.environ["SCAIDNS_API_KEY"]},
    json={"records": records, "continue_on_error": False},
)
result = resp.json()
print(f"Created: {result['total_created']}, errors: {result['total_errors']}")
typescript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
const records = Array.from({ length: 100 }, (_, i) => ({
  name: `app${i}`,
  type: "A",
  content: `192.0.2.${10 + i}`,
  ttl: 300,
}));

const resp = await fetch(
  `https://scaidns.scailabs.ai/api/v1/domains/${domainId}/records/bulk`,
  {
    method: "POST",
    headers: {
      "X-API-Key": process.env.SCAIDNS_API_KEY!,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ records, continue_on_error: false }),
  },
);
const result = await resp.json();

Request#

Field Type Required Notes
records array Yes Same shape as single-record create; up to 500 records per request
continue_on_error boolean No Default false (atomic)

Response#

json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "created": [
    {"id": "r_1", "name": "app1.example.com", "type": "A", "content": "192.0.2.10", "ttl": 300}
  ],
  "errors": [
    {"index": 2, "name": "app3", "type": "A", "error": "IP conflict with record r_existing"}
  ],
  "total_created": 99,
  "total_errors": 1
}

When continue_on_error: false (default), any error fails the whole batch and created is empty. When true, partial success is possible.

Bulk delete#

bash
1
2
3
4
5
6
curl -X POST https://scaidns.scailabs.ai/api/v1/domains/$DOMAIN_ID/records/bulk-delete \
  -H "X-API-Key: $SCAIDNS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "record_ids": ["r_abc", "r_def", "r_ghi"]
  }'

Response:

json
1
2
3
4
5
6
{
  "deleted": 3,
  "errors": [],
  "total_deleted": 3,
  "total_errors": 0
}

Atomicity#

When continue_on_error: false (default):

  • Validation runs first against every record.
  • If anything fails validation (invalid content, conflicts, permission), the request returns 4xx and nothing is written.
  • Otherwise, all records are committed in a single transaction.

When continue_on_error: true:

  • Each record is processed independently.
  • Successes are returned in created, failures in errors.
  • Partial success is possible — the request always returns 200.

Use atomic mode for configuration changes where partial application would leave the zone in an inconsistent state. Use continue_on_error: true for opportunistic imports where you want to see what got in.

Limits#

  • Max records per request: 500. For larger imports, chunk into batches.
  • Payload size: 10 MB default. Lower if behind a proxy with smaller limits.
  • Rate limits: Bulk calls count as a single request against your API key's rate limit. The underlying record count isn't multiplied.

Patterns#

Split across multiple zones#

The bulk endpoint operates on a single zone. If you need to provision records across many zones, loop at the application layer:

python
1
2
3
4
5
6
7
for zone_id, records in by_zone.items():
    resp = httpx.post(
        f"https://scaidns.scailabs.ai/api/v1/domains/{zone_id}/records/bulk",
        headers={"X-API-Key": os.environ["SCAIDNS_API_KEY"]},
        json={"records": records},
    )
    resp.raise_for_status()

Full zone replacement via bulk-delete + bulk-create#

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# Get current records
resp = httpx.get(
    f"https://scaidns.scailabs.ai/api/v1/domains/{DOMAIN_ID}/records",
    headers=HEADERS,
)
existing_ids = [r["id"] for r in resp.json()["data"]]

# Delete them all
if existing_ids:
    httpx.post(
        f"https://scaidns.scailabs.ai/api/v1/domains/{DOMAIN_ID}/records/bulk-delete",
        headers=HEADERS,
        json={"record_ids": existing_ids},
    ).raise_for_status()

# Create the new set
httpx.post(
    f"https://scaidns.scailabs.ai/api/v1/domains/{DOMAIN_ID}/records/bulk",
    headers=HEADERS,
    json={"records": new_records, "continue_on_error": False},
).raise_for_status()

For a proper atomic replace, prefer import with mode: replace — see Import and Export.

Idempotent bulk creates#

Bulk create rejects duplicates that match (name, type, content). To make repeated calls safe:

  • Pre-check with GET /records?type=A&name=app1.
  • Or set continue_on_error: true and filter errors for "already exists" messages.
  • Or use import with mode: skip_existing for larger batches.

Errors#

Status Cause
400 Empty records array, or per-record validation failed in atomic mode
403 Caller lacks records:create / records:delete on this domain
409 Conflict with existing record (atomic mode)
413 Payload too large
429 Rate limit exceeded

The response body details which record failed when relevant.

What's next#

Updated 2026-05-17 02:38:20 View source (.md) rev 1