---
title: "Search code in a workspace"
method: POST
path: "/workspaces/{workspace}/codesearch"
tags: ["Workspaces"]
---

# Search code in a workspace

`POST /workspaces/{workspace}/codesearch`

## Request

- Base URL: `https://api.bitbucket.org/2.0`
- URL: `https://api.bitbucket.org/2.0/workspaces/{workspace}/codesearch`
- Auth: one of:
  - OAuth 2
    - Authorization code: `https://bitbucket.org/site/oauth2/authorize`
    - Token URL: `https://bitbucket.org/site/oauth2/access_token` scopes: `repository`
  - HTTP basic
  - API key in header `Authorization`

Search code across repositories in a workspace.

Results are limited to the repositories that the calling user can
access.

#### Supported authentication

The endpoint requires a credential that identifies the calling user.
The following authentication modes are accepted:

- Browser session cookie (Bitbucket web UI)
- Atlassian API token (Basic auth using your email and a scoped API
  token that includes both `repository:read` and `workspace:read`)
- OAuth 2.0 via Forge or an Atlassian app (3LO or 2LO, with both
  `repository:read` and `workspace:read` granted to the app)
- Bitbucket repository, project, or workspace access tokens

Authentication modes that **do not** identify the user, including
Atlassian Connect JWT, legacy Bitbucket OAuth not migrated to
identity, app passwords, and API keys, are rejected with a `403`.

#### Request body

Requires `Content-Type: application/json`. Anything else returns `415`.

`query.search_term` is required. These fields are accepted:

- `query.search_term`: non-blank, at most 1024 characters
- `query.filters`
- `config.max_results`: a JSON integer (a string or float is rejected),
  1 to 100, default 50
- `config.search_mode`: `LEXICAL` or omitted
- `request_id` and `pagination.next_cursor`: see "Pagination"

Any other field is ignored.

```json
{
  "query": {
    "search_term": "logr",
    "filters": { "attribute": "language", "eq": "go" }
  },
  "config": {
    "max_results": 10
  }
}
```

#### Search mode

This endpoint performs lexical code search only. `config.search_mode`
is fixed to `LEXICAL` server-side, so callers may omit the field or
set it explicitly to `LEXICAL`. Any other value returns a `400`.

#### Filters

Use `query.filters` to narrow a search. For example, to search for
`logr` only in Go files whose path matches `cmd/*/main.go`:

```json
{
  "query": {
    "search_term": "logr",
    "filters": {
      "and": [
        { "attribute": "language", "eq": "go" },
        { "attribute": "path", "wildcard": "cmd/*/main.go" }
      ]
    }
  }
}
```

A filter names an `attribute` and exactly one operator:

* `eq` matches a value, and works with every attribute. Values are
  not case-sensitive.
* `wildcard` matches a pattern, and works only with `path` and
  `repository`. In a pattern, `*` matches any run of characters and `?`
  matches one character. A pattern must match the whole value. On
  `repository` it ignores case, and on `path` it is case-sensitive.

Setting both `eq` and `wildcard` on one filter returns `400`.

These attributes are supported:

* `repository`: a repository slug, such as `my-repo`. Use a wildcard
  such as `my-*` to match every slug that starts with `my-`.
* `path`: with `eq`, a value such as `src/main` matches any path that
  contains those folder or file names in that order, such as
  `app/src/main/App.java`. Each name must match in full. With
  `wildcard`, the pattern must match the whole file path, such as
  `*/main.go`.
* `language`: a language name, such as `go`, `python`, or
  `javascript`. To filter by file extension, use `extension`.
* `extension`: a file extension without the dot, such as `go` or `py`.
* `project`: a project key or name.
* `scm`: the source control host of the repository.
* `branches`: searches run on the default branch only. Set `branches`
  to `"true"` to search all indexed branches.
* `forks`: by default, results include repositories that are forks. Set
  `forks` to `"true"` to get results from forks only, or to `"false"` to
  get results from repositories that are not forks.

`branches` only accepts the value `"true"`. `branches` and `forks` only
work when they are the whole of `query.filters`. You cannot combine
them with other filters.

Combine filters with `and`, `or`, and `not`. Filters can nest up to 5
levels deep, and the workspace filter this endpoint adds uses one of
them. That leaves 3 levels of `and`, `or`, or `not` around a filter.
Deeper filters return `400`. This filter uses all 3 levels:

