Kontacts docs

Projects

GET/POST /api/v1/projects — list what a key can see, or create a project with an org-wide 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> — any key format works. A project key returns an array with just its own project. An org/scoped key returns every project its scope resolves to.

{
  "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

Org-wide keys only

Only a key with scope: "*" and an org_id can create a project. A project key, or a scoped key limited to specific projects/workspaces, gets 403 here even though it can authenticate fine against every other endpoint on this site.

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 cannot create projects; mint an org-wide (*) keyKey is a project key, or a scoped (non-*) key.
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.
409a project with slug "<slug>" already exists in this orgSlug collision within the 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 jwt_…" \
  -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 the difference between a project key and an org-wide key.

On this page