# Code Search API Reference

## Endpoints

### GET /v1/search — Code Search
Search across all indexed repositories.

**Parameters:**
- `q` (required) — Search query, must be valid regex (see syntax below). Special characters like `(`, `)`, `.`, `$` must be escaped with `\`
- `num` (optional) — Maximum number of file results to return
- `context` (optional) — Number of context lines before/after each match (like grep -C)
- `maxmatches` (optional) — Stop searching after this many total matches
- `chunks` (optional, default: true) — Set to `false` to return LineMatches instead of ChunkMatches

**Example:** `/v1/search?q=hook_help&num=10&context=3`

To search across multiple repos in one call, use `r:^(repo1|repo2|repo3)$` in the query:
`/v1/search?q=hook_help+r:^(webform|token|pathauto)$&num=10`

### GET /v1/search/repo — Repository List
Returns all indexed repositories and their metadata.

**Example:** `/v1/search/repo`

To fetch metadata for specific repos, use `r:^(repo1|repo2|repo3)$` in a single call:
`/v1/search/repo?q=r:^(webform|token|pathauto)$`

---

## Query Syntax

### Text Search
| Pattern | Meaning |
|---------|---------|
| `foo bar` | Files containing both "foo" and "bar" |
| `"foo bar"` | Files containing the exact phrase "foo bar" |
| `foo OR bar` | Files containing "foo" or "bar" |
| `foo -bar` | Files containing "foo" but not "bar" |

### Field Filters
| Field | Example | Meaning |
|-------|---------|---------|
| `r:` or `repo:` | `r:webform` | Restrict to repos matching "webform" |
| | `r:^(webform\|token)$` | Restrict to multiple specific repos (use one call, not multiple) |
| `f:` or `file:` | `f:\.module$` | Restrict to file paths matching pattern |
| `lang:` | `lang:php` | Restrict to files of a specific language |
| `b:` or `branch:` | `b:main` | Restrict to a specific branch |
| `case:yes` | `case:yes Foo` | Case-sensitive search |
| `sym:` | `sym:className` | Search symbol definitions |

### Regex
Queries are regex by default. Special characters (`.`, `(`, `)`, `{`, etc.) must be escaped with `\`.

**Examples:**
- `hook_\w+_alter` — matches hook_form_alter, hook_node_alter, etc.
- `function\s+mymodule_` — matches function declarations in mymodule
- `once\(` — literal search for `once(` (parenthesis escaped)
- `\$form_state` — literal `$form_state` (dollar sign escaped)

### Important: Always Batch Queries

**Never loop over individual repos.** Always query multiple repositories in a **single call** using `r:^(repo1|repo2|repo3)$` regex alternation:

`/v1/search?q=hook_help+r:^(webform|token|pathauto)$&num=10`

**Bad** — 3 separate requests:
- `/v1/search?q=hook_help+r:webform`
- `/v1/search?q=hook_help+r:token`
- `/v1/search?q=hook_help+r:pathauto`

**Good** — 1 batched request:
- `/v1/search?q=hook_help+r:^(webform|token|pathauto)$`

This applies to both search and repo endpoints. The `r:` filter accepts any regex. Use `^...$` anchors for exact name matching.

---

## Response Structure

### /v1/search Response (default: ChunkMatches)

Returns structured location objects with exact byte offsets, line numbers, and columns. Chunks can span multiple lines when using `context`, and each chunk carries precise start/end `Ranges`.

```json
{
  "Result": {
    "Files": [
      {
        "Repository": "webform",
        "FileName": "webform.module",
        "ChunkMatches": [
          {
            "Content": "<base64-encoded chunk content>",
            "ContentStart": {
              "ByteOffset": 1223,
              "LineNumber": 35,
              "Column": 1
            },
            "Ranges": [
              {
                "Start": { "ByteOffset": 1237, "LineNumber": 35, "Column": 15 },
                "End": { "ByteOffset": 1246, "LineNumber": 35, "Column": 24 }
              }
            ]
          }
        ]
      }
    ],
    "MatchCount": 150,
    "FileCount": 12
  }
}
```

> `Content` is **base64-encoded**. Each `Range` gives the exact match boundaries (byte offset, line, column) relative to the file start.

### /v1/search Response (`chunks=false`: LineMatches)

Legacy format with one entry per matched line and flat offset integers.

```json
{
  "Result": {
    "Files": [
      {
        "Repository": "webform",
        "FileName": "webform.module",
        "LineMatches": [
          {
            "Line": "<base64-encoded line content>",
            "LineNumber": 35,
            "LineStart": 1223,
            "LineEnd": 1250,
            "LineFragments": [
              {
                "LineOffset": 14,
                "Offset": 1237,
                "MatchLength": 9
              }
            ]
          }
        ]
      }
    ],
    "MatchCount": 150,
    "FileCount": 12
  }
}
```

> `Line` values are **base64-encoded**. Decode them to get the actual source line.

### /v1/search/repo Response
```json
{
  "List": {
    "Repos": [
      {
        "Repository": {
          "Name": "drupal/webform",
          "URL": "...",
          "RawConfig": {
            "drupal-core": "11.x",
            "drupal-usage": "1234567",
            "drupal-security": "covered",
            "priority": "80"
          }
        },
        "Stats": {
          "Documents": 500,
          "ContentBytes": 2048000
        }
      }
    ]
  }
}
```

**RawConfig metadata:**
- `drupal-core` — Drupal core compatibility version
- `drupal-usage` — Total install count
- `drupal-security` — Security coverage status ("covered" or empty)
- `priority` — Indexing priority score
