Skip to content

Deploy & admin API

The deploy admin API is the server-side contract behind nimbus deploy: one endpoint that validates uploaded app artifacts, reports a diff against the active app generation, and (unless the request is a dry run) atomically activates the new generation.

MethodPathPurpose
POST/api/admin/deployValidate, diff, and optionally activate app artifacts

Requests and responses are JSON (Content-Type: application/json).

ConditionEffect
NIMBUS_DEPLOY_TOKEN set in the server’s environment at startupEndpoint enabled; the value is the expected deploy bearer token
NIMBUS_DEPLOY_TOKEN not set at startupEvery request returns 401 (deploy admin API is disabled; set NIMBUS_DEPLOY_TOKEN before starting the server)

Two independent credentials gate the endpoint:

CredentialHeaderRequired when
Deploy tokenAuthorization: Bearer <NIMBUS_DEPLOY_TOKEN>Always
Local admin tokenX-Nimbus-Admin-Token: <token>Whenever the server runs with local security — always the case for nimbus start

Notes:

  • The deploy bearer comparison is constant-time; a wrong or prefix-matching token returns 401 (invalid deploy admin token).
  • The local admin gate accepts only the X-Nimbus-Admin-Token header on this route. Operator session cookies and Authorization-bearer admin tokens, which other admin routes accept, are not valid here — the Authorization header is reserved for the deploy token.
  • The local admin token file lives at ~/.local/share/nimbus/auth/token (Linux), ~/Library/Application Support/nimbus/auth/token (macOS), or %LOCALAPPDATA%\nimbus\auth\token.json (Windows).
  • Requests carrying a browser Origin header are restricted to loopback HTTP origins on the server’s port; others return 403.
{
"dry_run": false,
"artifacts": {
"convex": {
"functions_json": { "functions": [] },
"http_routes_json": { "routes": [] },
"schema_json": { "tables": {} },
"auth_config_json": {},
"bundle_mjs": "export const value = 1;\n",
"bundle_sha256": "<64-character lowercase sha256 hex>"
},
"cloud_functions": {
"artifact_json": { },
"targets_json": { },
"bundle_mjs": "export const value = 1;\n",
"bundle_sha256": "<64-character lowercase sha256 hex>"
}
}
}
FieldTypeRequiredMeaning
dry_runbooleanno (default false)Validate and diff without activating
artifacts.convexobjectat least one familyConvex-compatible artifact family
artifacts.cloud_functionsobjectat least one familyCloud Functions artifact family
FieldTypeRequired
functions_jsonJSON valueyes
http_routes_jsonJSON valueno
schema_jsonJSON valueno
auth_config_jsonJSON valueno
bundle_mjsstringpaired
bundle_sha256stringpaired

bundle_mjs and bundle_sha256 are a pair: supplying one without the other is a 400.

FieldTypeRequired
artifact_jsonJSON valueyes
targets_jsonJSON valueyes
bundle_mjsstringyes
bundle_sha256stringyes
FactDetail
StagingArtifacts are written to a private (0700) randomized temporary directory and loaded through the same registry path as a server started directly from an app directory
ValidationManifest readability, optional HTTP routes, schema and index definitions, auth config readability, and runtime-bundle SHA-256 integrity are all checked during staging
ActivationNon-dry-run deploys activate only after staging and validation succeed; the swap is atomic — in-flight requests keep the generation they captured, new requests observe the new one
FailureIf staging or validation fails, the previous generation remains active
Generation counterProcess-local: 0 on a server started without an app, 1 when started with one, incremented per activation; it resets on restart
RollbackThere is no rollback endpoint and no retained generation history
Partial familiesA deploy that includes only one artifact family keeps the other family’s active registry unchanged
Diff scopeThe diff object is computed from Convex artifacts only; Cloud Functions changes do not appear in it
{
"dry_run": false,
"activated": true,
"generation": 2,
"previous_generation": 1,
"diff": {
"functions": {
"added": [{ "name": "messages:list", "kind": "query" }],
"changed": [],
"removed": []
},
"http_routes": {
"added": [{ "key": "GET /healthz" }],
"changed": [],
"removed": []
},
"schema_changed": true,
"indexes_changed": true,
"runtime_bundle_changed": true
}
}
FieldTypeMeaning
dry_runbooleanEchoes the request
activatedbooleantrue exactly when dry_run is false
generationintegerNew generation after activation; the current (unchanged) generation on a dry run
previous_generationintegerGeneration active before the request
diff.functionsobjectadded / changed / removed arrays of {name, kind}
diff.http_routesobjectadded / changed / removed arrays of {key}
diff.schema_changedbooleanSchema fingerprint differs
diff.indexes_changedbooleanIndex fingerprint differs
diff.runtime_bundle_changedbooleanRuntime bundle fingerprint differs

Function and route changes are detected by fingerprint comparison against the previously active Convex registry.

All errors use the standard error envelope.

StatusCodeCondition
401auth.unauthorizedNIMBUS_DEPLOY_TOKEN was not set at server startup
401auth.unauthorizedMissing or non-Bearer Authorization header, or wrong deploy token
401auth.unauthorizedMissing or invalid X-Nimbus-Admin-Token on a server with local security
403auth.forbiddenNon-loopback browser Origin header
400op.invalid_inputNeither artifacts.convex nor artifacts.cloud_functions present
400op.invalid_inputbundle_mjs supplied without bundle_sha256, or vice versa
400op.invalid_inputArtifact staging or manifest/schema/bundle validation failed
500service.internalUnexpected server-side failure while staging artifacts or loading the registry