Skip to main content

Projects API

HTTP API reference for creating, listing, updating, and deleting projects -- the second level of CodeSpar's account -> project tenancy model.

1 min read
View MarkdownEdit on GitHub

Projects API

The Projects API manages the second level of CodeSpar's tenancy model: Account -> Project. See the Projects concept for the full model.

Base URL: https://api.codespar.dev

All endpoints require authentication via Bearer token. See Authentication.

Project object

FieldTypeDescription
idstringProject ID in the form prj_<16chars>
org_idstringParent account ID
namestringDisplay name (free-form)
slugstringURL-safe identifier, unique per account
is_defaultbooleantrue for the account's default project (exactly one per account)
created_atstringISO 8601 timestamp

Slug rules

  • Lowercase alphanumeric characters plus _ and -
  • Max length: 64 characters
  • default is reserved (only the auto-created default project uses it)
  • Must be unique within the account

Violations return slug_invalid, slug_reserved, or slug_conflict (see Errors).


GET /v1/projects

Lists all projects in the authenticated account.

Auth required: Yes

Query parameters

ParameterTypeDefaultDescription
is_defaultboolean--Filter to only the default project (true) or non-default projects (false)
limitnumber50Results per page (max 100)
offsetnumber0Pagination offset

curl example

curl https://api.codespar.dev/v1/projects \
  -H "Authorization: Bearer csk_live_abc123..."

Response -- 200 OK

{
  "data": [
    {
      "id": "prj_a1b2c3d4e5f6g7h8",
      "org_id": "org_xyz789",
      "name": "Default",
      "slug": "default",
      "is_default": true,
      "created_at": "2026-04-01T10:00:00Z"
    },
    {
      "id": "prj_i9j0k1l2m3n4o5p6",
      "org_id": "org_xyz789",
      "name": "Staging",
      "slug": "staging",
      "is_default": false,
      "created_at": "2026-04-15T09:12:00Z"
    }
  ],
  "total": 2,
  "limit": 50,
  "offset": 0
}

GET /v1/projects/:id

Retrieves a single project by ID.

Auth required: Yes

curl example

curl https://api.codespar.dev/v1/projects/prj_a1b2c3d4e5f6g7h8 \
  -H "Authorization: Bearer csk_live_abc123..."

Response -- 200 OK

{
  "id": "prj_a1b2c3d4e5f6g7h8",
  "org_id": "org_xyz789",
  "name": "Default",
  "slug": "default",
  "is_default": true,
  "created_at": "2026-04-01T10:00:00Z"
}

POST /v1/projects

Creates a new project in the authenticated account.

Auth required: Yes

Request body

FieldTypeRequiredDescription
namestringYesDisplay name
slugstringYesURL-safe identifier (see slug rules)

New projects are always created with is_default: false. To promote a project to default, use PATCH /v1/projects/:id with {"is_default": true}.

curl example

curl -X POST https://api.codespar.dev/v1/projects \
  -H "Authorization: Bearer csk_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production",
    "slug": "prod"
  }'

Response -- 201 Created

{
  "id": "prj_q7r8s9t0u1v2w3x4",
  "org_id": "org_xyz789",
  "name": "Production",
  "slug": "prod",
  "is_default": false,
  "created_at": "2026-04-20T14:22:00Z"
}

PATCH /v1/projects/:id

Updates a project's name, slug, or default status.

Auth required: Yes

Request body

FieldTypeRequiredDescription
namestringNoNew display name
slugstringNoNew slug (subject to slug rules)
is_defaultbooleanNoSet to true to promote this project to default. Atomic: the previous default is demoted in the same transaction.

is_default can only be set to true. You cannot un-set it directly -- to change the default, promote a different project instead. Sending {"is_default": false} on the current default project is a no-op or error (exact behavior: TODO -- confirm).

curl example -- promote to default

curl -X PATCH https://api.codespar.dev/v1/projects/prj_q7r8s9t0u1v2w3x4 \
  -H "Authorization: Bearer csk_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{"is_default": true}'

Response -- 200 OK

{
  "id": "prj_q7r8s9t0u1v2w3x4",
  "org_id": "org_xyz789",
  "name": "Production",
  "slug": "prod",
  "is_default": true,
  "created_at": "2026-04-20T14:22:00Z"
}

DELETE /v1/projects/:id

Deletes a project and all of its scoped resources (API keys, triggers, session history, connection records).

Auth required: Yes

Deletion cascades. There is no undo. Export audit logs first if you need to retain them.

