API Overview
Nametag exposes a JSON REST API covering everything the web app itself uses: people, groups, relationships, journal entries, the map, dashboard stats, CardDAV sync, and account settings. If you can do it in the UI, there’s almost always an API endpoint behind it.
Base URL
Section titled “Base URL”All paths in this reference are relative to your Nametag instance:
https://your-instance.example.comIf you’re using the hosted service, that’s https://nametag.one. If you’re self-hosting, it’s whatever domain you’ve configured.
Authentication
Section titled “Authentication”The API supports two authentication methods.
Session cookie
Section titled “Session cookie”When you’re signed in through the browser, every request automatically carries the authjs.session-token cookie set by NextAuth.js. This is how the Nametag web app itself talks to the API. It’s not meant for external scripts since it requires a browser login flow, but it’s why the app “just works” without you thinking about tokens.
API token
Section titled “API token”For scripts, integrations, and anything outside the browser, create a personal API token in Settings > API Tokens and send it as a bearer token:
curl https://your-instance.example.com/api/people \ -H "Authorization: Bearer ntag_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"API tokens work on nearly every endpoint that accepts a session cookie. The token management endpoints themselves (/api/user/api-tokens) are the one exception: creating and revoking tokens always requires a session, since letting a token manage other tokens would be a security hole. See API Tokens for scopes, expiry, and full examples.
Response format
Section titled “Response format”Every response is JSON.
Successful responses return the resource, usually wrapped in a named key that matches the resource type:
{ "person": { "id": "clxyz...", "name": "Ada" } }or, for list endpoints:
{ "people": [ { "id": "clxyz...", "name": "Ada" } ] }Errors return an error field with a human-readable message:
{ "error": "Person not found" }Validation failures (HTTP 400) additionally include a details array with per-field messages:
{ "error": "Validation failed", "details": [ { "field": "name", "message": "Name is required" } ]}Common status codes
Section titled “Common status codes”| Status | Meaning |
|---|---|
| 200 | Success |
| 201 | Resource created |
| 400 | Bad request, usually a validation error |
| 401 | Not authenticated, missing or invalid session/token |
| 403 | Forbidden, valid auth but not allowed (e.g. a plan limit or a read-only token trying to write) |
| 404 | Resource not found, or not owned by the authenticated user |
| 409 | Conflict, e.g. trying to create a second CardDAV connection |
| 413 | Payload too large, e.g. a vCard file over 2 MB |
| 429 | Rate limited, see below |
| 500 | Server error |
| 503 | Service unavailable, used by the health check when the database is unreachable |
Rate limiting
Section titled “Rate limiting”Rate limiting applies to sensitive, unauthenticated endpoints, not to the general CRUD API. It protects things like login, registration, password reset, and CardDAV credential testing from brute-force abuse. Limits are IP-based sliding windows: for example, login allows 5 attempts per 15 minutes, and registration allows 3 attempts per hour.
Storage is Redis-backed in production when Redis is configured (falling back to an in-memory store per instance otherwise) and always in-memory in development. A 429 response includes a Retry-After header and a retryAfter field in seconds.
Everyday endpoints like /api/people or /api/groups are not rate-limited beyond your plan’s usage limits.
Rate limits by endpoint
Section titled “Rate limits by endpoint”| Endpoint | Max attempts | Window |
|---|---|---|
| Login | 5 | 15 minutes |
| Register | 3 | 1 hour |
| Forgot password | 3 | 1 hour |
| Reset password | 5 | 1 hour |
| Resend verification | 3 | 15 minutes |
| Verify email | 10 | 15 minutes |
| CardDAV test | 10 | 15 minutes |
| CardDAV sync | 5 | 5 minutes |
| CardDAV backup | 5 | 15 minutes |
Pagination
Section titled “Pagination”The REST endpoints (/api/people, /api/groups, /api/relationships, /api/relationship-types) return the full collection in one response, since personal networks are typically a few hundred people at most rather than millions of rows. The one REST endpoint that paginates is journal entries.
The web app’s own list pages (which call these same endpoints and paginate client-side) use these page sizes:
| List | Page size |
|---|---|
| Default page size (generic fallback used elsewhere in the app) | 20 |
| Maximum page size allowed by any paginated view | 100 |
| People page | 50 per page |
| Groups page | 24 per page |
| Journal | 50 per page |
GET /api/journal accepts a page query parameter and returns a pagination object alongside the entries:
{ "entries": [ /* ... */ ], "pagination": { "page": 1, "pageSize": 50, "totalCount": 132, "totalPages": 3 }}See Journal for details.
Request size limits
Section titled “Request size limits”| Body | Limit |
|---|---|
| Default API request body | 1 MB |
| JSON import | 5 MB |
| vCard import / upload | 2 MB |
| Photo upload | 50 MB |
Requests over the applicable limit are rejected with 413 Payload Too Large.
Interactive docs
Section titled “Interactive docs”The API also ships with self-documenting tools you can use alongside this reference:
- Swagger UI at
/api/docs, an interactive explorer where you can browse every endpoint and try requests against your own instance. - Raw OpenAPI spec at
/api/openapi.json, the machine-readable source of truth this reference is generated from.
Quick example
Section titled “Quick example”List the people in your network with an API token:
curl https://your-instance.example.com/api/people \ -H "Authorization: Bearer ntag_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"{ "people": [ { "id": "clx1a2b3c", "name": "Ada", "surname": "Lovelace", "organization": "Analytical Engine Society", "groups": [], "createdAt": "2026-01-15T10:00:00.000Z", "updatedAt": "2026-01-15T10:00:00.000Z" } ]}