Skip to main content

Projects & environments

A project is the unit of work inside a workspace — in the current vocabulary a project is a repo, and the SDK exposes client.repos as the canonical accessor (client.projects remains as an alias). Each project owns a set of environments (dev, staging, prod, …) that scope configuration, secrets, and orun runs.

Projects and environments are soft-archived, never hard-deleted: DELETE on either resource archives it, preserving history, audit trails, and references from past runs.

Resource shape​

{
"project": {
"id": "prj_5e6f7a8b9c0d",
"orgId": "org_1f2e3d4c5b6a",
"name": "Checkout Service",
"slug": "checkout-service",
"status": "active",
"createdAt": "2026-06-01T09:12:44.120Z",
"updatedAt": "2026-06-01T09:12:44.120Z",
"archivedAt": null
}
}

Environments add projectId and lastActiveAt (see Environment liveness). Names are 1–100 characters; slugs are 2–63 lowercase alphanumerics and hyphens, and are derived from the name when omitted. Slugs are unique per scope — a duplicate returns 409 conflict.

Endpoints​

MethodPathPermissionDescription
POST/v1/organizations/{orgId}/projectsproject.createCreate a project
GET/v1/organizations/{orgId}/projectsproject.listList projects
GET/v1/organizations/{orgId}/projects/{projectId}project.readGet a project
DELETE/v1/organizations/{orgId}/projects/{projectId}project.deleteArchive a project
POST/v1/organizations/{orgId}/projects/{projectId}/restoreproject.createRestore an archived project
POST…/projects/{projectId}/environmentsenvironment.createCreate an environment
GET…/projects/{projectId}/environmentsenvironment.readList environments (?includeArchived=true to include archived)
GET…/environments/{envId}environment.readGet an environment
DELETE…/environments/{envId}environment.deleteArchive an environment

/v1/workspaces/… is an accepted alias for /v1/organizations/… on these paths.

warning

DELETE archives — it does not destroy. The response returns the resource with status updated and archivedAt set. Archiving is reversible: restore a project explicitly with POST …/restore (below); an archived environment is revived automatically if activity references it again.

Restore an archived project​

POST …/projects/{projectId}/restore brings an archived project back to active. The gating mirrors create, not archive: restoring grows the workspace's active project count, so it authorizes under project.create (there is no separate project.restore action — the capability being exercised is "add an active project to this workspace") and must clear the same limit.projects entitlement a creation would. At the plan ceiling, restore fails with 412 precondition_failed.

The restore is idempotent-safe: the update predicates on status = 'archived', so restoring an already-active project changes nothing and a concurrent double-restore cannot double-count.

What restore does — and does not — touch:

  • Repo links restore automatically as part of repo onboarding: linking a repository to an archived project restores the project as part of the link, and a failed restore fails the link — no half-linked archived project.
  • CI cannot push into an archived project — the state plane rejects writes until the project is active again.
  • Archiving does not cascade to the workspace link — deliberately. The link (the CI allow-list entry) survives the archive, which is why the project's history and identity stay reachable and the restore is clean.

Create a project​

curl -X POST "https://api.orunbase.com/v1/organizations/org_1f2e3d4c5b6a/projects" \
-H "Authorization: Bearer $ORUN_CLOUD_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: create-checkout-service-1" \
-d '{"name": "Checkout Service", "slug": "checkout-service"}'
import { OrunCloud } from "@saas/sdk";

const client = new OrunCloud({
baseUrl: "https://api.orunbase.com",
auth: { kind: "bearer", token: process.env.ORUN_CLOUD_TOKEN! },
});

const { project } = await client.repos.create(
"org_1f2e3d4c5b6a",
{ name: "Checkout Service", slug: "checkout-service" },
{ idempotencyKey: "create-checkout-service-1" },
);

Creation returns 201 with the project in the standard envelope. Every lifecycle change (project.created, environment.archived, …) also lands in the audit log under the projects category.

Create and archive environments​

curl -X POST "https://api.orunbase.com/v1/organizations/org_1f2e3d4c5b6a/projects/prj_5e6f7a8b9c0d/environments" \
-H "Authorization: Bearer $ORUN_CLOUD_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "staging"}'
const { environment } = await client.environments.create(
"org_1f2e3d4c5b6a",
"prj_5e6f7a8b9c0d",
{ name: "staging" },
);

// Later: archive it (soft delete)
await client.environments.archive("org_1f2e3d4c5b6a", "prj_5e6f7a8b9c0d", environment.id);

Plan limits​

Creation is gated by billing entitlements after the permission check and before anything is written:

EntitlementApplies toFreeProBusinessEnterprise
limit.projectsActive projects per workspace325100Unlimited
limit.environmentsActive environments per project335Unlimited

Only active resources count — archiving frees quota. When a limit is reached, creation fails with 412 precondition_failed and a details.reason explaining the gate. The gate fails closed: if the entitlement check itself cannot complete, creation is refused rather than over-provisioned.

Environment liveness​

Environments track a lastActiveAt timestamp and follow an activity-driven lifecycle:

  • Register on activity — when a run or plan references an environment that doesn't exist yet, the platform materializes it automatically (a system-actor environment.created event lands in audit). The same signal bumps lastActiveAt on existing environments and revives archived ones. System-materialized environments are not quota-blocked — you already referenced them.
  • Archive when stale — a periodic platform sweep archives active environments whose lastActiveAt predates the retention window (default 90 days), emitting environment.archived with reason: "stale". The sweep is reversible: any later activity touch revives the row.

Environment lists return active environments by default; pass includeArchived=true to see what the sweep has retired.

Connect projects to repos and runs​

  • Git repos — link a project to a GitHub repository so pushes and pull requests flow into Orunbase (repo links require project.repo_link.write). See GitHub integration.
  • orun runs — projects and environments are the tenancy scope for remote state, runs, and the catalog. See State plane overview.