---
title: Architecture
path: concepts/architecture
status: published
---

# Architecture

ScaiDNS is a control-plane API that sits in front of PowerDNS. This page describes how the pieces fit together and what you're actually talking to when you make an API call.

## The pieces

```mermaid
flowchart TB
    Client["Your client<br/>(SDK / curl)"]
    ScaiKey["ScaiKey<br/>(identity)"]

    subgraph API["ScaiDNS API (FastAPI)"]
        direction LR
        Auth["Auth layer"]
        Zone["Zone service"]
        Record["Record service"]
        Access["Access &<br/>audit service"]
    end

    MariaDB[("MariaDB<br/>(app database)")]
    PowerDNS[("PowerDNS<br/>(authoritative<br/>+ API + DNSSEC)")]
    Redis[("Redis<br/>(cache, rate limiting)")]
    Resolvers["Resolvers / clients"]

    Client -- "HTTPS (JWT or X-API-Key)" --> API
    ScaiKey -. "JWKS, OAuth, webhooks" .-> API

    API --> MariaDB
    API --> PowerDNS
    API --> Redis
    PowerDNS -- "UDP/TCP 53" --> Resolvers
```

**The API.** FastAPI, async, served under `/api/v1/`. Stateless: all state lives in MariaDB and PowerDNS.

**MariaDB.** The application database. Stores tenants, users (cached from ScaiKey), groups, roles, role assignments, access grants, domain metadata, audit logs, and API keys. Zone data is *not* stored here — that lives in PowerDNS.

**PowerDNS.** The authoritative DNS server. Holds the actual zones, records, DNSSEC keys, and signed zone data. ScaiDNS talks to PowerDNS via its HTTP API for writes and serves DNS responses to resolvers directly on port 53.

**Redis.** Used for rate limiting, short-lived caching (JWKS, tenant lookups), and background job coordination via [arq](https://arq-docs.helpmanual.io/).

**ScaiKey.** The identity provider. Issues OAuth tokens, manages the partner/tenant/user/group hierarchy, and sends webhook events when those entities change. ScaiDNS does not manage identities directly — it stays in sync with ScaiKey.

## Data plane vs control plane

ScaiDNS has a hard split between the two:

- **Data plane.** DNS queries. Served by PowerDNS. ScaiDNS is not in the request path. A typical resolver queries PowerDNS on port 53 directly.
- **Control plane.** Everything you read in these docs. The HTTP API, authentication, zone provisioning, access control, audit. ScaiDNS is the control plane and does not serve DNS queries itself.

This matters for two reasons:

1. **Performance.** DNS query latency is bounded by PowerDNS, not by ScaiDNS. The API being slow or down doesn't affect query resolution.
2. **Truth is PowerDNS.** If you bypass the API and write to PowerDNS directly, ScaiDNS's database will be stale. Don't do that — go through the API so audit, permissions, and validation apply.

## Authentication flow

Users authenticate through ScaiKey's OAuth 2.0 authorization code flow with PKCE:

1. Client redirects to ScaiKey's authorization endpoint.
2. User signs in at ScaiKey, returns with an authorization code.
3. Client calls `POST /api/v1/auth/token` with the code; ScaiDNS proxies to ScaiKey and returns access and refresh tokens.
4. Client includes `Authorization: Bearer <access_token>` on subsequent requests.

The JWT is validated on every request using JWKS fetched from ScaiKey. Claims (`sub`, `email`, `tenant_id`, `roles`, `scopes`) are parsed into a `CurrentUser` context used for authorization.

API keys are an alternative: send `X-API-Key: sdk_<secret>` on requests. API keys inherit permissions from their owning user or group, can carry their own rate limit and IP whitelist, and never expire unless explicitly configured. See [Authentication](./authentication.md).

## Zone lifecycle

1. **Create.** `POST /api/v1/domains/` creates a domain row in MariaDB with status `pending_validation`. PowerDNS zone is *not* created yet.
2. **Validate.** The client publishes a TXT record at their DNS provider proving ownership. `POST /api/v1/domains/{id}/validation/check` triggers DNS lookup against the authoritative nameservers. If the TXT value matches, status flips to `active` and PowerDNS zone is created.
3. **Manage.** Records are added via `POST /api/v1/domains/{id}/records`, validated server-side, and written to PowerDNS.
4. **Optional: sign.** `POST /api/v1/domains/{id}/dnssec/enable` generates KSK and ZSK in PowerDNS and surfaces DS records for publication at the registrar.
5. **Delete.** `DELETE /api/v1/domains/{id}` soft-deletes by default (recoverable via `/restore`) or hard-deletes with `?hard=true`, removing the PowerDNS zone.

Reverse zones follow the same flow but can be bootstrapped from a CIDR block — see [Reverse Zones](../tutorials/reverse-zones.md).

## Identity sync

Tenants, users, and groups are owned by ScaiKey. ScaiDNS keeps a local cache in MariaDB to support:

- Fast authorization decisions without calling ScaiKey on every request.
- Offline operation if ScaiKey is temporarily unreachable.
- Audit log records for users after they leave the organization.

Two mechanisms keep the cache fresh:

- **Webhooks.** ScaiKey sends HMAC-signed events on every change. ScaiDNS exposes `POST /api/v1/webhooks/scaikey` to receive them. See [Webhooks Deep Dive](../tutorials/webhook-integration.md).
- **Periodic sync.** A `scaidns sync` CLI command pulls the full state from ScaiKey. Useful for initial setup and recovery.

## What's next

- [Quickstart](../quickstart.md) — create your first zone.
- [Zones and Records](./zones-and-records.md) — the mental model.
- [Permissions and Access](./permissions-and-access.md) — how authorization resolves.
