> This page is for Gateway.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.merge.dev/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.merge.dev/_mcp/server.

# Projects

> Project CRUD over /v1/projects: the project object, cursor pagination, updates, deactivation, and the delete guard for active project API keys.

`/v1/projects` creates and maintains projects, each with its own [routing policy](/merge-gateway/automation/projects-api/project-routing), [budget](/merge-gateway/automation/projects-api/budgets), and [usage](/merge-gateway/automation/projects-api/usage) reporting. All endpoints need the `manage_projects` scope.

## Create a project

`POST /v1/projects` returns `201` with the project object. Only `name` (up to 255 characters) is required.

```bash
curl -X POST https://api-gateway.merge.dev/v1/projects \
  -H "Authorization: Bearer $MERGE_GATEWAY_MANAGEMENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Internal Tools", "description": "Admin dashboards and scripts" }'
```

Gateway derives `slug` from the name (`Internal Tools` becomes `internal-tools`), appending `-2`, `-3`, and so on on collision. The API doesn't accept `slug`; change it in the dashboard. The project object has `id`, `organization_id`, `name`, `slug`, `description`, `is_active`, `uses_organization_default_routing`, `routing_policy`, `budget_config`, `created_at`, and `updated_at` ([types](/merge-gateway/management-api)).

## List and get projects

`GET /v1/projects` returns up to `limit` projects (default 20, max 100), oldest first, in a `{results, next, previous}` envelope. While `next` isn't `null`, pass it as `cursor` for the next page.

**`List`**

```bash title="List"
curl "https://api-gateway.merge.dev/v1/projects?limit=100" \
  -H "Authorization: Bearer $MERGE_GATEWAY_MANAGEMENT_KEY"
```

**`Next page`**

```bash title="Next page"
curl "https://api-gateway.merge.dev/v1/projects?limit=100&cursor=$NEXT_CURSOR" \
  -H "Authorization: Bearer $MERGE_GATEWAY_MANAGEMENT_KEY"
```

**`Get one`**

```bash title="Get one"
curl https://api-gateway.merge.dev/v1/projects/$PROJECT_ID \
  -H "Authorization: Bearer $MERGE_GATEWAY_MANAGEMENT_KEY"
```

A project in another organization returns `404`.

## Update a project

`PATCH /v1/projects/{project_id}` changes only the fields you send: `name`, `description`, `is_active`, `budget_config`, `uses_organization_default_routing`, and `routing_policy`. `name` and `is_active` can't be `null`: sending `null` returns `422`, so omit a field to keep it. `"description": null` clears the description.

```bash
curl -X PATCH https://api-gateway.merge.dev/v1/projects/$PROJECT_ID \
  -H "Authorization: Bearer $MERGE_GATEWAY_MANAGEMENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Batch Pipelines", "description": "Nightly ETL jobs" }'
```

Setting `is_active` to `false` doesn't stop traffic: requests naming the project are still served, but its guardrail overrides stop applying and new project API keys are blocked.

## Delete a project

`DELETE /v1/projects/{project_id}` returns `204` and also deletes the project's routing policy and budget.

```bash
curl -X DELETE https://api-gateway.merge.dev/v1/projects/$PROJECT_ID \
  -H "Authorization: Bearer $MERGE_GATEWAY_MANAGEMENT_KEY"
```

While active project API keys exist, delete returns `409 PROJECT_HAS_ACTIVE_API_KEYS` with their `count`; revoke them first. If Gateway can't confirm there are no live keys, it returns `503` and deletes nothing, so retry.

## Next steps

#### [Project routing](/merge-gateway/automation/projects-api/project-routing)

Give a project its own routing policy

#### [Budgets](/merge-gateway/automation/projects-api/budgets)

Set a project's spend limit and alerts

#### [Usage](/merge-gateway/automation/projects-api/usage)

Read a project's spend