nexmap documentation
Administrators
Deploying, configuring and running a nexmap server.
- Installation: container image, Coolify, first start
- Configuration: every environment variable and flag of
nexmapd - Security: TLS, the encryption key, the first administrator, accounts and tokens
Users
Working with nexmap in the browser.
- Concepts: products, structure, requirements, tasks, constraints, defects, tests, roles
- Getting started: creating a product, settings, team, structure
- Backlog: requirements, tasks, constraints, defects, boards and sprints
- Testing: test cases, campaigns, recording results
CLI users
Developers, CI pipelines and AI agents working with the nexmap command line client.
- Setup: installing, logging in, connecting a repository, configuration, commands
- Scripting: output formats, exit codes, raw API calls
- CI integration: recording automated test results with
nexmap utest - AI agents: agent guide, MCP server, what agents may change
API
- REST API: authentication, response format, endpoints
Contributing
nexmap is developed in its repository, which also holds the material for contributors that this documentation leaves out:
- the specification: what nexmap shall do, with requirement identifiers. It describes targets, not necessarily the current behaviour;
- the design decisions and their reasoning;
- CONTRIBUTING.md: building, testing, and how this documentation is organised and published.
Concepts
nexmap keeps everything about a product in one place: what it is made of, what it must do, the work to get there, how it is tested and what is broken. This page explains the building blocks and how they relate.
Products
Everything in nexmap belongs to a product. Each product has its own team, settings, structure, backlog and tests; nothing is shared between products. The dashboard shows the products you are a member of, and the product switcher in the header moves between them while staying in the same section.
Structure: components and capabilities
- Components are the parts a product is built from, such as the server, the mobile app or the billing module.
- Capabilities are what the product can do, such as “export reports” or “sign in with 2FA”. A capability belongs to a component, or directly to the product if it spans several.
Structure is optional, but it is what everything else hangs off: requirements, constraints, test cases and defects can all be linked to capabilities, which tells you, per feature area, what is required, how it is tested and what is broken.
Requirements, tasks and constraints
These three look alike, but they are different things:
| Requirement | Task | Constraint | |
|---|---|---|---|
| What it is | A statement of what the product must do | Work that says nothing about the product: maintenance, tooling, research | A rule the product must keep following |
| Example | “Users can export a report as PDF” | “Upgrade the PDF library” | “All traffic is encrypted with TLS” |
| Done when | Its definition of done holds | It is finished | Never: it is met or violated, continuously |
| Moves through the workflow | Yes | Yes | No; it has a compliance state instead |
| Linked to capabilities and test cases | Yes | No | Capabilities only |
Each one gets an identifier from its category: FR-12 is the 12th item in the product’s
numbering, in the category with prefix FR. Requirements and tasks share one counter, so FR-12 and
MT-12 can never both exist. Use identifiers in conversation, commit messages and code comments.
New products start with these categories, which the product owner can change:
- Requirements: Functionality (
FR), Architecture (AR), Security (SR), UX (UR) - Tasks: Maintenance (
MT), Tooling (TL), Spike (SP) - Constraints: Architecture (
AC), Security (SC), UX (UC)
Requirements and tasks can be broken down: a requirement can have child requirements or tasks, and a task can have child tasks. A requirement never sits below a task. Items that still need breaking down can be marked coarse.
Together, the open requirements and tasks are the product’s backlog. See Backlog.
Defects
A defect is something that does not work as it should. It has a criticality (low, medium, high, critical), moves through the same kind of workflow as the backlog, and can be linked to the components, capabilities, requirements and test cases it affects.
Test cases and testing campaigns
- A test case describes one check. It is manual (a person carries it out) or automatic (a test in code, identified by its test identifier, such as a Go test function name). It can run in several runtime environments, such as operating systems or browsers, and sit at a level of the test pyramid (by default Unit, Integration, End-to-End).
- A testing campaign is one round of testing: a selection of test cases, each with a result per environment. Results start as pending and become passed, failed, blocked, skipped or re-test. A failed result always names the defect that explains it.
See Testing.
Milestones and sprints
- Milestones mark points on the product’s roadmap. Their type says what they are: planning, release or internal. Components, capabilities, requirements, constraints, defects and sprints can be assigned to a milestone; an item belongs to at most one.
- Sprints are time boxes of work, used when a product works in scrum mode. See Backlog.
Comments, attachments and history
Requirements, tasks, constraints, defects, test cases, components, capabilities and milestones all have comments and attachments, and every change to them is recorded in their History tab: who changed what, and when. Descriptions and comments are written in Markdown.
Roles
What you can do in a product depends on your role in it. Each role includes everything the roles below it can do:
| Role | Can |
|---|---|
| Product owner | Everything in the product: settings, team, structure, backlog, campaigns |
| Developer | Create and edit test cases; comment; change the status of requirements, tasks and defects once they are activated |
| Tester | Record campaign results; create defects; comment on defects and test cases |
| Viewer | Read everything, change nothing |
Administrators manage the whole installation (users and instance settings) and can act in every product.
Getting started
This page walks a product owner through setting up a product: creating it, adjusting its settings, bringing in the team and describing its structure. The terms are explained in Concepts.
Create a product
On the dashboard, choose Create Product (/products/new). Any user can do this if the administrator has
enabled product creation; administrators always can. You become the product’s owner.
| Field | Meaning |
|---|---|
| Name, description | Shown on the dashboard and the product overview |
| Estimation mode | How backlog items are estimated: story points, t-shirt sizes, hours, or disabled |
| Workflow mode | Kanban (one continuous board) or scrum (work planned into sprints); see Backlog |
Adjust the settings
Settings in the product’s sidebar has five tabs. Changes are saved as you make them.
- General: name, description, estimation mode and workflow mode.
- Categories: the categories of requirements, tasks and constraints, each with a name and an
identifier prefix. The defaults are listed in Concepts.
Settle the prefixes early, before identifiers like
FR-12appear in commit messages. - Statuses: the workflow columns. New products start with New, In Progress, In Review and Done. One status is the default for new items; statuses marked closed (like Done) count as finished, so their items drop out of “open” lists. At first, requirements, tasks and defects share one set of statuses. If defects need a different workflow, split the sets; splitting cannot be undone.
- Environments: the runtime environments tests run in, such as
linux,windowsorfirefox. Name them the way your CI jobs will (see CI integration). - Pyramid levels: the levels of the test pyramid, by default Unit, Integration and End-to-End.
Bring in the team
On the Team page, search for existing users, add them with a role, and change or remove roles later. The roles are explained in Concepts.
People who have no account yet can be invited:
- Under Invite New User, enter their email address and the role they should get, and choose Send Invitation.
- nexmap shows an invitation link. It does not send emails, so pass the link on yourself, through a channel only that person reads: whoever opens it can use it.
- The link opens a page where they choose a username and password. Their account gets the invited email address, joins the product with the invited role, and they are signed in right away.
An invitation link works once and expires after 7 days. Accounts created from an invitation never need an administrator’s approval.
If the administrator has enabled self-registration, people can also create an account themselves on the
sign-in page (Register) and then be added to the product by you. With moderation enabled, such an
account waits for an administrator’s approval under /admin/users before it can sign in.
To hand the product over, use Transfer Ownership next to a member. They become the owner and you become a developer.
Describe the structure
Under Structure in the sidebar:
- Create the components the product consists of.
- Create capabilities, each under a component or at product level.
- Optionally, lay out the roadmap as milestones.
The structure can grow over time; nothing else depends on it being complete.
Fill the backlog and the test catalogue
- Write down the rules that always apply as constraints, and what the product must do as requirements, each with a definition of done. Link both to the capabilities they concern. See Backlog.
- Describe how requirements are verified as test cases, linked to the requirements they cover. For
automated tests,
nexmap test synccan create the test cases from a test run instead; see CI integration. - When it’s time to test, run a testing campaign; see Testing.
Your own account
Under your user menu:
- Profile (
/user/profile): display name, email address, password, theme, two-factor authentication, and a request to delete your account. - API Tokens (
/user/tokens): personal access tokens for the CLI.
Backlog
The backlog holds the product’s requirements, tasks and defects, and the boards and sprints you work them off with. Constraints live next to them. The differences between these are explained in Concepts.
Requirements and tasks
Requirements and Tasks in the sidebar list them separately. Product owners create and edit them.
| Field | Meaning |
|---|---|
| Category | Decides the identifier prefix, such as FR or MT |
| Title, description | The description is written in Markdown |
| Definition of done | Requirements only: when the requirement counts as fulfilled. Test cases can be linked to it |
| Parent | Breaks work down: a requirement under a requirement, a task under either |
| Status | Position in the workflow; new items get the product’s default status |
| Milestone | The milestone this item contributes to |
| Sprint | Scrum mode only: the sprint the item is planned into |
| Priority | A number; lower numbers mean higher priority |
| Estimate | Only when the product uses estimation, in the product’s unit |
| Coarse | Marks an item that still needs breaking down |
A requirement’s detail page shows its links in tabs: the capabilities it concerns, the test cases that verify it, and the defects reported against it. Tasks have no such tabs, because they make no statement about the product to verify.
Constraints
Constraints are rules that always apply, such as “all traffic is encrypted”. Instead of a workflow status, each has a compliance state: not assessed, met or violated. Review constraints when designing a change, and set a constraint to violated when the product breaks it, so the problem stays visible until it is fixed. Constraints can be linked to capabilities and assigned to a milestone.
Defects
Defects records what is broken. Testers, developers and product owners can report defects, testers usually from a failed test result (see Testing). Editing a defect’s fields is up to product owners; developers change its status (see Activation).
A defect has a criticality (low, medium, high, critical), a status, optionally the runtime environment it occurs in, and links to the components, capabilities, requirements and test cases it affects. From a defect’s Test Cases tab you can link an existing test case or create a regression test for it.
Moving a defect into a closed status (such as Done) puts every failed result it explains back to re-test in its testing campaign; see Testing.
Working with lists
All lists work the same way:
- The search box matches the text columns; filters narrow down columns with a fixed set of values, such as status or category. A single control resets search and filters.
- Clicking a column header sorts ascending, then descending, then back to the default order. Statuses sort in workflow order and criticalities by severity.
- Search, filters, sorting and the page are part of the URL, so a filtered view can be bookmarked or shared.
Kanban or scrum
The product’s workflow mode decides how work is organised.
Kanban
Board shows all requirements, tasks and defects in one column per status. Drag a card to another column to change its status. If the product’s statuses have been split, requirements and defects get separate boards. Defects also has a board of its own.
Scrum
Work is planned into sprints, each with a start and end date and optionally a milestone it leads to. A sprint’s detail page has four tabs:
- Board: the sprint’s items in status columns, as on the kanban board.
- Planning: the product backlog next to the sprint’s contents; drag items between them. Only product owners can plan.
- Summary: how many items are in which status, progress, and estimate totals.
- History: the sprint’s changes.
A sprint is planned until its owner chooses Start Sprint, and completed after Close Sprint. Only one sprint per product can be active at a time. Closing a sprint does not move unfinished items; plan them into the next sprint.
Activation: what developers may move
Developers may change the status of a requirement, task or defect only once a product owner has activated it. This keeps the product owner in charge of what gets worked on. Product owners can always change statuses.
-
In scrum mode, planning an item into a sprint activates it, and taking it out of the sprint deactivates it again.
-
In kanban mode, items are meant to be activated directly, but the web UI has no control for this yet. Until it does, a product owner can activate an item with the CLI:
nexmap api PATCH /products/{product}/requirements/FR-12 --data '{"is_active": true}'
The detail page shows whether an item is active.
Testing
Testing in nexmap has two parts: a catalogue of test cases that describes how the product is verified, and testing campaigns that record the results of one round of testing.
Test cases
Test Cases in the sidebar lists the catalogue. Developers and product owners create and edit them.
| Field | Meaning |
|---|---|
| Title, description | What is checked and how; for manual tests, the steps to follow |
| Execution mode | Manual (a person carries it out) or automatic (a test in code) |
| Test identifier | Automatic tests only: the name that test results report, such as a Go test function name. It connects CI results to the test case |
| Component, capability | What part of the product the test case covers |
| Pyramid level | Unit, Integration, End-to-End, or the product’s own levels |
| Environments | The runtime environments the test runs in. A campaign tracks one result per selected environment; with none selected, there is a single result |
On a test case’s Requirements tab, link the requirements it verifies, either the requirement as a whole or its definition of done. The Campaigns tab shows how the test case fared across campaigns.
Automated tests can be registered in bulk from a test run with nexmap test sync; see
CI integration.
Testing campaigns
A campaign moves through three states:
- Draft: a product owner creates it under Campaigns with a name, an optional description and planned dates, and selects the test cases in scope. Filters for component, capability, pyramid level and execution mode, plus a select-all over the filtered list, help to pick them. The scope can still change while the campaign is a draft.
- Active: the product owner activates it. The scope is now fixed: test cases created later do not join. Results can only be recorded while a campaign is active, and only one campaign per product can be active at a time.
- Closed: the product owner closes it, which freezes all results. Closing a campaign with results that are still pending, blocked or awaiting re-test asks for confirmation first.
Recording results
The campaign’s Matrix tab shows test cases in rows and environments in columns, with each result’s status in its cell. Rows can be grouped by component or by pyramid level. Testers, developers and product owners click a cell to set its result:
| Status | Meaning |
|---|---|
| Pending | Not run yet; every result starts here |
| Passed | The check succeeded |
| Failed | The check failed; requires a defect |
| Blocked | The check could not be carried out, for example because something it depends on is broken |
| Skipped | Deliberately not run in this campaign |
| Re-test | The defect behind a failure was fixed; run the check again |
A small amber dot on a cell marks a result whose status has changed more than once in this campaign.
Marking a result as failed always requires a defect that explains it: link an existing one, or create one on the spot. The defect is linked to the test case automatically.
Automated tests record their results from CI instead; see CI integration.
The defect loop
When a defect that explains failed results is moved into a closed status, those results move to re-test. They are not marked as passed automatically: a tester runs the check again and records the outcome. For automated tests, the next green CI run does this.
Progress and verdict
The Progress tab shows how many results are in which status and how that changed over time; the History tab lists every result change with who made it and when.
A campaign is complete when no result is pending, blocked or awaiting re-test. It passes when every result that was not skipped has passed.
Installation
Container image
The Dockerfile’s default stage is the server image: nexmapd alone on a distroless base, running as a
non-root user, listening on port 8080 on all interfaces, with a HEALTHCHECK (nexmapd healthcheck).
It keeps the SQLite database and attachments in /data, so mount persistent storage there. Migrations
run automatically when the server starts.
When building with podman, pass --format docker: the default OCI image format drops the HEALTHCHECK.
Coolify
Create the application with the Dockerfile build pack and:
- add persistent storage (a volume) with destination path
/data; - set the environment variables, at least
NEXMAP_ENCRYPTION_KEY(see generating a key), andNEXMAP_ADMIN_USER/NEXMAP_ADMIN_PASSWORDfor the first start; for PostgreSQL,NEXMAP_DB_DRIVER=postgresandNEXMAP_DB_DSN. All variables are listed in the configuration reference; - nothing for the version: the build reads it from the repository’s git tags, as
go builddoes locally (v0.1.0on a tagged commit,v0.1.1-0.<date>-<commit>after it). If the checkout has no tags (a shallow clone), it reportsv0.0.0-<date>-<commit>; set the build variableVERSIONto override it.
Ports (8080) and the health check come from the image. Coolify’s proxy terminates TLS; see Security for what the server expects from it.
After the first start
Sign in as the administrator created from NEXMAP_ADMIN_USER / NEXMAP_ADMIN_PASSWORD (or with the
generated password from the log, see the first administrator).
Then change the administrator’s email address on the profile page, and review the instance settings under
/admin/settings: self-registration, moderation, product creation, enforced 2FA and password rules.
Server configuration
nexmapd is configured through environment variables, most of which also have a command line flag.
Each setting is taken from the first source that has it: flag, then environment variable, then the
built-in default. There is no configuration file.
The container image sets some variables differently from the built-in defaults; both are listed below.
Reference
Network
| Variable | Flag | Default | Image | Meaning |
|---|---|---|---|---|
NEXMAP_HOST | --host | localhost | 0.0.0.0 | Address to listen on. 0.0.0.0 listens on all interfaces |
NEXMAP_PORT | --port | 8080 | 8080 | Port to listen on |
nexmapd serves plain HTTP. Put it behind a reverse proxy that terminates TLS; see
Security.
Database
| Variable | Flag | Default | Image | Meaning |
|---|---|---|---|---|
NEXMAP_DB_DRIVER | --db-driver | sqlite | sqlite | sqlite or postgres |
NEXMAP_DB_DSN | --db-dsn | nexmap.db | /data/nexmap.db | For SQLite, the database file path. For PostgreSQL, a connection URL (postgres://user:password@host:5432/nexmap?sslmode=require) or a key/value string (host=… user=… dbname=…) |
SQLite suits small installations: one file, no separate server, run in WAL mode with a single connection.
PostgreSQL suits larger ones; nexmapd opens up to 25 connections. Either way, the database is created
or migrated automatically at startup, and the server refuses to start if it cannot reach it.
Encryption
| Variable | Flag | Default | Meaning |
|---|---|---|---|
NEXMAP_ENCRYPTION_KEY | — | none, required | Key ring for encrypting personal data, as JSON. See Security |
The server does not start without it.
First administrator
| Variable | Flag | Default | Meaning |
|---|---|---|---|
NEXMAP_ADMIN_USER | — | admin | Username of the administrator created on first start |
NEXMAP_ADMIN_PASSWORD | — | generated | Password of that administrator |
These are only read when the database has no users yet, i.e. on the very first start. See Security.
Sessions and passwords
| Variable | Flag | Default | Meaning |
|---|---|---|---|
NEXMAP_SESSION_TTL | --session-ttl | 24h | How long a web UI session lasts, as a Go duration (8h, 30m, 168h) |
NEXMAP_COOKIE_SECURE | --cookie-secure | true | Mark session cookies Secure, so browsers send them over HTTPS only. Set to false only for local development over plain HTTP |
NEXMAP_PBKDF2_ITERATIONS | --pbkdf2-iterations | 600000 | PBKDF2-SHA256 iterations for password hashes |
Each password hash records its own iteration count, so changing NEXMAP_PBKDF2_ITERATIONS applies to
passwords set from then on; existing passwords keep working.
Attachments
| Variable | Flag | Default | Image | Meaning |
|---|---|---|---|---|
NEXMAP_UPLOAD_DIR | --upload-dir | ./uploads | /data/uploads | Directory for uploaded attachments |
NEXMAP_MAX_UPLOAD_BYTES | --max-upload-bytes | 10485760 (10 MiB) | Largest accepted attachment, in bytes | |
NEXMAP_ALLOWED_UPLOAD_TYPES | --allowed-upload-types | empty: all types | Comma-separated presets and/or file extensions |
The presets are images (.png, .jpg, .jpeg, .gif, .webp), logs (.txt, .log, .json,
.xml, .csv, .md, .yaml, .yml) and documents (.pdf). For example,
NEXMAP_ALLOWED_UPLOAD_TYPES=images,logs,zip allows those presets plus .zip files.
Besides the extension, the server checks the file content: a file named .png must really be an image,
and a text extension rejects content recognised as an archive, PDF or media file. This stops mislabelled
uploads but is not a malware scanner. Attachments are always served as downloads, never rendered or
executed by the browser.
Logging
| Variable | Flag | Default | Meaning |
|---|---|---|---|
NEXMAP_LOGLEVEL | --log-level | info | debug, info, warn or error |
NEXMAP_LOGFILE | --log-file | empty: stdout | Write the log to this file (appended, mode 0600) instead of stdout |
Logs are JSON, one entry per line.
Development
| Variable | Flag | Default | Meaning |
|---|---|---|---|
NEXMAP_DEV_MODE | — | false | Enables the component catalogue at /dev/catalogue and fills a newly created database with demo products and users. Never enable it in production |
Value formats
- Boolean variables (
NEXMAP_COOKIE_SECURE,NEXMAP_DEV_MODE) are true fortrueor1; any other value means false. - An invalid duration or number (
NEXMAP_SESSION_TTL,NEXMAP_PBKDF2_ITERATIONS,NEXMAP_MAX_UPLOAD_BYTES) is ignored and the default is used, without a warning. Check the values carefully. - An unknown log level or database driver stops the server with an error.
Health check
nexmapd healthcheck requests /api/v1/health from the server running with the same configuration and
exits with 0 if it answers. The container image uses it as its HEALTHCHECK; outside a container, the
endpoint can be polled directly.
Minimal production setup
NEXMAP_ENCRYPTION_KEY='[{"id":"2026-01","key":"<base64 key>"}]'
NEXMAP_ADMIN_USER=jdoe
NEXMAP_ADMIN_PASSWORD='<long random password>'
# for PostgreSQL instead of SQLite:
NEXMAP_DB_DRIVER=postgres
NEXMAP_DB_DSN='postgres://nexmap:<password>@db:5432/nexmap?sslmode=require'
Behind a TLS-terminating proxy, the remaining defaults are suitable for production.
Security
This page covers what an administrator has to set up and look after to run nexmap safely. Every setting mentioned is described in the configuration reference.
TLS and the reverse proxy
nexmapd speaks plain HTTP. Run it behind a reverse proxy that terminates TLS (on Coolify, the built-in
Traefik does this), and do not expose its port to the network directly:
- Leave
NEXMAP_COOKIE_SECUREattrue. Browsers then send the session cookie over HTTPS only. - The server records the
X-Forwarded-Forheader, without checking it, as the client address of sessions and failed logins. Only a proxy in front of the server makes that address trustworthy, and the proxy should replace a header sent by the client rather than append to it.
The encryption key
Personal data (email addresses and display names) is encrypted in the database with AES-256-GCM.
Usernames are treated as pseudonyms and are not encrypted. The key comes from NEXMAP_ENCRYPTION_KEY,
a JSON list of keys with an ID and a base64-encoded 32-byte key each:
[{"id": "2026-01", "key": "<32 random bytes, base64-encoded>"}]
To generate one:
printf '[{"id":"%s","key":"%s"}]\n' "$(date +%Y-%m)" "$(openssl rand -base64 32)"
Treat the key like a database password: keep it in your platform’s secret store, not in a repository.
Back up the key separately from the database. Without it, the personal data in a database backup cannot be decrypted, and nexmap has no way to recover it. Conversely, a database backup stored together with its key protects nothing.
Rotating the key
The first key in the list encrypts new data; the others are kept only to decrypt data written with them. To switch to a new key, put it first and keep every older key in the list:
[{"id": "2027-01", "key": "<new key>"}, {"id": "2026-01", "key": "<old key>"}]
Never remove an old key: data encrypted with it would become unreadable.
Rotation is incomplete today, so rotate only when you have a reason to, such as a leaked key:
- Existing data is not re-encrypted. It stays encrypted with the old key until the record is next changed, so a leaked old key still exposes it.
- Email addresses also get a lookup hash derived from the first key, which is how the database prevents two accounts from sharing an address. After a rotation, that check only compares addresses hashed with the same key, so a duplicate of an address stored under the old key is not caught.
The first administrator
On the very first start, when the database has no users, nexmapd creates an administrator from
NEXMAP_ADMIN_USER and NEXMAP_ADMIN_PASSWORD. If the username is unset it is admin; if the password is
unset, a random one is generated and written to the log.
Set both variables for the first start. If you rely on the generated password, read it from the log, sign
in, change the password right away, and keep in mind that the log still contains it. The administrator’s
email address starts out as admin@localhost; change it on the profile page.
Once users exist, both variables are ignored and can be removed.
Accounts and sign-in
- Two-factor authentication: users can set up TOTP on their profile page. Enforce 2FA in the
instance settings (
/admin/settings) requires it for everyone. An administrator can reset a user’s 2FA from the user’s detail page, after which that user can sign in with the password alone until they set it up again. - Passwords are stored as salted PBKDF2-SHA256 hashes (
NEXMAP_PBKDF2_ITERATIONS). The instance settings can require uppercase and lowercase letters, digits and special characters in new passwords. - Email is not implemented yet: there are no password reset or invitation emails. Administrators set new passwords for users from the user’s detail page.
- Sessions expire after
NEXMAP_SESSION_TTL(24 hours by default).
New accounts
People get an account in one of two ways:
- Invitation: a product owner invites someone to their product with a role (on the product’s Team
page), or an administrator invites someone to the installation (
/admin/invitations). nexmap shows a link, which the inviter passes on. Whoever holds the link can create the account, so treat it as a secret: it works once, expires after 7 days, and is never written to the log. Accounts created from an invitation skip moderation. - Self-registration, if self-registration is enabled in the instance settings: anyone who can reach
the server can create an account on
/register. With moderation enabled, such accounts wait for an administrator’s approval under/admin/usersbefore they can sign in. A self-registered account belongs to no product until a product owner adds it.
Only product owners (and administrators) can invite to a product, and only administrators can invite to the installation without a product.
Access tokens
Personal access tokens, which the CLI, CI jobs and AI agents use, carry every right of the user who
created them; they cannot be restricted to reading or to one product yet. For automation, create a
dedicated user with the lowest product role that does the job (usually tester for CI), and revoke
tokens that are no longer used under API Tokens (/user/tokens).
Attachments
By default, every file type can be uploaded. To restrict this, set NEXMAP_ALLOWED_UPLOAD_TYPES; the
server then also checks that the content matches the extension. Attachments are always served as
downloads and never rendered by the browser, but nexmap does not scan them for malware.
Development mode
NEXMAP_DEV_MODE fills a new database with demo users whose password is publicly known (it is in the
source code and the log). Never enable it on a server that others can reach.
Setting up the CLI
nexmap is the command line client for a nexmap server. It talks to the server only through the
REST API, so it needs nothing but the server’s URL and a personal access token.
Installing
Where your Go module proxy can reach the module:
go install nexmap.net/cmd/nexmap@<version>
Alternatively, build it from a checkout (just build, or just container-build for release binaries;
see CONTRIBUTING.md). The binary has no runtime dependencies.
Logging in
-
In the web UI, open API Tokens in the user menu (
/user/tokens) and create a token. -
Store it:
nexmap login --server https://nexmap.example.comOn a terminal,
loginprompts for the token without echoing it. Otherwise it reads the first line of stdin, which suits scripts:printf '%s\n' "$TOKEN" | nexmap login --server …. There is deliberately no flag for the token itself, as it would end up in shell history and process listings.
login checks the token with the server and stores it per server in the user configuration file
(config.json in the user configuration directory, see below), readable only by you. nexmap logout
removes it again; nexmap whoami shows the user, server and product in effect and where each value
came from.
A token carries all the rights of the user who created it. For CI jobs and AI agents, create a dedicated user with only the product role the job needs.
Connecting a repository
In the repository, run:
nexmap init
This writes .nexmap.json at the repository root (or into --dir) with the server and product:
{
"server": "https://nexmap.example.com",
"product": "<product id>"
}
The product comes from --product if given; if you are a member of only one product, that one is used;
otherwise init lists the products and asks. Without a terminal it fails and lists the choices instead,
so pass --product in scripts. --force overwrites an existing file, and --agents additionally sets
up AI agents (see AI agents).
Commit .nexmap.json: it never contains a token, and every command run anywhere in the repository finds
it by walking up from the current directory.
Configuration
Each setting is taken from the first source that has it, in this order:
- Command line flags
- Environment variables
.nexmap.jsonin the current directory or the nearest parent- The user configuration file
| Setting | Flag | Environment | .nexmap.json | User configuration |
|---|---|---|---|---|
| Server URL | --server | NEXMAP_SERVER | server | — |
| Product ID | --product | NEXMAP_PRODUCT | product | — |
| Output format | -o, --output | NEXMAP_OUTPUT | — | output |
| Token | — | NEXMAP_TOKEN | — | stored per server by login |
| Plain HTTP | --allow-http | NEXMAP_ALLOW_HTTP | — | — |
NEXMAP_TOKEN takes precedence over a stored token; login warns when it is set. Boolean variables
accept 1, true, yes or on.
The user configuration directory is nexmap inside the operating system’s configuration directory:
$XDG_CONFIG_HOME/nexmap (usually ~/.config/nexmap) on Linux, ~/Library/Application Support/nexmap
on macOS, %AppData%\nexmap on Windows. Set NEXMAP_CONFIG_DIR to use another directory.
Plain HTTP
The CLI refuses http:// servers unless they run on the local machine (localhost, *.localhost or a
loopback address), so a token never crosses the network unencrypted. --allow-http (or
NEXMAP_ALLOW_HTTP=1) lifts this deliberately, e.g. on a trusted internal network.
Commands
| Command | Purpose |
|---|---|
login, logout, whoami | Manage the stored token, show what is in effect |
init | Write .nexmap.json; with --agents, set up AI agents |
version | Client version and, if a server is configured, the server version |
context | Product briefing: structure, constraints, work in progress, defects, testing status |
req, task | Requirements and tasks: list, show, status, create |
constraint | list, show |
component, capability | Product structure |
defect | list, show, status, create |
test | Test cases: list, show, sync |
campaign | status of the active testing campaign |
comment | add a comment to any commentable item |
utest | Record test results from CI, see CI integration |
api | Raw REST call, see Scripting |
agent-guide, mcp | For AI agents, see AI agents |
nexmap help <command> (e.g. nexmap help req show) lists a command’s arguments and flags.
Requirements, tasks and constraints are referenced by their identifier (FR-12, MT-3, AC-42),
defects and test cases by their short ID (the first 8 characters of the ID, shown in lists), test cases
also by their test identifier, and capabilities by name.
Scripting with the CLI
The output formats and exit codes below are a stable contract for scripts and AI agents.
Output formats
| Format | Use |
|---|---|
-o table | Plain tables and records for people. The default |
-o md | Compact markdown with the same headings every time; best for pasting into an AI agent’s context |
-o json | Machine-readable, with stable field names; use it for anything that parses the output |
The default is table whether or not stdout is a terminal, so a command’s output never changes with the
environment it runs in: scripts must ask for -o json explicitly. To change your personal default, set
NEXMAP_OUTPUT or output in the user configuration file (see Setup).
Errors and exit codes
| Exit code | Meaning |
|---|---|
0 | Success |
1 | Usage or configuration error (bad arguments, no server configured, ambiguous reference) |
2 | API error: server unreachable, authentication failed, or not permitted for your role |
3 | Not found: the item does not exist or is not visible to you |
4 | Tests failed (only utest --exit-code) |
Errors go to stderr as a single line, nexmap: <message>, followed by a hint for authentication and
role errors. With -o json, stderr carries {"error": {"code": "…", "message": "…"}} instead.
A role refusal (exit 2) is final: retrying will not help; the user needs a different role in the product.
Long texts
Commands that take a description or comment body accept it from a file or stdin (--body-file -), which
avoids shell quoting problems with markdown:
nexmap comment add FR-12 --body-file - <<'EOF'
Decided to keep the old endpoint until **v2**.
EOF
Raw API calls
For anything without a dedicated command, nexmap api sends an authenticated request to the
REST API and prints the response:
nexmap api /products/{product}/requirements -o json
nexmap api POST /products/{product}/requirements --data @new.json
generate-body | nexmap api POST /products/{product}/requirements --data @-
The method defaults to GET; {product} in the path is replaced with the configured product; --data
takes JSON text, @file or @- for stdin.
Retries
Read requests (GET, HEAD, OPTIONS) are retried automatically after network errors and 429,
502, 503 and 504 responses. Requests that change data are sent once, so a failed write is never
applied twice behind your back.
CI Integration
nexmap utest records automated test results in the product’s active testing campaign (FR-018, FR-409). It reads a test report from stdin or from files, maps each test to an automatic test case by its test identifier, and uploads the run in one request.
go test -json ./... | nexmap utest --env linux-amd64
go test -v ./... | nexmap utest --identifier-style name # format auto-detected
nexmap utest --format junit reports/*.xml
What happens to a run
| Situation | Effect |
|---|---|
| Test passes / is skipped | Its campaign result is set to passed / skipped; a defect on the result is cleared |
| Test fails | Result set to failed. An open defect already on the result is reused; otherwise a defect Automated test failed: <identifier> is created with the run metadata and the test’s output (last 64 KiB), linked to the test case |
| Status unchanged since the last run | Nothing is written, so repeated CI runs do not flood the result history |
Subtests (TestX/case) without their own test case | Rolled up into TestX: any failure fails it, the failing subtests’ output goes into the defect |
| Test case runs per environment | Needs --env NAME matching a runtime environment of the product; other environments’ results are untouched |
| No automatic test case has the identifier, or the test case is not in the campaign | Not recorded, no defect; failures are still listed in the build log |
| No active campaign | The upload is accepted, nothing is written (no results, events or defects), and the CLI prints no active campaign — results not recorded |
The FR-504 loop works with automation: closing the defect moves the result to re-test, and the next green run marks it passed. A later failure files a new defect, since the old one is closed.
Each status change is recorded in the result history with the token’s user, the report format (go-test, junit), and the revision and branch.
Setting up
- Create a CI user with the tester role in the product (tokens carry their user’s full rights, so do not use a personal token), sign in as that user and create a personal access token under API Tokens in the user menu (
/user/tokens). - Register the tests as automatic test cases with a test identifier.
nexmap test synccreates the missing ones from a report (developer role):
New test cases join campaigns created afterwards; an active campaign keeps the scope it was created with.go test -json ./... | nexmap test sync --dry-run # review first go test -json ./... | nexmap test sync - Check the mapping before wiring CI:
go test -json ./... | nexmap utest --dry-runshows how each test maps to a test case and what would change. - Install the CLI in the job (see Setup): a release binary, or
go install nexmap.net/cmd/nexmap@<version>where your Go proxy can reach the module (the snippets below assume the latter). - Configure the job with
NEXMAP_SERVER,NEXMAP_PRODUCTand the secretNEXMAP_TOKEN(or commit.nexmap.jsonfromnexmap initand only set the token; see Setup).
Options
| Flag | Meaning |
|---|---|
--format auto|go-json|go-text|junit | Report format; auto sniffs the input (JSON events → go-json, XML → junit, else go test -v text) |
--identifier-style package|name | package (default): pkg/path.TestName for Go, classname.name for JUnit; name: the bare test name. Match whatever your test cases use |
--env NAME | Runtime environment of this run (case-insensitive); unknown names are rejected |
--campaign NAME | Record in this campaign instead of the active one (must be active) |
--revision, --branch | Default from GITHUB_SHA/GITHUB_REF_NAME, CI_COMMIT_SHA/CI_COMMIT_REF_NAME, … or git rev-parse |
--dry-run | Evaluate and print the mapping; write nothing |
--exit-code | Exit 4 if any test (or a package build) failed |
--quiet | Do not copy stdin to stdout |
When reading stdin, utest copies its input to stdout so the test log stays in the build output, then prints a summary. With -o json it prints only the JSON report.
Exit status. In go test -json ./... | nexmap utest, the shell reports the exit status of nexmap, not of go test. Either let nexmap fail the job with --exit-code, or use set -o pipefail (bash) so a failing go test still fails it. Without either, the job passes even when tests fail.
GitHub Actions
name: test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: stable
- name: Install nexmap
run: go install nexmap.net/cmd/nexmap@latest
- name: Test
env:
NEXMAP_SERVER: https://nexmap.example.com
NEXMAP_PRODUCT: ${{ vars.NEXMAP_PRODUCT }}
NEXMAP_TOKEN: ${{ secrets.NEXMAP_TOKEN }}
run: go test -json ./... | nexmap utest --env linux-amd64 --exit-code
For a build matrix, pass the matrix value: --env ${{ matrix.os }}, with one runtime environment per OS in the product.
GitLab CI
test:
image: golang:1.26
variables:
NEXMAP_SERVER: https://nexmap.example.com
NEXMAP_PRODUCT: "<product id>"
# NEXMAP_TOKEN: set as a masked CI/CD variable
script:
- go install nexmap.net/cmd/nexmap@latest
- go test -json ./... | nexmap utest --env linux-amd64 --exit-code
Forgejo / Gitea Actions
Forgejo Actions sets the same GITHUB_* variables, so revision and branch are picked up automatically.
on: [push]
jobs:
test:
runs-on: docker
container:
image: golang:1.26
steps:
- uses: https://code.forgejo.org/actions/checkout@v4
- run: go install nexmap.net/cmd/nexmap@latest
- env:
NEXMAP_SERVER: https://nexmap.example.com
NEXMAP_PRODUCT: ${{ vars.NEXMAP_PRODUCT }}
NEXMAP_TOKEN: ${{ secrets.NEXMAP_TOKEN }}
run: go test -json ./... | nexmap utest --env linux-amd64 --exit-code
Other ecosystems
Anything that writes JUnit XML works: pytest (--junitxml=report.xml), jest (jest-junit), Maven Surefire (target/surefire-reports/*.xml), gotestsum (--junitfile).
pytest --junitxml=report.xml; status=$?
nexmap utest --format junit report.xml
exit $status
With the default package style, pytest identifiers look like tests.test_calc.test_add and Surefire ones like com.example.CalcTest.testAdd; jest-junit’s default (classname equal to name) yields the test title.
The upload endpoint behind nexmap utest is described in the REST API reference.
AI agents
AI coding agents working in a repository can read the product definition, backlog and QA state from nexmap, and, within their user’s role, update it. They use either the CLI or, for agents that speak the Model Context Protocol (Claude Code and others), the same operations as MCP tools.
Setting up a repository
After connecting the repository and logging in:
nexmap init --agents
This does two things on top of nexmap init (an existing .nexmap.json is kept):
-
registers the MCP server in the repository’s
.mcp.json, keeping any other servers in it:{ "mcpServers": { "nexmap": {"command": "nexmap", "args": ["mcp", "--read-only"]} } }An existing
nexmapentry is left alone unless you pass--force. -
appends the agent guide (
nexmap agent-guide) toCLAUDE.mdif that exists, otherwise toAGENTS.md, once.
The MCP server uses the same configuration and token as the CLI, so nothing else is needed. If the
configuration or token is missing, the server still starts and every tool call returns the reason
(e.g. “run nexmap login”), which the agent can pass on to you.
What agents can do
nexmap agent-guide prints the guide agents are given: the commands and tools, how items are
referenced, and working rules such as treating constraints as binding. Run it to see exactly what an
agent is told. In short:
- Read:
nexmap context -o md(or theget_contexttool) for a product briefing, then requirements, tasks, constraints, structure, defects, test cases and the campaign status as needed. - Write: set the status of requirements, tasks and defects, add comments, report defects, and create tasks, each only as far as the user’s role allows (see below).
Allowing writes
init --agents registers the MCP server with --read-only, which leaves out all tools that change
data. To let agents write through MCP, remove --read-only from the entry in .mcp.json. The CLI’s
write commands are always available to an agent that can run shell commands.
Writes are checked by the server against the token user’s role in the product, exactly as in the web UI:
| Action | Role needed |
|---|---|
| Set the status of a requirement, task or defect | developer, and only on items a product owner has activated (directly or by planning them into a sprint) |
| Comment | developer; tester on defects and test cases |
| Report a defect | tester |
| Create a task | product owner |
Each role includes the ones below it (product owner, developer, tester, viewer), and administrators may do everything.
A token carries every right of its user, and tokens cannot be narrowed yet. Give agents their own user with the lowest role that covers what they should do, rather than your personal token.
REST API
The REST API under /api/v1 is the interface for the nexmap CLI and third-party systems. The web UI uses
its own endpoints and does not go through it.
Authentication
Requests authenticate with a personal access token, created in the web UI under /user/tokens:
Authorization: Bearer <token>
A token carries the full rights of the user who created it. For automation, create a dedicated user with the role the job needs rather than using a personal token.
Responses
Successful responses wrap their payload in data; errors carry a machine-readable code and a
human-readable message:
{"data": { … }}
{"error": {"code": "…", "message": "…"}}
Endpoints
Test runs
POST /api/v1/products/{productId}/test-runs (tester role) takes
{
"source": "go-test",
"campaign_id": "",
"environment": "linux-amd64",
"revision": "4161949",
"branch": "main",
"dry_run": false,
"results": [
{"test_identifier": "pkg.TestA", "status": "passed", "duration_ms": 12},
{"test_identifier": "pkg.TestB", "status": "failed", "output": "b_test.go:9: got 1, want 2"}
]
}
and answers with recorded, campaign, environment, a summary (counts per status, per outcome, defects_created, defects_reused, total) and per-test results with outcome (updated, unchanged, unknown_test_case, not_in_campaign, environment_required, not_recorded) and defect (created, reused). Uploads are limited to 32 MiB and 50,000 results; statuses are passed, failed and skipped.