> ## Documentation Index
> Fetch the complete documentation index at: https://tbd-6fc993ce-fix-project-header-id-only.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Projects

> Organize resources and isolate access within your Kernel organization

A **Project** is a named container for Kernel resources inside an organization. Use projects to separate environments (like `production` and `staging`), split resources between teams, or isolate customer workloads — each project has its own browsers, profiles, credentials, proxies, extensions, deployments, and pools.

## Why Projects?

* **Isolate environments** — keep `production` resources apart from `staging` or experiments.
* **Scope access** — issue API keys that can only see resources in one project.
* **Concurrency limits** — set an org-wide default cap for every project, or override it per project, so one team or environment can't exhaust your org quota.

## The Default Project

Every organization has at least one project. Resources that existed before projects were introduced have been moved into a project named **Default**, so your existing browsers, apps, profiles, and other resources continue to work without any changes on your end.

Your organization must always have **at least one active project**. The API returns `409 Conflict` if you try to delete the last remaining project:

```json theme={null}
{ "code": "conflict", "message": "organization must have at least one project" }
```

A project must also be empty before it can be deleted — archive or remove its active resources first.

## Scoping Requests to a Project

Scope a request to a project **by name** with the CLI's `--project <name>` flag or by setting the project on an SDK client — both look the name up and send the project's ID for you, so you never handle the raw ID. When calling the REST API directly, send that ID yourself in the `X-Kernel-Project-Id` header (the header takes a project ID, not a name). Without it (and without a project-scoped API key), requests act on your organization's **default project**: reads return the default project's resources, and writes create resources in it.

```bash theme={null}
curl https://api.onkernel.com/browsers \
  -H "Authorization: Bearer $KERNEL_API_KEY" \
  -H "X-Kernel-Project-Id: proj_abc123"
```

### Resolution rules

* **Unknown or archived project** — a header naming a project that doesn't exist or isn't active fails with `404 Not Found`:

  ```json theme={null}
  { "code": "project_not_found", "message": "Project not found or inactive" }
  ```

* **The default project is the baseline** — naming your organization's default project is exactly equivalent to omitting the header, for both reads and writes.