curl example

curl -X DELETE https://api.codespar.dev/v1/projects/prj_q7r8s9t0u1v2w3x4 \
  -H "Authorization: Bearer csk_live_abc123..."

Response -- 200 OK

{
  "id": "prj_q7r8s9t0u1v2w3x4",
  "deleted": true
}

Deletion fails with cannot_delete_default or cannot_delete_last_project (see below).


Project members (RBAC overrides)

Every user with account membership inherits a base role (owner, admin, member) that applies across every project in that account. The four endpoints below let an account admin narrow or widen that role for a specific project — useful when a contractor should see exactly one project, or a member needs admin rights on staging without becoming a full account admin.

A user absent from project_members inherits their account-level role unchanged. Explicit rows are overrides, not the source of truth.

Not reachable with an API key — use the dashboard

The four member endpoints live in the API's service-auth subtree (requireServiceAuth), alongside api-keys, usage, billing and team. A csk_ Bearer key is not read at all there, so a Bearer call answers 401 {"error":"unauthorized"} — not a 403 and not a 404.

There is no key we can issue that changes this: the credential for that subtree is CodeSpar's own platform secret, and possession of it is the trust boundary for every account, so it never leaves our infrastructure. The dashboard is the supported surface. The request and response shapes below describe what it sends and renders.

GET /v1/projects/:id/members

Lists every user with effective access to the project, including account-level inherited members. The source field tells you whether the role came from a project override or from the account membership.

GET /v1/projects/prj_a1b2c3d4e5f6g7h8/members

Response — 200 OK

{
  "members": [
    {
      "user_id": "user_owner123",
      "role": "owner",
      "source": "org",
      "email": "founder@codespar.dev",
      "display_name": "Founder",
      "avatar_url": null,
      "added_at": "2026-04-15T10:00:00Z"
    },
    {
      "user_id": "user_contractor456",
      "role": "admin",
      "source": "project",
      "email": "ana@contractor.com",
      "display_name": "Ana",
      "avatar_url": null,
      "added_at": "2026-04-22T18:30:00Z"
    }
  ]
}

Sorted: owner first, then admin, then member — oldest first within each tier.

POST /v1/projects/:id/members

Adds or upserts a project-level override. Requires account-level admin or owner.

Request body

FieldTypeRequiredNotes
user_idstringYesMust already be a member of the account.
roleadmin | memberYesCannot override an account owner — they always retain full access.
POST /v1/projects/prj_.../members
Content-Type: application/json

{"user_id": "user_contractor456", "role": "admin"}

PATCH /v1/projects/:id/members/:user_id

Updates an existing override's role. Same gate (admin+ at account level).

PATCH /v1/projects/prj_.../members/user_contractor456
Content-Type: application/json

{"role": "member"}

DELETE /v1/projects/:id/members/:user_id

Removes the override. The user falls back to their account-level role for this project.

DELETE /v1/projects/prj_.../members/user_contractor456
StatusError codeWhen
401unauthorizedCalled with a Bearer key, or with no/invalid service key. Bare-string shape
403insufficient_rolePOST/PATCH/DELETE by a non-admin account member. Flat shape: {"error":"insufficient_role","required":"admin"}
404not_foundThe project id does not exist in this account, or a PATCH named a user with no override row
404user_not_in_orguser_id is not a member of this account
409cannot_override_ownerThe target user is the account owner; an owner's role cannot be narrowed per project

DELETE is idempotent: removing an override that does not exist answers 204, not 404 — the end state is the same, the user inherits their account role.


Errors

All endpoints follow the standard error format:

{
  "error": "error_code",
  "message": "Human-readable error description.",
  "status": 400
}

Project-specific error codes

StatusError codeWhen
400slug_invalidSlug contains disallowed characters, exceeds 64 chars, or is empty
400slug_reservedSlug is default (reserved for the auto-created default project)
409slug_conflictAnother project in the account already uses this slug
409cannot_delete_defaultDELETE on the default project. Promote another project first.
409cannot_delete_last_projectDELETE on the only remaining project. Every account must have at least one project.

Standard error codes

StatusError codeWhen
401unauthorizedInvalid or missing API key
403forbiddenAPI key lacks permission (e.g. a project-scoped key trying to manage a different project)
404not_foundProject ID does not exist or does not belong to the authenticated account
429rate_limitedToo many requests
500internal_errorServer error. Retry with exponential backoff.

Next steps

Projects API | CodeSpar