# GovStack building blocks

> nexxus offers two GovStack building blocks through their GovStack APIs. Digital Registries runs on the records of installed packages, Messaging on the outgoing mail queue. This page explains what each one maps to, how to call it, where nexxus departs from the templates, and which building blocks it does not cover.

[GovStack](https://specs.govstack.global) describes digital government services as a set of **building blocks**: a registry, messaging, a workflow engine, identity, payments and more. Each building block has a specification with an API, so a ministry can combine products from different vendors and swap one without rewriting the service.

nexxus implements two building blocks with the APIs of their GovStack templates. They sit on top of what nexxus already has, so there is nothing to install: every nexxus instance answers under `/api/govstack`.

<Flow
  steps={[
    { icon: 'key-round', title: 'API token', text: 'A GovStack client calls with a personal API token.' },
    { icon: 'shield-check', title: 'Same rights', text: 'nexxus checks the rights of the token\'s owner, as in the app.' },
    { icon: 'blocks', title: 'Building block', text: 'Registries or Messaging answer in the GovStack format.' },
  ]}
  caption="A GovStack call is an ordinary nexxus API call with GovStack paths and bodies."
/>

## What nexxus covers

| Building block | In nexxus | Base path |
|---|---|---|
| [Digital Registries](#digital-registries) | The records of installed [packages](/modules/overview/): every entity of a package is a registry | `/api/govstack/registries` |
| [Messaging](#messaging) | E-mail through the outgoing mail queue | `/api/govstack/messaging` |

nexxus does **not** offer these building blocks as GovStack APIs:

| Building block | Why, and what nexxus has instead |
|---|---|
| Identity | nexxus is an [OpenID Connect provider](/setup/sign-in/) for other applications, but has no identity register and no GovStack Identity API. |
| Consent | No consent register. Packages can ask for consent in their forms and keep it with the record. |
| Registration | No GovStack Registration API. Packages can publish forms with personal links and a portal. |
| Scheduler | No appointment or event API. |
| Workflow | No process engine. The workflow module and its GovStack Workflow API were removed in November 2026. To be told when records meet your conditions, set up a notification rule under <Ui>My profile</Ui> › <Ui>Rules</Ui>. |
| Information Mediator | Not part of nexxus. Put an X-Road security server in front if your services talk through one. nexxus accepts the `Information-Mediator-Client` header and does not require it. |
| E-Signature, Payments, Wallet, GIS, E-Marketplace, Cloud Infrastructure | Not offered. nexxus has maps for packages, but no GovStack GIS API. |

## Before you start

- A **personal API token** of the account the client acts as. See [Personal API tokens](/setup/security/#personal-api-tokens). The token has exactly the rights of its owner: a GovStack client never sees or changes more than that person could in the app.
- The **rights** of the building block:

| Building block | Reading | Writing |
|---|---|---|
| Digital Registries | `<package>.<entity>.read`, e.g. `curiaos.kanzlei.akte.read` | `<package>.<entity>.write` |
| Messaging | `communication.email.read` (status) | `communication.email.send` (sending) |

`communication.email.send` is a key of its own, shown as <Ui>Send new e-mails (API)</Ui> under <Ui>Emails</Ui> in the role editor. Nothing else in nexxus lets a person write a new mail to any address, so only administrators hold it until a role grants it.

## Authentication

Send the token as `Authorization: Bearer cx_...`. The GovStack templates name the header differently, so the GovStack routes also take `Authorization: cx_...` and `apiKey: cx_...`.

```bash
# Which building blocks this nexxus offers, with links to the API definitions used
curl -H "Authorization: Bearer cx_..." https://<your nexxus address>/api/govstack
```

## Digital Registries

A **registry** is an entity of an installed package. Its name is `<package>.<entity>`, for example `curiaos.kanzlei.akte` for the case files of CuriaOS. Its **version** is the installed version of the package: GovStack keeps schema and API version in step, and in nexxus a schema only changes when a new package version is installed. The routes follow the data and database API templates 1.3.0 of the building block.

nexxus builds the schemas from the package manifest. `GET /api/govstack/registries/openapi.json` returns an OpenAPI 3.1 document with concrete paths and a JSON Schema for every registry you may read: choice fields list their keys as `enum`, references say what they point at.

### A record

A record is its fields plus five envelope keys. Field keys never start with `$`, so the two cannot collide.

```json
{
  "$id": 61,
  "$title": "2026/0206",
  "$version": 1,
  "$createdAt": "2026-10-20T07:08:31.104Z",
  "$updatedAt": "2026-10-20T07:08:44.927Z",
  "betreff": "Rear-end collision B 15",
  "status": "in_bearbeitung",
  "mandant": 9
}
```

| Key | Meaning |
|---|---|
| `$id` | Record id. Use it in paths (`/{id}/delete`) and in queries. |
| `$title` | Display name, as the app shows it. |
| `$version` | Version of the record. Send it back in `write.content` and nexxus refuses the change with a [conflict](#problem-types) when someone changed the record in between. |
| `$createdAt`, `$updatedAt` | Times in UTC. |

nexxus ignores envelope keys in what you write, except `$version`.

### Routes

All paths start with `/api/govstack/registries`. `{r}` is the registry name, `{v}` its installed version.

| Method | Path | What it does |
|---|---|---|
| GET | `/databases` | Every registry you may read, grouped by package, with schema and routes. |
| GET | `/database/{id}` | One registry, by its numeric `id` or its name. |
| GET | `/openapi.json` | The OpenAPI 3.1 document of your registries. |
| GET | `/data/{r}/{v}` | A page of records. See [Listing](#listing). |
| POST | `/data/{r}/{v}/create` | Creates a record from `write.content`. |
| POST | `/data/{r}/{v}/read` | The one record `query.content` matches. |
| POST | `/data/{r}/{v}/exists` | Whether a record matches `query.content`. |
| PUT | `/data/{r}/{v}/update` | Changes the fields in `write.content` on the one record `query.content` matches. |
| PUT | `/data/{r}/{v}/updateEntries` | Changes every record the query matches, at most 1,000, all or none. |
| POST | `/data/{r}/{v}/updateOrCreate` | Changes the one record the query matches, or creates it from `write.content`. |
| DELETE | `/data/{r}/{v}/{id}/delete` | Deletes the record. |
| GET | `/data/{r}/{v}/{id}/readValue/{field}.{ext}` | One field as a file. A document field returns the document; any other field `json` or `txt`. |

### Queries

`query.content` names fields and their exact values. All of them must hold.

```bash
curl -X POST https://<your nexxus address>/api/govstack/registries/data/curiaos.kanzlei.akte/1.1.0/read \
  -H "Authorization: Bearer cx_..." -H "Content-Type: application/json" \
  -d '{"query": {"content": {"aktenzeichen": "2026/0192"}}}'
```

- Any field can be queried, not only the ones a package marks as indexed.
- `$id` picks a record by its id.
- A list field matches when it holds the value.
- `read`, `update` and `updateOrCreate` need exactly one match. Several matches are a [problem](#problem-types) of type `ambiguous-query` with the number in `count`, never a silent pick of the first.

### Listing

`GET /data/{r}/{v}` returns `count`, `next` and `previous` (links to the neighbouring pages, or null) and `results`.

| Parameter | Meaning |
|---|---|
| `search` | Free-text search over the registry's search fields, as the search box in the app. |
| `query.<field>` | Exact value of a field. Repeat for more fields. |
| `filter`, `ordering` | Sorting, as the template names it: `filter` is the field, `ordering` is `ascending` or `descending`. `ordering` alone takes a field with an optional leading `-` (`-$updatedAt`). Sorting needs an indexed field, `$id`, `$createdAt` or `$updatedAt`. |
| `page` | Page number, from 1. |
| `pageSize` | Records per page, 1 to 200. Default 10. |

### Where nexxus departs from the template

- Schemas come from signed packages. `POST /database/modify` and `DELETE /database/{id}` answer `not-supported`: install or upgrade the package under <MenuPath to="Administration > Operations > Modules" />, tab <Ui>Packages</Ui>, instead.
- `GET /data/mypersonalDataUsage` answers `not-supported`. nexxus does not log who read a record. Changes are in the [audit log](/setup/security/#the-audit-log).
- `GET /databases` returns a list of groups, one per package, each with its `databases`.
- Deleting is a soft delete, as everywhere in nexxus.

## Messaging

The Messaging routes follow the e-mail part of the GovStack Messaging API. All paths start with `/api/govstack/messaging`.

| Method | Path | What it does |
|---|---|---|
| POST | `/send/email/single` | Queues one mail. Answers with a `requestUID`. |
| POST | `/send/email/batch` | Queues one mail per recipient of `recipientInfo.batchEmailAsBase64`, at most 1,000, under one `requestUID`. nexxus checks every recipient before it queues the first. |
| GET | `/status/email` | Counts the mails of a request by state. Header `requestUid`, optional `status` to pick states. |

```bash
curl -X POST https://<your nexxus address>/api/govstack/messaging/send/email/single \
  -H "Authorization: Bearer cx_..." -H "Content-Type: application/json" \
  -d '{"senderInfo": {"replyTo": "office@example.org"},
       "recipientInfo": {"email": "citizen@example.org"},
       "emailContent": {"encoding": "UTF-8", "title": "Your application", "content": "Hello,\nyour application has arrived."}}'
```

What nexxus does with a request:

- **From** is always the tenant's sender address, the one entered for the outgoing mail server during [setup](/setup/first-run/). `senderInfo.replyTo`, or else `senderInfo.email`, becomes the Reply-To. A GovStack client cannot send in someone else's name.
- `emailContent.content` is plain text. nexxus escapes it for the HTML part, so it cannot carry links or markup. Line breaks stay.
- `emailContent.title` is one line of at most 998 characters, the content at most 100,000 characters. Only UTF-8 is accepted.
- Every address is a single address. `emailCC` and `emailBCC` take one each.
- `callback.uri` is ignored. Ask `/status/email` instead.

### States

| GovStack state | In nexxus |
|---|---|
| `scheduled` | In the queue, not tried yet. Without an outgoing mail server, mails stay here. |
| `deferred` | In the queue after a failed try, or paused. |
| `delivered` | Accepted by the outgoing mail server. nexxus learns nothing after that. |
| `notSent` | Given up after the last try, or failed for good. |
| `processing`, `bounced` | Always 0. nexxus sends each mail in one step and does not see bounces. |

`POST /callback/email` answers `not-supported`. Replies reach nexxus as inbound mail of a mailbox.

## Problem types

Every error of the GovStack routes is a problem details document as in RFC 9457, with the media type `application/problem+json`. Branch on `type`. `detail` explains the case for a person, and `errors` names the fields that caused it. These texts come from the platform and can be German; the `type` and the keys of `errors` never change with the language.

```json
{
  "type": "https://nexxus-ai.systaro.de/docs/reference/govstack/#problem-version-not-installed",
  "title": "This registry version is not installed",
  "status": 404,
  "detail": "Registry 'curiaos.kanzlei.akte' is installed at version 1.1.0, not 1.0.0.",
  "instance": "/api/govstack/registries/data/curiaos.kanzlei.akte/1.0.0",
  "installedVersion": "1.1.0"
}
```

| Type | Status | When |
|---|---|---|
| <span id="problem-validation">`validation`</span> | 400 | The request breaks a rule: a missing or unknown field, a value that does not fit, an address that is no single address. |
| <span id="problem-forbidden">`forbidden`</span> | 403 | The token's owner lacks the right. |
| <span id="problem-not-found">`not-found`</span> | 404 | No such registry, record or request. `read` answers `no record found`. |
| <span id="problem-version-not-installed">`version-not-installed`</span> | 404 | The path names another version than the installed one. `installedVersion` says which is. |
| <span id="problem-conflict">`conflict`</span> | 409 | `$version` is outdated. |
| <span id="problem-ambiguous-query">`ambiguous-query`</span> | 409 | The query matches more than one record. `count` says how many. |
| <span id="problem-unique">`unique`</span> | 409 | A value that must be unique is taken. `errors` names the field. |
| <span id="problem-not-supported">`not-supported`</span> | 501 | nexxus does not offer this route. `detail` says what to do instead. |
| <span id="problem-internal">`internal`</span> | 500 | Something failed on the server. The server log has the details. |

A missing or invalid token is refused before the GovStack routes, with status 401 and the JSON of the sign-in layer, not a problem document.

## Next steps

<CardGrid>
  <LinkCard title="Security" description="Create, rotate and revoke personal API tokens." href="/setup/security/#personal-api-tokens" />
  <LinkCard title="Permissions" description="Every permission key, including communication.email.send." href="/reference/permissions/" />
  <LinkCard title="Modules (NXM)" description="Packages, their entities and how they are installed." href="/modules/overview/" />
</CardGrid>