* **Scoped keys reject mismatches** — with a project-scoped API key, a header naming any other project fails with `403 Forbidden` (see [API keys](#api-keys) below).

### SDK usage

Set the header on the client so every request is scoped to the project. You can also override it per-request.

<CodeGroup>
  ```typescript TypeScript theme={null}
  import Kernel from '@onkernel/sdk';

  // Scope the whole client to a project
  const kernel = new Kernel({
    defaultHeaders: { 'X-Kernel-Project-Id': 'proj_abc123' },
  });

  const browser = await kernel.browsers.create();

  // Or override per-request
  const other = await kernel.browsers.create(
    {},
    { headers: { 'X-Kernel-Project-Id': 'proj_def456' } },
  );
  ```

  ```python Python theme={null}
  from kernel import Kernel

  # Scope the whole client to a project
  kernel = Kernel(
      default_headers={"X-Kernel-Project-Id": "proj_abc123"},
  )

  browser = kernel.browsers.create()

  # Or override per-request
  other = kernel.browsers.create(
      extra_headers={"X-Kernel-Project-Id": "proj_def456"},
  )
  ```

  ```go Go theme={null}
  package main

  import (
  	"context"

  	"github.com/kernel/kernel-go-sdk"
  	"github.com/kernel/kernel-go-sdk/option"
  )

  func main() {
  	ctx := context.Background()

  	// Scope the whole client to a project.
  	client := kernel.NewClient(
  		option.WithHeader("X-Kernel-Project-Id", "proj_abc123"),
  	)

  	browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{})
  	if err != nil {
  		panic(err)
  	}
  	_ = browser

  	// Or override per-request.
  	other, err := client.Browsers.New(
  		ctx,
  		kernel.BrowserNewParams{},
  		option.WithHeader("X-Kernel-Project-Id", "proj_def456"),
  	)
  	if err != nil {
  		panic(err)
  	}
  	_ = other
  }
  ```
</CodeGroup>

## Authentication and Project Scope

### API keys

API keys can be **org-wide** or **project-scoped**.

* **Existing API keys are org-wide.** They see every resource in your organization across all projects. Include an `X-Kernel-Project-Id` header to restrict a single request to one project.
* **Project-scoped API keys** can only access resources inside the project they were issued for. Create one from the **API Keys** page in the dashboard, the [CLI](/reference/cli/api-keys), an SDK, or the [API keys guide](/info/api-keys), and pass the target `project_id` when generating the key. Requests made with a scoped key are automatically limited to that project — no header required. If you do send an `X-Kernel-Project-Id` header and it conflicts with the key's project, the request is rejected with `403 Forbidden`.

### OAuth

OAuth tokens (used by the Kernel CLI and MCP server) are **always org-wide**. You cannot bind an OAuth session to a single project. To scope OAuth-authenticated requests, send the `X-Kernel-Project-Id` header with each request — or use the CLI's `--project` flag (see below).

## Using Projects from the CLI

The Kernel [CLI](/reference/cli/projects) has first-class project support:

* A global `--project <id-or-name>` flag scopes any command to a single project. Names are resolved case-insensitively, so `--project staging` works.
* The `KERNEL_PROJECT` environment variable does the same, so you can set it once in your shell or CI.
* A `kernel projects` command group lets you list, create, get, and delete projects, and manage per-project limit overrides.

```bash theme={null}
# Scope a single command
kernel browsers list --project staging

# Scope every command in the shell
export KERNEL_PROJECT=staging
kernel apps list

# Manage projects
kernel projects list
kernel projects create staging
kernel projects limits set staging --max-concurrent-sessions 5
```

Under the hood, `--project` (or the env var) adds the `X-Kernel-Project-Id` header to every authenticated request. It's the recommended way to target a specific project when you're logged in with OAuth (`kernel login`), since OAuth itself is always org-wide.

## Managing Projects

Use the `/org/projects` REST endpoints (or the SDKs' `projects` resource) to manage projects.

| Method   | Path                 | Description                                                      |
| -------- | -------------------- | ---------------------------------------------------------------- |
| `GET`    | `/org/projects`      | List projects in the organization                                |
| `POST`   | `/org/projects`      | Create a project                                                 |
| `GET`    | `/org/projects/{id}` | Get a project by ID                                              |
| `PATCH`  | `/org/projects/{id}` | Update a project's name or status (`active` / `archived`)        |
| `DELETE` | `/org/projects/{id}` | Delete a project (must be empty and not the last active project) |

### Create a project

<CodeGroup>
  ```typescript TypeScript theme={null}
  import Kernel from '@onkernel/sdk';

  const kernel = new Kernel();

  const project = await kernel.projects.create({ name: 'staging' });
  console.log(project.id); // proj_abc123
  ```

  ```python Python theme={null}
  from kernel import Kernel

  kernel = Kernel()

  project = kernel.projects.create(name="staging")
  print(project.id)  # proj_abc123
  ```

  ```go Go theme={null}
  package main

  import (
  	"context"
  	"fmt"

  	"github.com/kernel/kernel-go-sdk"
  )

  func main() {
  	ctx := context.Background()
  	client := kernel.NewClient()

  	project, err := client.Projects.New(ctx, kernel.ProjectNewParams{
  		CreateProjectRequest: kernel.CreateProjectRequestParam{
  			Name: "staging",
  		},
  	})
  	if err != nil {
  		panic(err)
  	}

  	fmt.Println(project.ID) // proj_abc123
  }
  ```
</CodeGroup>

### List projects

<CodeGroup>
  ```typescript TypeScript theme={null}
  for await (const project of kernel.projects.list()) {
    console.log(project.id, project.name, project.status);
  }
  ```

  ```python Python theme={null}
  for project in kernel.projects.list():
      print(project.id, project.name, project.status)
  ```

  ```go Go theme={null}
  pager := client.Projects.ListAutoPaging(ctx, kernel.ProjectListParams{})
  for pager.Next() {
  	project := pager.Current()
  	fmt.Println(project.ID, project.Name, project.Status)
  }
  if err := pager.Err(); err != nil {
  	panic(err)
  }
  ```
</CodeGroup>

### Update a project

<CodeGroup>
  ```typescript TypeScript theme={null}
  await kernel.projects.update('proj_abc123', { name: 'production' });
  ```

  ```python Python theme={null}
  kernel.projects.update("proj_abc123", name="production")
  ```

  ```go Go theme={null}
  project, err := client.Projects.Update(
  	ctx,
  	"proj_abc123",
  	kernel.ProjectUpdateParams{
  		UpdateProjectRequest: kernel.UpdateProjectRequestParam{
  			Name: kernel.String("production"),
  		},
  	},
  )
  if err != nil {
  	panic(err)
  }
  _ = project
  ```
</CodeGroup>

### Delete a project

<CodeGroup>
  ```typescript TypeScript theme={null}
  await kernel.projects.delete('proj_abc123');
  ```

  ```python Python theme={null}
  kernel.projects.delete("proj_abc123")
  ```

  ```go Go theme={null}
  if err := client.Projects.Delete(ctx, "proj_abc123"); err != nil {
  	panic(err)
  }
  ```
</CodeGroup>

<Info>
  You can't delete a project that still owns active resources, and you can't delete the last remaining active project in your org.
</Info>

## Concurrency Limits

Kernel caps how many browser sessions can run at once, at two levels:

* **Organization limit** — the total concurrent sessions allowed across your whole organization, determined by your plan. Every session counts against it.
* **Per-project limits** — optional caps on individual projects, so one team or environment can't consume the entire org limit.

Per-project caps come from two places:

* **An org-wide default** — one value that every project inherits unless it sets its own. It applies to existing and newly created projects alike, so you don't have to configure each project by hand.
* **A per-project override** — an explicit cap on a single project that takes precedence over the default.

A project's effective cap resolves in this order:

1. The project's explicit override, if set.
2. Otherwise, the organization's default project cap, if set.
3. Otherwise, no per-project cap — only the organization limit applies.

A per-project cap never lets a project exceed your organization's concurrent-session limit.

| Method  | Path                        | Description                                                          |
| ------- | --------------------------- | -------------------------------------------------------------------- |
| `GET`   | `/org/limits`               | Get the org concurrent-session limit and the default per-project cap |
| `PATCH` | `/org/limits`               | Set the default per-project cap (send `0` to clear it)               |
| `GET`   | `/org/projects/{id}/limits` | Get a single project's limit overrides                               |
| `PATCH` | `/org/projects/{id}/limits` | Set a single project's limit overrides (send `0` to clear a cap)     |

### Set an org-wide default

Apply a default of 10 concurrent sessions to every project that doesn't have its own override:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PATCH https://api.onkernel.com/org/limits \
    -H "Authorization: Bearer $KERNEL_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "default_project_max_concurrent_sessions": 10 }'
  ```

  ```typescript TypeScript theme={null}
  import Kernel from '@onkernel/sdk';

  const kernel = new Kernel();

  await kernel.organization.limits.update({
    default_project_max_concurrent_sessions: 10,
  });
  ```

  ```python Python theme={null}
  from kernel import Kernel

  kernel = Kernel()

  kernel.organization.limits.update(default_project_max_concurrent_sessions=10)
  ```

  ```go Go theme={null}
  package main

  import (
  	"context"

  	"github.com/kernel/kernel-go-sdk"
  )

  func main() {
  	ctx := context.Background()
  	client := kernel.NewClient()

  	_, err := client.Organization.Limits.Update(ctx, kernel.OrganizationLimitUpdateParams{
  		UpdateOrgLimitsRequest: kernel.UpdateOrgLimitsRequestParam{
  			DefaultProjectMaxConcurrentSessions: kernel.Int(10),
  		},
  	})
  	if err != nil {
  		panic(err)
  	}
  }
  ```
</CodeGroup>

To cap a specific project differently, set an explicit override on it with `PATCH /org/projects/{id}/limits` — that value takes precedence over the default.

See the [API reference](https://kernel.sh/docs/api-reference/projects/list-projects) for full request and response schemas, including `ProjectLimits` for per-project concurrency caps.