```json
{
  "and": [
    { "or": [
      { "not": { "attribute": "language", "eq": "python" } }
    ] },
    { "attribute": "extension", "eq": "go" }
  ]
}
```

#### Workspace scoping

The endpoint always applies a `namespace = <workspace_slug>` filter
and passes the workspace's wrapped UUID as `scope.workspace_ids`.
Callers must **not** include a `namespace` filter in `query.filters`
(anywhere in the tree, including nested AND/OR/NOT). Doing so returns
`400`. A `scope.workspace_ids` value in the request body is ignored.
The workspace in the URL always sets the scope.

#### Pagination

Pagination uses a cursor. To page beyond the first result set, include
the `id` from the previous response as `request_id`, and the previous
response's `pagination.next_cursor` as `pagination.next_cursor`.
`request_id` is required whenever `pagination` is sent. Repeat
until `pagination.has_more` is `false`.

On every page, `query.search_term` and `query.filters` must be the
same as on the first page, including the order of items inside
`and`/`or`. A different value returns `400`. `config.max_results` may
change between pages.

```json
{
  "query": { "search_term": "logr" },
  "config": { "max_results": 10 },
  "request_id": "550e8400-e29b-41d4-a716-446655440000",
  "pagination": { "next_cursor": "eyJlbnRyaWVzIjpbeyJsZXhpY2FsS2V5Ijoi..." }
}
```

#### Response

You can rely on these fields:

- `id`, `pagination.next_cursor`, `pagination.has_more`
- `repositories[]`: `url`, `host`, `ns`, `name`
- `matched_files[]`: `path`, `name`, `language`, `url`, `repositoryId`,
  `version` (the commit hash), `branch_or_tag_name`, `is_default_branch`
- `matched_files[].matched_chunks[]`: `source_code`, `line_range`,
  `matched_line_numbers`, `matched_ranges`
- `metadata.total_count`: an approximate size, meaningful on page 1 only

`branch_or_tag_name` and `is_default_branch` are left out when not
known, and `language` can be `null`.

Line numbers start at 1, and `line_range.end` is the last line of the
chunk. Each `matched_ranges` entry is `[line, start_column,
end_column]`, where `line` is the line number in the file, the columns
start at 0, and `end_column` is exclusive.

`matched_line_numbers` and `matched_ranges` mark where a search term
appears as a whole word in the chunk. They can be empty even when the
chunk matched.

Other fields can change or be removed without notice, so ignore any
field not listed here. Treat ids and `pagination.next_cursor` as opaque.

Errors from this endpoint, including errors from the search backend, use
the standard Bitbucket error envelope:
`{"type": "error", "error": {"message": ..., "code": ...}}`.
A backend error keeps its status code, except that a backend 5xx is
returned as `502 Bad Gateway`. The backend's own error code, when there
is one, is in `error.detail.upstream_code`.

## Response `200`

Search completed successfully. See the Response section for the fields you can rely on.

## Other responses

- `400`: If the search request was invalid due to one of the following reasons: * a missing or malformed `query.search_term`, `config`, or `pagination`. * a `config.max_results` or `config.search_mode` value that is not allowed. * an unsupported filter, a filter nested too deeply, or a `namespace` filter (the namespace is set implicitly by the workspace in the URL). * a pagination cursor used with a different `query.search_term` or `query.filters`.
- `401`: The request is not authenticated.
- `403`: The caller is not a member of the workspace (non-members get `403`, as on other workspace endpoints), the authentication mode used to make the request is not supported (see description), or the credential is missing a required scope.
- `404`: The workspace does not exist or the feature is not enabled.
- `415`: `Content-Type` was not `application/json`.
- `429`: Too many requests, try again later.
- `502`: The search backend returned a server error or an invalid response.

## Changes

- **2026-10-21** `6ae783fa75da`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/atlassian/apis/bitbucket-api/changes/workspaces/:workspace/codesearch/post.md)

---

[API](https://skmtc.dev/atlassian/apis/bitbucket-api.md) - [All operations](https://skmtc.dev/atlassian/apis/bitbucket-api/llms.txt) - [OpenAPI document](https://skmtc.dev/atlassian/apis/bitbucket-api/revisions/6ae783fa75da?raw)
