For machines
There are three separate things here, and they are for three different moments. Pick the one that matches what your agent is doing.
MCPHTTP APIdeployment.mdConnect
Each box exposes its own endpoint at /mcp on the panel domain. Tools are scoped to the token’s account, so an agent can never do more than the person who issued it.
.mcp.json{
"mcpServers": {
"nimbus": {
"url": "https://panel.example.com/mcp",
"headers": {
"Authorization": "Bearer $NIMBUS_TOKEN"
}
}
}
}
HTTPThe API mirrors the panel’s vocabulary one-to-one, so the guide doubles as its documentation.
$ curl -H "Authorization: Bearer $NIMBUS_TOKEN" \
https://panel.example.com/api/sites
unlock_user token can unlock an address and nothing else. Treat one like the password it replaces.
/system, a read-only page stating what this particular box uses: the panel's own version number under Platform, which is what to quote in any problem report; the MySQL and Redis hosts — separately for catalog sites and for stacks, because they differ — where site and stack folders live, and the certificate authority. Any signed-in account may read it, whatever its role, provided the request comes from an unlocked address. It carries no credentials; those come from the site's own Databases page.
404 means deleted or somebody else's and the response cannot tell you which. Check the account's role before reporting that a site is gone. A 403 is a separate matter and still means the IP allowlist, never the role — the two are not interchangeable, and a runbook that starts on an administrator-only page has to name the role it needs. Whether a site is locked is now readable from the sites list itself: its Visibility column carries a Locked chip for a whole-site lock and a folder count when only some paths are locked, alongside published or unpublished, which is a separate question — a site can be both published and locked, and for a staging site usually is.
laravel, web-base and web-spa as the set of site types. They are the built-in ones; an administrator can upload custom images — a Dockerfile and a nimbus-image.json manifest in a zip — through the Site Images page, formerly Catalog, and they appear in the same picker. Read the list off the box rather than assuming it. A custom image also carries a version, and a site pins the exact version it was created with: images are immutable, there is no edit of any field, and changing one means uploading a new version and moving sites onto it with the ordinary change-image action. An image that has not finished building, or that failed, is listed but cannot be chosen. The settings a site accepts come from the image's manifest, so they vary per image and an undeclared key is rejected rather than passed through. The manifest also decides whether a site gets an .env and a database at all: nimbus-image.json takes displayName, description, settings and envFile, and only when envFile is true does a site on that image get the Environment and Databases tabs. It is optional and defaults to false, so a manifest written without it produces an application image with no way to set credentials — and nothing reports that as an error. Built-in images are not special here; laravel declares it like any other.
2001:DB8::1 and 2001:db8::1 are the same address. Removing a token's row does not revoke the token, and the token reopens an address on its next call; only revoking it, by the account that holds it, is permanent. Describing the page as one table describes the old one.
.env, its database dump and its TLS private keys, so never route one into a shared location or a log.
@ otherwise reads as a host separator: sftp -P 2223 'you@example.com'@host. There is no password authentication and cannot be one. A session shows one directory per site the account holds, computed at connect time, so ownership changes need no re-authorisation; the panel's own site is never among them, and a mounted folder is not either, because a mount lives in the container while SFTP sees the folder on the server. Uploaded files are owned correctly with no chown. The security shape matters for any advice you give: that port is not behind the IP allowlist, so locking a site does not close its files — removing the key from the account is what revokes access, and it applies to the next connection rather than to an open session. An administrator can list and remove keys on any account but cannot add one. Describe the session confinement as the SFTP service restricting it to those directories, not as a filesystem permission boundary.
NIMBUS_NOTIFICATION_TOKEN in the server's .env; without it the option is shown disabled with the reason and typing a password still works. The invitation page is outside the IP allowlist by design, since the recipient could not be on it yet.
403 from the box means the calling address is not on the allowlist, and a token can fix that for itself: PUT /api/v1/allowlist/address on the panel domain, with the bearer token and a JSON body carrying a required label, unlocks the address the call came from — there is no address field, and one in the body, the query string or a forwarding header is ignored. That bounded blast radius is why every role can hold a token, unlock-only included, and why one is safe on a timer. The call is idempotent, one token holds one address at a time, and revoking a token also closes the address it opened. Read the reply, not just the status: a 200 carrying "enforced": false means saved but not yet in force, and the fix is to repeat the same call. 401 covers wrong and revoked alike; 429 is the limit of ten calls a minute per address. Tokens are issued at /account/tokens, which is outside the admin area on purpose, and shown exactly once.
2200–2299 range forwarded straight to a service's container port, added by an administrator on the Ports page, for traffic that is not HTTP — git clone ssh://… against a Gitea stack is the case it exists for. Two consequences a runbook must not get wrong. There is no TLS, so nothing is encrypted unless the service does it itself. And the IP allowlist is an HTTP-level control that cannot cover a raw port at all, so locking a stack does not close its published ports — the service's own authentication is the only thing in the way. Do not advise publishing a port as a way of exposing a website; that is a Route. Catalog sites have none, and a site-owner token cannot see or change any of this. Also keep the two lists apart: a stack's allocated ports are the ${NIMBUS_PORT_*} numbers the panel picks out of the compose file and only the edge can reach, while published ports are picked by a person and reachable by anyone.
.env, a new background program. Recreate builds a new container from the image, and is the only one that applies a changed site type, a setting that is read as the container is created such as a memory limit, or a mounted folder. Nothing in the site's folder, and no domain, certificate, database or deployment link, is lost either way. A stack has neither; it is deployed.
0 B / 0 B; memory and swap are live while the CPU figure beside them is an average over up to a minute, and there is no per-site swap figure at all.
Drop-in
When a coding agent is asked to add deployment to an app hosted here, it needs facts about this platform that are not in the repository it is looking at. deployment.md is those facts, written for a machine to act on.
Save it at the root of the site’s repository, keeping the filename.
$ curl -O https://panel.nimbus-online.net/deployment.md
laravel, web-base and web-spa are the built-in entries, not the whole list: the image list is per box and an administrator can upload to it, so read the value off the site rather than assuming one of three. Guessing the runner label produces a workflow that never runs. For a custom stack there is no runner and no domain: tell the agent it is writing a compose file for a stack, and it will follow the contract in deployment.md instead.
http:// links. Have the agent configure trusted proxies during scaffolding rather than after the first bug report: on Nimbus, trust all proxies — at: '*' in Laravel — with the four X-Forwarded-* headers. The proxy's own address is a container address and is not knowable from inside the app. Tell it not to carry that '*' to any other host, where a visitor could forge the header; the reasoning is in chapter 07.
Reading, not acting
For an agent that just needs to understand the platform, every page here is published as markdown too, and indexed the standard way.
/llms.txt/llms-full.txt.md.md to any page URL — /help/sites.html.md is the markdown of /help/sites.html. A URL with no filename takes index.html.md, so /help/ becomes /help/index.html.md. That is the llmstxt.org convention: appended, not substituted.