---
name: prismns-dns
description: >-
  Manage DNS through the Prism DNS (prismns.com) automation API. Use this skill
  whenever the user wants to create, read, update, or delete DNS zones or
  records, manage DNSSEC, or import/export zone data on a Prism DNS account.
  Authenticates with a Prism automation token via a Bearer header.
---

# Prism DNS — Agent Skill

This skill lets an AI agent manage DNS on a [Prism DNS](https://prismns.com) account
through its REST API, authenticated with an **automation token**. Use it for any
request to add/change/remove DNS records or zones, configure DNSSEC, or
import/export zone data.

> Full endpoint reference (every parameter and response shape) lives at
> `https://prismns.com/api/docs` (Swagger) and in the `AUTOMATION_API.md`
> shipped with this skill. This file is the **operating guide** — read it first,
> then fall back to the reference for the long tail.

---

## Setup

The skill needs two things in the environment before it can do anything:

| Variable | Meaning | Example |
|----------|---------|---------|
| `PRISM_API_BASE` | API base URL (no trailing slash) | `https://prismns.com/api` |
| `PRISM_TOKEN` | Automation token (the secret) | `aBcDeFgH...` (32 chars) |

```bash
export PRISM_API_BASE="https://prismns.com/api"   # or your self-hosted URL
export PRISM_TOKEN="<paste-your-automation-token>"
```

**Getting a token:** the human creates it in the Prism web UI under
**Settings → Automation Tokens → Generate New Automation Token**. The token is
shown **once** — they must copy it then. If the agent does not have a token,
stop and ask the user to generate one; the agent cannot mint tokens for itself
(token-management endpoints require an interactive login, not an automation token).

Every request authenticates with:

```
Authorization: Bearer $PRISM_TOKEN
```

---

## The 8 rules that prevent most mistakes

1. **Zone names end with a dot.** It's `example.com.`, not `example.com`. The
   trailing dot is part of the identifier in every path and body.
2. **A token only sees its owner's zones.** Operating on a zone the user doesn't
   own returns `403`/`404`. Don't assume a zone exists — list first.
3. **`PUT` on a record replaces the entire record set** for that name+type. To
   *add* an IP to an existing `A` record, fetch the current set, append, then
   `PUT` the full list — otherwise you'll wipe the others.
4. **`records` is always an array of `{"content": "..."}`,** even for a single
   value. Content is the raw zone-file value (e.g. MX = `"10 mail.example.com."`,
   TXT = the quoted string).
5. **Record `name` may be relative or FQDN.** `"www"` in zone `example.com.`
   means `www.example.com.`. Use `"example.com."` (or `"@"` style FQDN) for the
   apex.
6. **Destructive ops are irreversible.** Deleting a zone deletes all its records.
   Confirm with the user before any `DELETE` on a zone, and before `replace`-mode
   imports.
7. **Preview before bulk imports.** Run `POST /dns/import/preview` (or
   `dry_run: true`) and show the user the diff before applying.
8. **Respect rate limits.** Reads ~100–200/min, writes ~50/min, deletes 30/min,
   imports 10/min. On `429`, back off and retry — don't hammer.

---

## Common workflows

In the snippets below, `$PRISM_API_BASE` and `$PRISM_TOKEN` are set as above.
Pipe through `jq` to read results.

### List the user's zones
```bash
curl -s "$PRISM_API_BASE/dns/zones?limit=100" \
  -H "Authorization: Bearer $PRISM_TOKEN" | jq '.zones[].name'
```

### Inspect one zone and its records
```bash
curl -s "$PRISM_API_BASE/dns/zones/example.com./records?limit=500" \
  -H "Authorization: Bearer $PRISM_TOKEN" | jq
```

### Create a record (A / AAAA / CNAME / MX / TXT …)
```bash
curl -s -X POST "$PRISM_API_BASE/dns/zones/example.com./records" \
  -H "Authorization: Bearer $PRISM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "www",
    "type": "A",
    "ttl": 3600,
    "records": [{"content": "93.184.216.34"}]
  }' | jq
```

### Point a record at a new IP (safe replace of one name+type)
```bash
curl -s -X PUT "$PRISM_API_BASE/dns/zones/example.com./records/www.example.com./A" \
  -H "Authorization: Bearer $PRISM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ttl": 300, "records": [{"content": "10.0.1.100"}]}' | jq
```

### Add a value to an existing record set without losing the others
```bash
# 1. read the current set
curl -s "$PRISM_API_BASE/dns/zones/example.com./records/www.example.com./A" \
  -H "Authorization: Bearer $PRISM_TOKEN" | jq
# 2. PUT back the full list (existing + new)
curl -s -X PUT "$PRISM_API_BASE/dns/zones/example.com./records/www.example.com./A" \
  -H "Authorization: Bearer $PRISM_TOKEN" -H "Content-Type: application/json" \
  -d '{"ttl": 300, "records": [{"content": "10.0.1.100"}, {"content": "10.0.1.101"}]}' | jq
```

### Delete a record set
```bash
curl -s -X DELETE "$PRISM_API_BASE/dns/zones/example.com./records/old.example.com./A" \
  -H "Authorization: Bearer $PRISM_TOKEN" | jq
```

### Create a new zone
```bash
# Use the nameservers shown in the Prism web UI / your account's zone defaults.
curl -s -X POST "$PRISM_API_BASE/dns/zones" \
  -H "Authorization: Bearer $PRISM_TOKEN" -H "Content-Type: application/json" \
  -d '{"name": "newzone.com.", "kind": "Native",
       "nameservers": ["ns1.example.com.", "ns2.example.com."]}' | jq
```

### Find records across all zones
```bash
# by name
curl -s "$PRISM_API_BASE/dns/records/search?q=www&record_type=A" \
  -H "Authorization: Bearer $PRISM_TOKEN" | jq
# by content (which records point at this IP?)
curl -s "$PRISM_API_BASE/dns/records/search?q=10.0.1.50&content=true" \
  -H "Authorization: Bearer $PRISM_TOKEN" | jq
```

### Enable DNSSEC, then hand the user DS records for their registrar
```bash
curl -s -X POST "$PRISM_API_BASE/dns/zones/example.com./dnssec/enable" \
  -H "Authorization: Bearer $PRISM_TOKEN" -H "Content-Type: application/json" \
  -d '{"algorithm": 13, "nsec3": false}' | jq

curl -s "$PRISM_API_BASE/dns/zones/example.com./dnssec/ds-records" \
  -H "Authorization: Bearer $PRISM_TOKEN" | jq
# -> give the ds_records values to the user to paste at their domain registrar
```

### Bulk change: preview, then apply
```bash
# preview only — show the user before applying
curl -s -X POST "$PRISM_API_BASE/dns/import/preview" \
  -H "Authorization: Bearer $PRISM_TOKEN" -H "Content-Type: application/json" \
  -d '{"data": "<zone data>", "format": "json", "mode": "merge"}' | jq
# apply
curl -s -X POST "$PRISM_API_BASE/dns/import/zones" \
  -H "Authorization: Bearer $PRISM_TOKEN" -H "Content-Type: application/json" \
  -d '{"data": "<zone data>", "format": "json", "mode": "merge", "dry_run": false}' | jq
```

---

## Endpoint quick reference

All paths are relative to `$PRISM_API_BASE`. All require the `Authorization`
header except where noted.

| Action | Method & path |
|--------|---------------|
| List zones | `GET /dns/zones?page=&limit=&search=&sort=&order=` |
| Search zones | `GET /dns/zones/search?q=&zone_type=&limit=` |
| Filter zones | `POST /dns/zones/filter` |
| Get zone | `GET /dns/zones/{zone}` |
| Create zone | `POST /dns/zones` |
| Update zone | `PUT /dns/zones/{zone}` |
| Delete zone | `DELETE /dns/zones/{zone}` |
| List records | `GET /dns/zones/{zone}/records?record_type=&name=&page=&limit=` |
| Get record set | `GET /dns/zones/{zone}/records/{name}/{type}` |
| Create record | `POST /dns/zones/{zone}/records` |
| Update record set | `PUT /dns/zones/{zone}/records/{name}/{type}` |
| Delete record set | `DELETE /dns/zones/{zone}/records/{name}/{type}` |
| Search records | `GET /dns/records/search?q=&record_type=&zone=&content=&limit=` |
| Export records | `GET /dns/records/export?format=json|csv|bind` |
| Export zones | `GET /dns/export/zones?format=&zones=&include_dnssec=` |
| Import zones | `POST /dns/import/zones` |
| Preview import | `POST /dns/import/preview` |
| DNSSEC status | `GET /dns/zones/{zone}/dnssec` |
| Enable DNSSEC | `POST /dns/zones/{zone}/dnssec/enable` |
| Disable DNSSEC | `POST /dns/zones/{zone}/dnssec/disable` |
| List keys | `GET /dns/zones/{zone}/dnssec/keys` |
| Rotate keys | `POST /dns/zones/{zone}/dnssec/rotate` |
| DS records | `GET /dns/zones/{zone}/dnssec/ds-records` |
| Validate DNSSEC | `GET /dns/zones/{zone}/dnssec/validate-crypto?check_parent=` |
| Health (no auth) | `GET /dns/health` |

> **Not available to automation tokens:** creating/listing/revoking tokens
> (`/v1/tokens*`) requires an interactive login. If the user asks the agent to
> manage tokens, direct them to **Settings → Automation Tokens** in the web UI.

---

## Error handling

Errors come back as JSON: `{"detail": "...", "error_type": "...", "status_code": N}`.

| Code | Meaning | What the agent should do |
|------|---------|--------------------------|
| 400 | Bad request | Fix the input (missing field, malformed zone name — check the trailing dot). |
| 401 | Unauthorized | Token missing/expired/revoked. Ask the user for a fresh token. |
| 403 | Forbidden | The token's account doesn't own this zone. Don't retry; tell the user. |
| 404 | Not found | Zone/record doesn't exist (or isn't owned). List to confirm the exact name. |
| 409 | Conflict | Zone already exists. Use the existing one or pick another name. |
| 422 | Validation error | Body fails schema — re-read the required fields for this endpoint. |
| 429 | Rate limited | Back off (a few seconds) and retry; batch fewer requests. |
| 5xx / 503 | Server / PowerDNS down | Not the agent's fault. Report it; retry once after a short wait. |

---

## Operating principles for the agent

- **Read before you write.** Confirm the zone exists and inspect current records
  before mutating, so you don't clobber data with a `PUT`.
- **Confirm destructive actions.** Always get explicit user sign-off before
  deleting a zone, deleting records in bulk, or running a `replace`-mode import.
- **Show your work.** After a change, read the record back and show the user the
  resulting state.
- **Never print the token.** Treat `PRISM_TOKEN` as a secret; don't echo it into
  logs or messages.
- **Stay in scope.** This skill manages DNS only. It cannot manage billing,
  users, or other tokens.
