Kontacts docs
Getting started

Projects

GET/POST /api/v1/projects — list what a key can see, or create a project with an all-projects key.

GET /api/v1/projects and POST /api/v1/projects

The project surface: see what a key can act as, or — with the right key — create a new one. Session-cookie dashboard users have a separate route for this (/api/orgs/:id/projects); this page is the key-authed one.

List projects

Authorization: Bearer <key> and projects:read. Returns every project the key's scope resolves to — one project for a key scoped to a specific list of just one, more for a key scoped to several projects or to all of them.

{
  "projects": [
    { "id": "project-uuid", "org_id": "org-uuid", "name": "Marketing", "slug": "marketing", "created_at": "2026-01-10T00:00:00.000Z" }
  ]
}

A key with no projects in scope gets { "projects": [] }, not an error.

curl https://kontacts.dev/api/v1/projects \
  -H "Authorization: Bearer gm_live_…"

Create a project

Needs projects:write AND an all-projects key

Two separate requirements, both enforced: the key needs projectswrite, and it must be scoped to all projects in the workspace (all_projects: true). A key pinned to a specific project list gets 403 here even with projects:write and even though it can authenticate fine against every other endpoint on this site — a key scoped to fixed projects has no say over a project that doesn't exist yet.

Request body

FieldRequired
nameyes
slugno

slug is derived from name (slugified) when omitted. Both are read as plain strings from the JSON body — there's nothing else to configure at creation; sending identity is set up as a best-effort side step of the create call and isn't guaranteed to exist yet by the time the response comes back.

Response

201 Created:

{
  "project": {
    "id": "project-uuid",
    "org_id": "org-uuid",
    "name": "Marketing",
    "slug": "marketing",
    "created_at": "2026-09-05T12:00:00.000Z"
  }
}

Errors

StatusBody errorCause
401missing or malformed API keyNo Authorization header, or it isn't Bearer <key>.
401invalid API keyThe key doesn't match any stored hash.
401API key has been revokedrevoked_at is set.
403this key lacks projects:writeKey doesn't hold projectswrite.
403this key cannot create projects; mint one scoped to all projects in the workspaceKey holds projects:write but is scoped to a specific project list, not all projects.
400name is requiredEmpty or missing name.
400name must contain at least one alphanumeric charactername/slug slugifies to an empty string.
403plan-specific, e.g. "The free plan includes 2 projects. Upgrade to add another."Organisation is at its max_projects ceiling for its plan.
400that slug is reservedThe slug shadows a reserved path such as api, dashboard or login.
409slug "<slug>" is already takenSlug collision. Slugs are unique across all of kontacts.dev, not just your organisation.
500(unhandled)Lookup failure or an unexpected error rethrown as a generic 500.

Error strings are exact (aside from the plan-limit message, which substitutes the org's actual limit and plan name), read from apps/web/app/api/v1/projects/route.ts at the time this page was written. The per-plan project cap is Free 2 / Indie Hacker 10 / Enterprise unlimited — apps/web/lib/plans.ts, max_projects.

curl -X POST https://kontacts.dev/api/v1/projects \
  -H "Authorization: Bearer gm_live_…" \
  -H "Content-Type: application/json" \
  -d '{"name":"Marketing"}'

Keys

Both list and create use the same keys as every other endpoint on this site, minted under Settings → API keys (/dashboard/settings/api-keys). See Authentication for project reach, the projects permission, and what an all-projects key is.

On this page