API overview
Everything the dashboard does for you, your code can do over plain REST at https://api.naijacloud.com/v1. One nc_live_ key, one set of conventions, and a reference generated from the API itself.
The basics
| Property | Value |
|---|---|
| Base URL | https://api.naijacloud.com/v1 |
| Format | JSON in, JSON out. camelCase keys, ISO-8601 UTC times, string ids |
| Auth | Authorization: Bearer nc_live_… |
| Contract | /v1/openapi.json (OpenAPI 3) |
| Reference | API reference, generated from that contract |
The same base URL serves the platform (projects, services, deploys, domains,
databases, cron jobs) and Naijamail (/v1/emails). One key works for both.
What it can reach depends on the scopes you gave it.
Argus, the monitoring product, is the exception. It has its own hosts and its own keys, because the code that sends errors and metrics shouldn't hold a key that can change your infrastructure. See the Argus API.
Your first call
Create a key under Settings → API keys, then list the workspaces it can see. Almost every other call starts from a workspace id.
curl https://api.naijacloud.com/v1/workspaces \
-H "Authorization: Bearer $NC_API_KEY"{
"data": [
{ "id": "3f2b…", "name": "Acme", "role": "OWNER", "defaultRegion": "af-west" }
],
"nextCursor": null
}Keep the key in an environment variable. NC_API_KEY is the convention. Set
it as a service variable rather than
committing it, and it is there on every deploy.
Authenticating
Send the key in the Authorization header as a bearer token. Nothing else is
accepted. A key in a cookie or a query string is refused: a browser attaches
cookies to requests on its own, and that is what CSRF abuses.
A key belongs to one workspace. It is refused everywhere else, even if the person who created it belongs to other workspaces.
Scopes
A key has an access level and one or more resource areas. You pick them when you create it.
| Scope | Allows |
|---|---|
READ | GET requests |
WRITE | Everything else: POST, PUT, PATCH, DELETE |
PLATFORM_API | Workspaces, projects, environments, services, variables, secret files, custom domains, cron jobs |
DEPLOYS | Deploys: trigger, list, read logs, cancel, roll back |
DATABASES | Backups and restores |
EMAIL_SEND | Sending and reading email through Naijamail |
Every route in the reference lists the two
scopes it needs. Anything not covered by a narrower area needs PLATFORM_API.
That includes routes added after your key was issued, so a narrow key does not
widen as the API grows.
The split catches people out in one place. Creating a service needs
PLATFORM_API. Deploying it needs DEPLOYS. A CI key that only ships
existing services wants WRITE and DEPLOYS. A provisioning script that also
creates them wants PLATFORM_API as well.
Ticking any resource area also grants WRITE, because every area describes
something a machine does. A read-only key is READ with no areas ticked.
What a key cannot do
- Issue or revoke credentials. Creating and revoking API keys needs a signed-in person. A leaked key cannot mint a broader replacement.
- Change account security. Passwords, two-factor, sign-in methods, members and roles are not on the API at all.
- Exceed its owner. A key is always a subset of the person who created it. If they lose access to the workspace, so does the key.
- Reach the admin console. No API key can.
Pagination
Lists return one page and a cursor:
{ "data": [ … ], "nextCursor": "eyJwIjoyLCJsIjoyNX0" }limitsets the page size: 1 to 100, default 25.- Pass
nextCursorback ascursorto get the next page. Do not decode it. nextCursorisnullon the last page.
curl "https://api.naijacloud.com/v1/services/$SERVICE_ID/deploys?limit=50" \
-H "Authorization: Bearer $NC_API_KEY"Errors
The API uses real HTTP status codes. Every failure has the same body:
{
"error": {
"code": "FORBIDDEN",
"message": "This API key does not have the DEPLOYS scope. Create a key with DEPLOYS in the dashboard, under Settings → API keys.",
"requestId": "4f1c…"
}
}Branch on code, never on message. The message is written for a person and
can change.
| Status | code | Means |
|---|---|---|
400 | BAD_REQUEST | The body or a parameter failed validation. The message lists every problem |
401 | UNAUTHENTICATED | Missing, malformed, revoked or expired key |
402 | PAYMENT_REQUIRED | A paid size and not enough balance |
403 | FORBIDDEN | The key lacks a scope, or your role does not allow it |
404 | NOT_FOUND | It does not exist, or it is not in this key's workspace |
409 | CONFLICT | It clashes with something that exists, or the same idempotent request is still running |
422 | IDEMPOTENCY_KEY_REUSED | An Idempotency-Key you already used, with a different body |
429 | RATE_LIMITED | Too many requests. Wait for Retry-After |
5xx | INTERNAL_SERVER_ERROR | Our fault. Retry with backoff, and quote the requestId if it persists |
A missing scope says which one it needs. You are holding the key, so nothing leaks by telling you.
Idempotency
POST routes that create something or start work accept an Idempotency-Key
header: projects, environments, services, deploys, rollbacks, backups, restores,
custom domains and cron runs. Send a unique value, such as a UUID, and retry
safely after a timeout.
- A retry with the same key and the same body returns the first response, with
Idempotent-Replayed: true. Nothing runs twice. - The same key with a different body is
422 IDEMPOTENCY_KEY_REUSED. - A retry while the first request is still running is
409 CONFLICT. Wait and retry. - Keys are kept for 24 hours. Only successes are kept, so a failed request can be retried with the same key.
curl -X POST "https://api.naijacloud.com/v1/services/$SERVICE_ID/deploys" \
-H "Authorization: Bearer $NC_API_KEY" \
-H "Idempotency-Key: release-2026-10-19-1"Rate limits
Each caller gets 300 requests per minute. Every response says where you stand:
| Header | Means |
|---|---|
X-RateLimit-Limit | Requests allowed in the window |
X-RateLimit-Remaining | Requests left in this window |
X-RateLimit-Reset | Seconds until the window resets |
Over the limit is 429 RATE_LIMITED with Retry-After. A 429 is worth
retrying after that many seconds. A 403 is not. Sending email has its own,
higher limit, described in Sending email.
Request ids
Every response carries an x-request-id header, and every error repeats it as
requestId. Log it. It is how support finds your exact request.
You can send your own to join our logs to yours: at most 64 characters of
A-Za-z0-9_.:-. It is echoed back. Anything else is replaced with one of ours,
so a malformed header never fails a request.
Versioning
/v1 is a contract. Nothing in it is removed, renamed or retyped. New optional
fields and new response fields can appear, so ignore fields you do not know. A
breaking change would be /v2, announced with a date, while /v1 keeps
working.
Related: Custom domains and API reference.