Naijacloud
Docs

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.

6 min read

The basics

PropertyValue
Base URLhttps://api.naijacloud.com/v1
FormatJSON in, JSON out. camelCase keys, ISO-8601 UTC times, string ids
AuthAuthorization: Bearer nc_live_…
Contract/v1/openapi.json (OpenAPI 3)
ReferenceAPI 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.

bash
curl https://api.naijacloud.com/v1/workspaces \ -H "Authorization: Bearer $NC_API_KEY"
Response
json
{ "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.

ScopeAllows
READGET requests
WRITEEverything else: POST, PUT, PATCH, DELETE
PLATFORM_APIWorkspaces, projects, environments, services, variables, secret files, custom domains, cron jobs
DEPLOYSDeploys: trigger, list, read logs, cancel, roll back
DATABASESBackups and restores
EMAIL_SENDSending 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:

A page
json
{ "data": [ … ], "nextCursor": "eyJwIjoyLCJsIjoyNX0" }
  • limit sets the page size: 1 to 100, default 25.
  • Pass nextCursor back as cursor to get the next page. Do not decode it.
  • nextCursor is null on the last page.
bash
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:

An error
json
{ "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.

StatuscodeMeans
400BAD_REQUESTThe body or a parameter failed validation. The message lists every problem
401UNAUTHENTICATEDMissing, malformed, revoked or expired key
402PAYMENT_REQUIREDA paid size and not enough balance
403FORBIDDENThe key lacks a scope, or your role does not allow it
404NOT_FOUNDIt does not exist, or it is not in this key's workspace
409CONFLICTIt clashes with something that exists, or the same idempotent request is still running
422IDEMPOTENCY_KEY_REUSEDAn Idempotency-Key you already used, with a different body
429RATE_LIMITEDToo many requests. Wait for Retry-After
5xxINTERNAL_SERVER_ERROROur 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.
Safe to retry
bash
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:

HeaderMeans
X-RateLimit-LimitRequests allowed in the window
X-RateLimit-RemainingRequests left in this window
X-RateLimit-ResetSeconds 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.