> ## Documentation Index
> Fetch the complete documentation index at: https://bisibility.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Projects

> List, update, delete, and configure project defaults.

Project API keys are scoped to one project, so `GET /projects` returns only that
project and `POST /projects` remains forbidden for them. A personal access token
returns every project the user belongs to and can create a new one when its
tier is at least `write`.

## Create project

```http theme={null}
POST /api/v1/projects
Authorization: Bearer <personal_token>
Content-Type: application/json

{
  "name": "Example",
  "domain": "example.com",
  "tracking_scope": "country"
}
```

The response status is `201`. Self-hosters may cap the number of projects owned
by one user with `BISIBILITY_MAX_PROJECTS_PER_USER`.

## List projects

```http theme={null}
GET /api/v1/projects
Authorization: Bearer <api_key>
```

Response:

```json theme={null}
{
  "data": [
    {
      "id": "prj_abc123",
      "name": "Example",
      "domain": "example.com",
      "created_at": "2026-06-28T10:00:00.000Z",
      "updated_at": "2026-06-28T10:00:00.000Z",
      "write_mode": "active"
    }
  ],
  "meta": { "next_cursor": null }
}
```

## Get project

```http theme={null}
GET /api/v1/projects/{project_id}
Authorization: Bearer <api_key>
```

`project_id` can be the public `prj_...` id or the internal project id. It must
match the authenticated API key's project.

Response:

```json theme={null}
{
  "id": "prj_abc123",
  "name": "Example",
  "domain": "example.com",
  "created_at": "2026-06-28T10:00:00.000Z",
  "updated_at": "2026-06-28T10:00:00.000Z",
  "write_mode": "active"
}
```

If the project does not match a project key, the API returns `403`. A PAT gets
`404` when the user is not a member, so project existence is not disclosed.

## Get project overview

```http theme={null}
GET /api/v1/projects/{project_id}/overview?range=28d&device=all&tag=Docs
Authorization: Bearer <api_key>
```

Returns numeric project health aggregates without requiring clients to paginate
the keyword list. `range` defaults to `28d` and accepts `7d`, `28d`, or `90d`.
`device` defaults to `all` and also accepts `desktop` or `mobile`. `tag` is an
optional exact tag name.

```json theme={null}
{
  "project_id": "prj_abc123",
  "tracked_keyword_count": 42,
  "keywords_added_this_month": 3,
  "average_position": 12.4,
  "average_position_delta": 1.8,
  "top_3_count": 4,
  "top_10_count": 11,
  "top_10_delta": 2,
  "top_100_count": 37,
  "visibility": 24.7,
  "visibility_delta": 3.1,
  "position_distribution": [
    { "min": 1, "max": 3, "count": 4 },
    { "min": 4, "max": 10, "count": 7 },
    { "min": 11, "max": 20, "count": 8 },
    { "min": 21, "max": 50, "count": 10 },
    { "min": 51, "max": 100, "count": 8 }
  ],
  "last_check_at": "2026-06-28T10:00:00.000Z",
  "next_check_at": "2026-06-29T06:00:00.000Z"
}
```

Positive `average_position_delta` means the average rank improved. Visibility
is a percentage and `visibility_delta` is measured in percentage points. Rank
aggregates and distribution counts are `null` before the project has check data;
a completed check with no keyword in a bucket returns the real count `0`.

## Update project

```http theme={null}
PATCH /api/v1/projects/{project_id}
Authorization: Bearer <api_key>
Content-Type: application/json
```

Request body may include `name`, `domain`, or both.

## Get project defaults

```http theme={null}
GET /api/v1/projects/{project_id}/defaults
Authorization: Bearer <api_key>
```

Returns the effective market and schedule. Projects without a stored defaults
row return the schema-defined schedule defaults and a market inferred from existing
keywords, without creating a row. The `source` field is `explicit`, `derived`,
or `fallback` and identifies how the effective market was selected.

## Update project defaults

```http theme={null}
PATCH /api/v1/projects/{project_id}/defaults
Authorization: Bearer <api_key>
Content-Type: application/json
```

Project defaults store scheduling intent. `country` and `device` in this
endpoint represent the project's current default keyword market, derived from
existing keywords. Supplying both `country` and `device` moves eligible keywords
from the current default market to the new market. `city` narrows that market
when it resolves inside the selected country. `location_key` is the canonical
market selector and can point at a country, region, or city. Omitting market
fields updates only the schedule and does not move keywords.

`country` and `device` must be supplied together unless `location_key` is
provided.

```json theme={null}
{
  "frequency": "weekly",
  "cron_expression": null,
  "timezone": "Europe/Warsaw",
  "jitter_minutes": 60,
  "country": "Poland",
  "city": "Warsaw",
  "location_key": "PL/Masovian Voivodeship/Warsaw",
  "device": "mobile"
}
```

Schedule-only update:

```json theme={null}
{
  "frequency": "daily",
  "timezone": "UTC",
  "jitter_minutes": 60
}
```

Response:

```json theme={null}
{
  "project_id": "prj_abc123",
  "frequency": "daily",
  "cron_expression": null,
  "timezone": "UTC",
  "jitter_minutes": 60,
  "serp_depth": 100,
  "serp_stop_on_match": true,
  "country": "United States",
  "city": null,
  "location_key": "US",
  "device": "desktop",
  "source": "explicit",
  "last_checked_at": null,
  "next_check_at": null,
  "updated_at": "2026-06-28T10:00:00.000Z"
}
```

## Delete project

```http theme={null}
DELETE /api/v1/projects/{project_id}
Authorization: Bearer <api_key>
```

Deletes the scoped project and returns the deleted project resource. A PAT may
delete it only when the user has the `owner` role.
