API
/
Projects, services and deploys
Projects, services and deploys
The provisioning path in four calls — project, environment, service, deploy. Everything here is GraphQL at `https://api.naijacloud.com/graphql`; the first three need `PLATFORM_API` and the last needs `DEPLOYS`.
6 min read
The shape
A project holds environments, and an environment holds services. A service is the unit that runs: a web app, a static site, a cron job, or a managed database. See core concepts for how they relate.
Every call below takes a teamId. Get it once from getMyTeams and keep it.
1. Create a project
createProject takes plain arguments rather than an input object.
| Argument | Type | Required | Notes |
|---|---|---|---|
teamId | ID! | yes | The workspace that will own it |
name | String! | yes | Slug-shaped, unique in the workspace |
displayName | String | no | What the dashboard shows |
description | String | no |
Create a project
curl https://api.naijacloud.com/graphql \
-H "Authorization: Bearer $NC_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "mutation($teamId: ID!, $name: String!) { createProject(teamId: $teamId, name: $name) { id name environments { id name } } }",
"variables": { "teamId": "3f2b…", "name": "storefront" }
}'Response
{
"data": {
"createProject": {
"id": "9c11…",
"name": "storefront",
"environments": [{ "id": "4ab0…", "name": "prod" }]
}
}
}A new project arrives with an environment already in it. Read environments
off the response rather than creating one — you need its id for the next
call, and a second prod will be refused as a duplicate.
2. Add an environment (optional)
Only when you want more than the one you were given — a staging beside prod.
createEnvironment
mutation($projectId: ID!, $name: String!) {
createEnvironment(projectId: $projectId, name: $name) {
id
name
}
}3. Create a service
createService takes a single CreateServiceInput. It covers every service
type, so most fields are optional and which ones matter depends on type and
sourceType.
Always required:
| Field | Type | Notes |
|---|---|---|
environmentId | ID! | From step 1 or 2 |
name | String! | Display name; the URL slug is derived from it |
type | ServiceType! | WEB, STATIC, CRON, POSTGRES, MYSQL, MARIADB, MONGODB, REDIS, VALKEY |
For anything that runs your code, pick where it comes from:
sourceType | Then set |
|---|---|
GITHUB_APP | repoFullName, branch, installationId |
PUBLIC_GIT | gitUrl, branch |
DOCKER_IMAGE | image, optionally imageCommand |
And the common tuning fields:
| Field | Type | Notes |
|---|---|---|
region | String | af-west (Port Harcourt) or eu-west (UK, France). Defaults to the workspace's default region |
tier | ServiceTier | FREE, STARTER, STANDARD, PRO. Sizes and prices are in the pricing catalogue |
port | Int | The port your app listens on. Set it — a wrong one is a 502 nothing else will explain |
buildCommand, startCommand | String | Override what the builder detected |
rootDir, monorepoStrategy | For a service inside a monorepo | |
envVars | [EnvVarInput!] | { key, value, secret?, scope? }, seeded at creation |
schedule | String | Cron expression. CRON services only |
staticOutputDir, staticSpa | STATIC services only |
A web service from a public repo
curl https://api.naijacloud.com/graphql \
-H "Authorization: Bearer $NC_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "mutation($input: CreateServiceInput!) { createService(CreateServiceInput: $input) { id name type status url } }",
"variables": {
"input": {
"environmentId": "4ab0…",
"name": "Storefront API",
"type": "WEB",
"sourceType": "PUBLIC_GIT",
"gitUrl": "https://github.com/acme/storefront.git",
"branch": "main",
"region": "af-west",
"tier": "STARTER",
"port": 3000,
"envVars": [{ "key": "NODE_ENV", "value": "production" }]
}
}
}'A managed database is the same call with a data type and no source:
variables for a Postgres instance
{
"input": {
"environmentId": "4ab0…",
"name": "Storefront DB",
"type": "POSTGRES",
"region": "af-west",
"tier": "STARTER"
}
}Creating a service starts its first build. status walks
PROVISIONING → ACTIVE, and url fills in once it is routable.
4. Deploy
triggerDeploy
mutation($serviceId: ID!) {
triggerDeploy(serviceId: $serviceId) {
id
status
commitSha
}
}This one needs the DEPLOYS scope, not PLATFORM_API. Creating a service
and deploying it are different areas on purpose: a CI key that only ships
existing services should not also be able to create and delete them. A key
that does both needs both ticked.
A deployment moves through QUEUED → BUILDING → DEPLOYING → RUNNING, or
FAILED. A poller will also see CANCELLED and SUPERSEDED — the second means
a newer deploy overtook this one, which is a normal outcome of pushing twice,
not a failure. Poll it, or read the log:
Watching a deploy
query($serviceId: ID!) {
getDeployments(serviceId: $serviceId, OffsetPaginationArgs: { limit: 1 }) {
items { id status createdAt finishedAt }
}
}Build and runtime logs come from
getDeploymentLogs(deploymentId, OffsetPaginationArgs) — note it keys on the
deployment, not the service — and is also DEPLOYS.
Reading back
| Operation | Returns |
|---|---|
getProjects(teamId, OffsetPaginationArgs) | A page of projects |
getMyServices(OffsetPaginationArgs) | Every service across every workspace you belong to |
getService(id) | One service with its deployments, domains and connection |
Every list is paginated the same way. Pass
OffsetPaginationArgs: { page, limit, search, sortBy, sortOrder } — page is
1-based, limit defaults to 25 and caps at 100 — and read pageInfo back:
{ page, limit, totalCount, totalPages, hasNextPage, hasPreviousPage }. Nothing
returns an unbounded list, so a script that walks every service has to follow
hasNextPage.