Skip to main content

Projects

Projects are the second level of CodeSpar's 2-level tenancy model -- an isolation boundary inside an account for API keys, connections, triggers, sessions, and events.

2 min read · updated

Projects

CodeSpar uses a 2-level tenancy model: Account -> Project. Every resource -- sessions, API keys, triggers, connections, events -- belongs to exactly one project, and every project belongs to exactly one account.

Projects are the unit of environment isolation. A typical setup has one account per company and multiple projects inside it (for example dev, staging, prod), each with its own API keys, connected accounts, and audit log.

Why projects

Before projects, every resource was scoped to the account. That forced teams to either share credentials across environments or spin up separate accounts just to keep dev from touching prod. Projects fix that without splitting billing, team membership, or settings:

  • Environment isolation -- dev, staging, and prod live side by side under one account with independent API keys and OAuth connections.
  • Per-project audit -- tool calls, sessions, and events are filtered by project in the dashboard.
  • Per-project limits -- quota and rate-limit counters can be scoped per project (see Billing).
  • Same team, same billing -- members, plan, and invoice live at the account level and apply across all projects.

The default project

Every account has a default project, auto-created at signup. If you never touch projects, everything you create lands there and the HTTP API works exactly as before -- no header required.

PropertyValue
Auto-createdOn account signup
is_defaulttrue
Slugdefault (reserved)
DeletableNo -- protected by cannot_delete_default

Find your default project's ID in the dashboard under Dashboard -> Projects, or via the API:

curl "https://api.codespar.dev/v1/projects?is_default=true" \
  -H "Authorization: Bearer csk_live_..."

Project IDs and slugs

FieldFormatNotes
idprj_<16chars>Stable identifier. Used in headers, URLs, webhooks.
sluglowercase alphanumeric + _/-, max 64 charsUnique per account. default is reserved. Used in dashboard URLs.
namefree-form stringDisplay name only.
environmentlive or testSet on creation. Immutable. Determines which provider URLs the project's connections use (sandbox vs production).

Environments — live vs test

Every project has an environment field, set at creation and immutable afterward. This is independent of the API key prefix (csk_live_ vs csk_test_), but the two must match — a csk_test_ key cannot operate on a live project, and vice-versa.

FieldWhat it controls
project.environmentWhich provider URL the request lands on. A connection in a test project hits sandbox.melhorenvio.com.br; the same provider in a live project hits melhorenvio.com.br. Same OAuth token storage shape, different upstream host.
API key prefixWhat environment the key is allowed to access. csk_test_* only works against test projects; csk_live_* only against live. Mismatch returns 401 unauthorized.
Dashboard env toggleA pill in the dashboard header (Live / Test) filters which projects show up in the Project Switcher and which environment the "Create Project" modal defaults to. Stored in the codespar_env cookie. Cosmetic — does not change auth.

Why environment is immutable: the field is enforced via a Postgres trigger (projects_environment_immutable). A project that started in test cannot be promoted to live — the connections, vault entries, and webhook history were authored against sandbox URLs and would be wrong against production. The intended path is: create a separate live project, run OAuth fresh against the production provider hosts, and switch your application config to point at the new project.

Matched-environment mint default

The dashboard's mint modal at Dashboard → API Keys defaults each new key's prefix to match the active project's environment — a key minted against a test project is always csk_test_*; a key minted against a live project is always csk_live_*. New accounts get a test-environment project plus a csk_test_* key auto-created at signup, so the common first-day path is "click the auto-minted key, start building" — no environment-mismatch step.

For the cross-project case (minting a csk_live_* key against a live project from a session whose default project is test, or vice versa), the explicit environment toggle on the mint modal is still available. The default just stops being wrong for the common case — the pre-default-matched ticket where the operator minted a csk_live_* key against a test default project and saw 401 unauthorized on every request no longer fires.

Passing a project on requests

All /v1 endpoints accept the x-codespar-project header:

curl -X POST https://api.codespar.dev/v1/sessions \
  -H "Authorization: Bearer csk_live_..." \
  -H "x-codespar-project: prj_a1b2c3d4e5f6g7h8" \
  -H "Content-Type: application/json" \
  -d '{"servers": ["stripe"]}'

Resolution order:

  1. If the API key is project-scoped (pinned to one project), the key's project always wins and x-codespar-project is ignored.
  2. Otherwise, if x-codespar-project is present, that project is used.
  3. Otherwise, the request falls back to the account's default project.

The request will fail with 404 not_found if the project ID does not belong to the authenticated account. Projects never cross account boundaries.

Project-scoped API keys

An API key can optionally be pinned to a single project via its project_id column. When a request authenticates with a project-scoped key:

  • The key's project is resolved automatically -- you do not need to send x-codespar-project.
  • If the key holder does send x-codespar-project and it does not match the key's project, the header is ignored (the key wins).

This is the recommended pattern for CI/CD, production workloads, and anything else that should be locked to one environment. Create project-scoped keys from Dashboard -> Projects -> [project] -> API Keys.

Unpinned keys (no project_id) behave like account-level keys: they can operate on any project via x-codespar-project, and fall back to the default project when the header is omitted.

Dashboard routing

Per-project surfaces in the dashboard live under /dashboard/projects/[projectId]/...:

SurfaceRoute
API Keys/dashboard/projects/[projectId]/api-keys
Triggers/dashboard/projects/[projectId]/triggers
Sessions/dashboard/projects/[projectId]/sessions
Sandbox/dashboard/projects/[projectId]/sandbox
Servers/dashboard/projects/[projectId]/servers

A Project Switcher in the sidebar swaps the active project across all of these. Project CRUD lives at /dashboard/projects.

Common workflows

Promote a non-default project to default

is_default is promoted atomically via PATCH /v1/projects/:id with {"is_default": true}. The previous default is demoted in the same transaction. You cannot un-set is_default directly -- to change the default, promote a different project instead.

Delete a project

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

Deletion fails with:

  • cannot_delete_default -- promote a different project to default first.
  • cannot_delete_last_project -- every account must have at least one project.

Deleting a project deletes its API keys, triggers, sessions history, and connection records. There is no undo.

Grant per-project access without making someone an account admin

By default, every account member inherits the same role across all projects. To narrow or widen that — give a contractor admin access to one staging project without exposing production, or give a member admin rights on a specific project — use project-level role overrides:

curl -X POST https://api.codespar.dev/v1/projects/prj_.../members \
  -H "Authorization: Bearer csk_live_..." \
  -d '{"user_id": "user_contractor456", "role": "admin"}'

Absence of an override = inherits the account role. Endpoints documented in Projects API → Project members. Dashboard UI is on the roadmap; for now, this is API-only.

Next steps

Edit on GitHub

Last updated on