> For the complete documentation index, see [llms.txt](https://bugrecon.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bugrecon.gitbook.io/docs/features-list/api.md).

# Public API

The **Public API** lets you interact with BugRecon programmatically. Use it from scripts, CI/CD pipelines, or third-party tools to manage projects, start scans, monitor CertStream, and pull scan results without touching the web UI.

The API exposes the same features and respects the same subscription permissions and quotas as your web account. Everything you can do manually in the interface, you can automate through the API.

> **Plan requirement:** API access is available on **Basic**, **Premium** and **Enterprise** plans. Free accounts cannot create or use API tokens. If you are on Free, upgrade your plan before reading further.

***

## Authentication

The API uses **personal API tokens** issued from your account.

* Each token is a long, randomly generated secret prefixed with `br_`.
* Tokens are created from the **My Account** page and shown **only once** - store them safely.
* You can have up to **5 active tokens** per account.
* A token inherits your current subscription plan's permissions and quotas.
* Tokens can be revoked at any time; revocation is immediate.
* If you downgrade to **Free**, existing tokens stop working immediately (`403`). They remain in your account and resume working if you upgrade again.

> **Security note:** Tokens are stored server-side as SHA-256 hashes only. Even an administrator cannot see your token after it has been created. Treat a token like a password: never commit it to source control, never share it, and rotate it if you suspect it has leaked.

***

## Create a token

1. Open **My Account** (profile menu → Account).
2. Scroll to the **API Tokens** card.
3. Click **Create token**.
4. Give the token a descriptive name (e.g. `ci-pipeline`, `my-script`). The name is for your own reference.
5. Click **Create**. The token is displayed once in a modal.
6. Click the copy icon, paste the token into your secret manager, then close the modal.

You will never be able to view the plaintext again. If you lose it, revoke it and create a new one.

***

## Revoke a token

In the **API Tokens** card of **My Account**, click **Revoke** next to the token. The token stops working immediately - any script using it will receive `401 Unauthorized` on its next call.

Revoke a token when:

* It might have leaked (committed by mistake, shared in a chat, etc.).
* The script or integration using it is decommissioned.
* You rotate credentials on a schedule.

***

## Using a token

Send the token in the `Authorization` header as a Bearer token on every request:

```bash
curl -H "Authorization: Bearer br_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
     https://api.bugrecon.me/api/v1/projects
```

All API responses are JSON. Every response includes a `success` boolean. On error the response also includes an `error` field describing the problem.

**Base URL:** `https://api.bugrecon.me/api/v1`

All endpoints below are relative to this base.

***

## Response format

### Success

```json
{
  "success": true,
  "data": { ... }
}
```

Some endpoints return additional top-level fields (e.g. `message`, `pagination`) depending on the operation.

### Error

```json
{
  "success": false,
  "error": "Human-readable error message"
}
```

Common HTTP status codes:

| Status | Meaning                                                                        |
| ------ | ------------------------------------------------------------------------------ |
| 200    | Request succeeded                                                              |
| 400    | Invalid request (missing or malformed parameters)                              |
| 401    | Missing or invalid token                                                       |
| 403    | Forbidden - your subscription does not allow this action or a quota is reached |
| 404    | Resource not found (or not owned by you)                                       |
| 409    | Conflict (duplicate resource)                                                  |
| 429    | Rate limit or queue limit reached                                              |
| 500    | Server error                                                                   |
| 503    | Feature temporarily disabled                                                   |

***

## Rate limits

API tokens are rate-limited **per token** (separate counters from the web UI) based on your subscription plan:

| Plan       | Requests per hour |
| ---------- | ----------------- |
| Basic      | 1 000             |
| Premium    | 10 000            |
| Enterprise | 10 000            |

When the limit is exceeded, you receive `429` with a `retry_after` field (seconds).

Your scan, subdomain, and CertStream quotas are also enforced on API calls, exactly as they are in the web UI.

***

## Endpoints

### Index

`GET /api/v1/` - Lists all available endpoints. Useful as a sanity check.

***

### Projects

#### List your projects

`GET /api/v1/projects`

**Response**

```json
{
  "success": true,
  "data": [
    {
      "id": "uuid",
      "name": "Example",
      "slug": "example",
      "description": "...",
      "origin": "api",
      "disabled": false,
      "is_favorite": false,
      "thumbnail": "https://...",
      "created_at": "2026-04-17T12:00:00+00:00",
      "updated_at": "2026-04-17T12:00:00+00:00"
    }
  ]
}
```

#### Create a project

`POST /api/v1/projects`

**Request body**

```json
{
  "name": "My project",
  "description": "Optional description"
}
```

Projects created through the API have `origin: "api"`.

Returns the created project under `data`. Returns `409` if you already have a project with the same name.

#### Get a project

`GET /api/v1/projects/{project_id}`

Returns the project if you own it, otherwise `404`.

#### Update a project

`PUT /api/v1/projects/{project_id}`

**Request body**

```json
{
  "name": "New name",
  "description": "New description"
}
```

Both fields are optional; omitted fields keep their current value.

#### Delete a project

`DELETE /api/v1/projects/{project_id}`

Deletes the project and **all** its scopes, notes, scheduled jobs, and queued jobs. This action is not reversible.

***

### Scopes

A scope is a target (a domain) inside a project on which you run scans.

#### List scopes of a project

`GET /api/v1/projects/{project_id}/scopes`

**Response**

```json
{
  "success": true,
  "data": [
    {
      "id": "uuid",
      "project_id": "uuid",
      "target_domain": "example.com",
      "scan_type": "subdomain",
      "status": "completed",
      "scan_date": "2026-04-17T12:00:00+00:00",
      "scan_last_update": "2026-04-17T12:05:00+00:00",
      "subdomains_count": 42
    }
  ]
}
```

#### Create a scope

`POST /api/v1/projects/{project_id}/scopes`

**Request body**

```json
{
  "target_domain": "example.com",
  "subdomains": ["example.com", "*.example.com"]
}
```

* `target_domain` (required) - the primary domain shown in the UI.
* `subdomains` (optional) - initial list of subdomains, including wildcards.

#### Get a scope

`GET /api/v1/scopes/{scope_id}`

Returns the full scope record, including the list of discovered subdomains and all scan result fields (JSONB).

#### Get domain info

`GET /api/v1/scopes/by-domain/{domain}`

Returns scan data and saved credentials for a **single domain** across scopes you own or collaborate on (as `read_only` or `writer`). The domain is matched against the scope's `target_domain` or any entry of its `subdomains` list (case-insensitive).

Unlike `GET /api/v1/scopes/{scope_id}`, this endpoint does **not** return the whole scope: each `*_results` field is filtered to the single entry keyed by the requested domain (scanners store results as `{host: data}` dicts). Scope-wide fields such as the `subdomains` list, `processes`, `logs`, aggregated counts and `ai_results` are intentionally omitted.

**Query parameters (optional, used to disambiguate conflicts)**

| Param        | Description                                |
| ------------ | ------------------------------------------ |
| `project_id` | Restrict the search to a specific project. |
| `scope_id`   | Restrict the search to a specific scope.   |

**Success response (`200`)**

| Field                                                                                                                                                                                    | Description                                                                                                                                                                                                                                                                                                              |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `domain`                                                                                                                                                                                 | The domain exactly as queried.                                                                                                                                                                                                                                                                                           |
| `match_type`                                                                                                                                                                             | `"target_domain"` if the domain is the scope's main target, `"subdomain"` if it was found inside the discovered subdomains list.                                                                                                                                                                                         |
| `scope_id`                                                                                                                                                                               | UUID of the parent scope. Use it with `GET /api/v1/scopes/{scope_id}` if you need the whole scope.                                                                                                                                                                                                                       |
| `project_id`                                                                                                                                                                             | UUID of the parent project.                                                                                                                                                                                                                                                                                              |
| `project_name`                                                                                                                                                                           | Name of the parent project.                                                                                                                                                                                                                                                                                              |
| `target_domain`                                                                                                                                                                          | The scope's primary target. May differ from `domain` when `match_type` is `"subdomain"`.                                                                                                                                                                                                                                 |
| `scope_status`                                                                                                                                                                           | Current status of the parent scope (`pending`, `running`, `completed`, `failed`, …).                                                                                                                                                                                                                                     |
| `scan_date`                                                                                                                                                                              | When the scope was created.                                                                                                                                                                                                                                                                                              |
| `scan_last_update`                                                                                                                                                                       | Last scan activity on the scope.                                                                                                                                                                                                                                                                                         |
| `http_results`, `port_results`, `urls_results`, `screenshot_results`, `vulns_results`, `leaks_results`, `basic_auth_results`, `takeover_results`, `secrets_results`, `endpoints_results` | The entry stored for this domain, or `null` if the corresponding scan has not produced data for it.                                                                                                                                                                                                                      |
| `credentials`                                                                                                                                                                            | Credentials stored for this exact domain (table `subdomain_credentials`, filtered by `subdomain = {domain}`). Each entry contains `id`, `scope_id`, `subdomain`, `label`, `credential_type` (`basic_auth`, `form`, `api_key`, `ssh`, `other`), `username`, `password`, `login_url`, `notes`, `created_at`, `updated_at`. |

```json
{
  "success": true,
  "data": {
    "domain": "api.example.com",
    "match_type": "subdomain",
    "scope_id": "uuid",
    "project_id": "uuid",
    "project_name": "My project",
    "target_domain": "example.com",
    "scope_status": "completed",
    "scan_date": "2026-04-17T12:00:00+00:00",
    "scan_last_update": "2026-04-17T12:05:00+00:00",
    "http_results": { "80": { "status": 200 }, "443": { "status": 200 } },
    "port_results": { "tcp": [80, 443] },
    "urls_results": [{ "url": "https://api.example.com/login" }],
    "screenshot_results": "screenshot-uuid.png",
    "vulns_results": [{ "template": "exposed-panel", "severity": "medium" }],
    "leaks_results": null,
    "basic_auth_results": null,
    "takeover_results": null,
    "secrets_results": null,
    "endpoints_results": null,
    "credentials": [
      {
        "id": "uuid",
        "scope_id": "uuid",
        "subdomain": "api.example.com",
        "label": "staging",
        "credential_type": "basic_auth",
        "username": "admin",
        "password": "...",
        "login_url": null,
        "notes": null,
        "created_at": "2026-04-17T12:00:00+00:00",
        "updated_at": "2026-04-17T12:00:00+00:00"
      }
    ]
  }
}
```

The shape of each `*_results` value matches what the scanner stored for this host - it can be a list, a dict, or a string. See the per-scan documentation pages ([HTTP scan](/docs/features-list/scopes-overview/http-scan.md), [Port scan](/docs/features-list/scopes-overview/port-scan.md), etc.) for the exact schemas.

**Conflict response (`409`)**

When the domain resolves to more than one accessible scope (same domain declared under several projects, or present as a subdomain in multiple scopes), the endpoint returns `409` with the list of candidates. Re-issue the request with `?project_id=` or `?scope_id=` to pick one.

```json
{
  "success": false,
  "error": "Multiple scopes match this domain. Provide 'project_id' or 'scope_id' as a query parameter to disambiguate.",
  "conflict": true,
  "candidates": [
    {
      "scope_id": "uuid-1",
      "project_id": "uuid-a",
      "project_name": "Project A",
      "target_domain": "example.com",
      "match_type": "target_domain"
    },
    {
      "scope_id": "uuid-2",
      "project_id": "uuid-b",
      "project_name": "Project B",
      "target_domain": "other.com",
      "match_type": "subdomain"
    }
  ]
}
```

**Other responses**

* `400` - the domain path is empty.
* `404` - no accessible scope matches this domain (possibly after filtering by `project_id` / `scope_id`).

#### Update a scope

`PATCH /api/v1/scopes/{scope_id}`

Updates a scope's display name and/or **replaces** its whole domain list. Requires **owner or writer** access to the scope (read-only collaborators get `404`).

**Request body** (both fields optional, at least one required)

```json
{
  "target_domain": "example.com",
  "subdomains": ["www.example.com", "api.example.com"]
}
```

* `target_domain` - new primary domain shown in the UI. Trimmed, lowercased, control characters stripped; must be non-empty and at most 253 characters.
* `subdomains` - the **complete** new domain list. It **replaces** the existing list (it does not merge). Each entry must be a valid domain (`label.tld`, letters/digits/`-`/`_`, TLD 2+ letters) and may carry a `*.` wildcard prefix (e.g. `*.example.com`), which is preserved; entries are lowercased and de-duplicated. Up to **50000** domains.

Domain additions/removals are recorded in the scope's domain history (`source: "api"`).

**Success response (`200`)** - returns the full updated scope (same shape as `GET /api/v1/scopes/{scope_id}`).

**Other responses**

* `400` - neither field provided, an empty `target_domain`, `subdomains` is not a list, one or more domains are invalid (the response includes an `invalid` array of up to 20 offending values), or the list exceeds the maximum.
* `404` - scope not found or write access required.

#### List a scope's domains

`GET /api/v1/scopes/{scope_id}/subdomains`

Returns the scope's domain list, paginated. Read access (owner, writer, or read-only collaborator).

**Query parameters**

| Param       | Default | Description             |
| ----------- | ------- | ----------------------- |
| `page`      | `1`     | Page number (>= 1).     |
| `page_size` | `100`   | Items per page (1-500). |

**Success response (`200`)**

```json
{
  "success": true,
  "data": ["www.example.com", "api.example.com"],
  "total": 42,
  "page": 1,
  "page_size": 100,
  "has_more": false
}
```

#### Add domains to a scope

`POST /api/v1/scopes/{scope_id}/subdomains`

Adds domains to the scope's list. The list is merged and de-duplicated; domains already present are ignored. Requires **owner or writer** access.

**Request body**

```json
{
  "subdomains": ["new.example.com", "cdn.example.com"]
}
```

Same per-domain validation as the update endpoint. The merged list may not exceed **50000** domains.

**Success response (`200`)** - the updated scope, plus `added` (number of new domains actually inserted) and `total` (list size after the operation).

**Other responses**

* `400` - `subdomains` missing/not a list, empty, one or more invalid (`invalid` array returned), or the merged list exceeds the maximum.
* `404` - scope not found or write access required.

#### Remove domains from a scope

`DELETE /api/v1/scopes/{scope_id}/subdomains`

Removes the given domains from the scope's list (case-insensitive). Domains not present are ignored. Requires **owner or writer** access.

**Request body**

```json
{
  "subdomains": ["cdn.example.com"]
}
```

**Success response (`200`)** - the updated scope, plus `removed` (number of domains actually removed) and `total` (list size after the operation).

**Other responses**

* `400` - `subdomains` missing/not a list or empty.
* `404` - scope not found or write access required.

#### Delete a scope

`DELETE /api/v1/scopes/{scope_id}`

Cancels any queued jobs for the scope and deletes the scope.

***

### Start a scan

`POST /api/v1/scopes/{scope_id}/scan`

Starts an asynchronous scan on the scope. The response returns immediately; poll `GET /api/v1/scopes/{scope_id}` to track status.

**Request body**

```json
{
  "scan_type": "subdomain",
  "subdomains": ["example.com"],
  "options": ["passive", "active", "ai"]
}
```

| Field        | Required          | Description                                                                                                              |
| ------------ | ----------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `scan_type`  | yes               | One of: `subdomain`, `port`, `http`, `url`, `screenshot`, `vulns`, `leaks`, `basic_auth`, `takeover`, `javascript`, `ai` |
| `subdomains` | yes (except `ai`) | Targets for this scan                                                                                                    |
| `options`    | no                | For `subdomain` - defaults to `["passive", "active", "ai"]`                                                              |
| `ports`      | no                | For `port` - list of integers (default `[80,443,8080,8443]`)                                                             |
| `ports`      | no                | For `http` - list of strings (default `["80","443","8080","8443"]`)                                                      |
| `templates`  | no                | For `vulns` - Nuclei template IDs or `["ALL"]`                                                                           |

The scan type you request must be allowed by your subscription (see [Subscription plans](/docs/subscription-plans.md)).

**Response**

```json
{
  "success": true,
  "data": {
    "scan_type": "subdomain",
    "job_id": "rq-job-id",
    "scope_id": "uuid"
  }
}
```

Returns `403` if your plan doesn't allow this scan type, `403` if a quota is reached, `429` if your job queue is full, `503` if the scan type is temporarily disabled server-side.

***

### Scan results

#### List available logs

`GET /api/v1/scopes/{scope_id}/logs`

Returns the list of scan types for which a log is currently available on this scope. Requires the **Access scan logs** permission (Premium and above).

**Response**

```json
{
  "success": true,
  "data": [
    {
      "scan_type": "subdomain",
      "size": 24576,
      "updated_at": "2026-04-17T12:05:00+00:00"
    },
    {
      "scan_type": "vulns",
      "size": 4096,
      "updated_at": "2026-04-17T12:07:12+00:00"
    }
  ]
}
```

Only entries whose log file exists on disk are returned.

#### Get the log of a scan type

`GET /api/v1/scopes/{scope_id}/logs/{scan_type}`

Returns the raw text content of the log for that scan type, inside a JSON envelope. Requires the **Access scan logs** permission.

`scan_type` must be one of: `subdomain`, `port`, `http`, `url`, `screenshot`, `vulns`, `leaks`, `basic_auth`, `takeover`, `javascript`, `ai`.

**Response**

```json
{
  "success": true,
  "data": {
    "scan_type": "subdomain",
    "content": "[*] Starting subdomains scan on 1 domain(s):\n   * example.com\n\n[*] Passive mode enabled\n[+] Found 42 subdomains for example.com\n"
  }
}
```

Returns `404` with an explicit message if no log exists for this scan type on this scope yet, `400` if `scan_type` is not supported.

#### Get AI scan results

`GET /api/v1/scopes/{scope_id}/ai`

Returns the analysis produced by the most recent AI scan on this scope.

**Response**

```json
{
  "success": true,
  "data": { ... }
}
```

`data` is `null` if no AI scan has run on this scope yet.

***

### Credentials

Per-subdomain credentials (username, password, login URL, free-text notes) attached to a scope. Useful to keep recon data alongside the asset that owns it.

Reads are allowed to scope owners and collaborators. Writes (create / update / delete) are owner-only.

**Credential object**

```json
{
  "id": "uuid",
  "scope_id": "uuid",
  "subdomain": "admin.example.com",
  "label": "Default",
  "credential_type": "form",
  "username": "alice",
  "password": "s3cr3t",
  "login_url": "https://admin.example.com/login",
  "notes": "found in JS bundle, valid 2026-04",
  "created_at": "2026-04-27T15:00:00+00:00",
  "updated_at": "2026-04-27T15:00:00+00:00"
}
```

| Field             | Type   | Notes                                                                               |
| ----------------- | ------ | ----------------------------------------------------------------------------------- |
| `subdomain`       | string | Required on create. Lowercased server-side.                                         |
| `label`           | string | Defaults to `"Default"`. Max 200 chars.                                             |
| `credential_type` | string | One of `basic_auth`, `form`, `api_key`, `ssh`, `other`. Defaults to `form`.         |
| `username`        | string | Optional. Max 500 chars.                                                            |
| `password`        | string | Optional. Max 2000 chars. Stored in plaintext (consistent with the API keys table). |
| `login_url`       | string | Optional. Max 2000 chars.                                                           |
| `notes`           | string | Optional. Max 10000 chars.                                                          |

#### List credentials of a scope

`GET /api/v1/scopes/{scope_id}/credentials`

Optional query parameter `subdomain` filters to a single subdomain (case-insensitive).

```bash
curl -H "Authorization: Bearer $BR_TOKEN" \
  "https://app.bugrecon.me/api/v1/scopes/$SCOPE_ID/credentials?subdomain=admin.example.com"
```

**Response**

```json
{
  "success": true,
  "data": [ { /* credential object */ }, ... ]
}
```

Returns `404` if the scope is not accessible to you.

#### Create a credential

`POST /api/v1/scopes/{scope_id}/credentials`

```json
{
  "subdomain": "admin.example.com",
  "label": "Stage admin",
  "credential_type": "form",
  "username": "alice",
  "password": "s3cr3t",
  "login_url": "https://admin.example.com/login",
  "notes": "found in JS bundle"
}
```

`subdomain` is required. All other fields are optional.

**Response** - `200 OK` with the full credential object inside `data`.

Errors:

* `400` - invalid `credential_type`, missing `subdomain`, or any field exceeds its length cap.
* `404` - the scope does not exist or you do not own it.

#### Get a credential

`GET /api/v1/credentials/{credential_id}`

Returns the credential object inside `data`. `404` if the credential does not exist or its scope is not accessible to you.

#### Update a credential

`PUT /api/v1/credentials/{credential_id}`

Partial update - only the fields present in the body are modified. To clear a field, send it explicitly with an empty string or `null`.

```json
{
  "password": "rotated",
  "notes": "rotated 2026-04-27"
}
```

**Response** - `200 OK` with the updated credential object inside `data`.

Errors:

* `400` - invalid `credential_type` or any field exceeds its length cap.
* `404` - credential does not exist or its scope is not owned by you.

#### Delete a credential

`DELETE /api/v1/credentials/{credential_id}`

**Response**

```json
{ "success": true, "message": "Credential deleted" }
```

Returns `404` if the credential does not exist or its scope is not owned by you.

***

### Recent added domains

`GET /api/v1/domains/recent/grouped`

Returns all domains **added** across your projects and scopes over a recent period, grouped by project then scope. Useful for monitoring new attack surface without polling each scope individually.

**Access**: includes scopes you own and scopes shared with you as collaborator (read\_only or writer).

**Query parameters**

| Parameter | Type         | Default | Description                                                                                 |
| --------- | ------------ | ------- | ------------------------------------------------------------------------------------------- |
| `date`    | `YYYY-MM-DD` | —       | Return domains added on this specific calendar day (UTC). When provided, `days` is ignored. |
| `days`    | integer 1–30 | `3`     | Look-back window in days from now. Ignored if `date` is set.                                |

**Response**

```json
{
  "success": true,
  "filter": {
    "date": null,
    "days": 3
  },
  "projects": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "My Bug Bounty Program",
      "scopes": [
        {
          "id": "661f9511-f3ac-52e5-b827-557766551111",
          "target_domain": "example.com",
          "domains": [
            "api.example.com",
            "staging.example.com"
          ]
        }
      ]
    }
  ]
}
```

When `date` is used:

```json
{
  "success": true,
  "filter": {
    "date": "2026-06-15",
    "days": null
  },
  "projects": [ ... ]
}
```

**Examples**

```bash
# Last 3 days (default)
curl -H "Authorization: Bearer br_YOUR_TOKEN" \
  https://YOUR_INSTANCE/api/v1/domains/recent/grouped

# Last 7 days
curl -H "Authorization: Bearer br_YOUR_TOKEN" \
  "https://YOUR_INSTANCE/api/v1/domains/recent/grouped?days=7"

# Specific date
curl -H "Authorization: Bearer br_YOUR_TOKEN" \
  "https://YOUR_INSTANCE/api/v1/domains/recent/grouped?date=2026-06-15"
```

**Error responses**

| Status | Reason                                       |
| ------ | -------------------------------------------- |
| `400`  | Invalid `date` format (must be `YYYY-MM-DD`) |
| `400`  | `days` out of range (must be 1–30)           |
| `401`  | Missing or invalid token                     |

***

### Quota

`GET /api/v1/quota`

Returns your current quota usage for the active billing period (or calendar month if you are on Free).

**Response**

```json
{
  "success": true,
  "data": {
    "scan_quota": {
      "limit": 300,
      "used": 12,
      "remaining": 288,
      "is_unlimited": false,
      "period_start": "2026-04-01T00:00:00+00:00",
      "period_end": "2026-05-01T00:00:00+00:00"
    },
    "subdomain_scan_quota": {
      "limit": 15,
      "used": 3,
      "in_progress_domains": 0,
      "remaining": 12,
      "is_unlimited": false,
      "period_start": "2026-04-01T00:00:00+00:00",
      "period_end": "2026-05-01T00:00:00+00:00"
    },
    "certstream_quota": {
      "limit": 15,
      "used": 5,
      "remaining": 10,
      "is_unlimited": false
    }
  }
}
```

A `limit` of `null` means the quota is unlimited.

***

### Recent domains

`GET /api/v1/domains/recent`

Lists subdomains **added** across your scopes within the last `days` days, newest first. Sourced from the per-scope domain-history log (the same data shown in a scope's *Domain history*), so it reflects additions from every source: manual edits, scans, CertStream auto-add, and integration syncs.

Scoped to projects **you own** (`projects.user_id`); scopes you only collaborate on are not included.

**Query parameters**

| Param  | Type | Default | Description                                                                                                                    |
| ------ | ---- | ------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `days` | int  | `7`     | Lookback window in days. Clamped to `1`–`365`. A value below `1` returns `400`. A non-numeric value falls back to the default. |

**Response**

```json
{
  "success": true,
  "data": {
    "days": 7,
    "count": 2,
    "domains": [
      {
        "domain": "api.example.com",
        "scope_id": "uuid",
        "project_id": "uuid",
        "target_domain": "example.com",
        "source": "certstream",
        "added_at": "2026-06-22T10:04:11+00:00"
      }
    ]
  }
}
```

| Field                     | Description                                                                                                               |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `days`                    | The effective lookback window applied (after clamping).                                                                   |
| `count`                   | Number of domains returned.                                                                                               |
| `domains[].domain`        | The subdomain that was added.                                                                                             |
| `domains[].scope_id`      | UUID of the scope it was added to.                                                                                        |
| `domains[].project_id`    | UUID of the parent project.                                                                                               |
| `domains[].target_domain` | The scope's primary target.                                                                                               |
| `domains[].source`        | Origin recorded with the addition (e.g. a scan type, `certstream`, an integration platform), or `null` when not recorded. |
| `domains[].added_at`      | When the domain was added (ISO-8601).                                                                                     |

Results are newest first and capped at **1000** rows.

```bash
curl -H "Authorization: Bearer $BR_TOKEN" \
  "https://api.bugrecon.me/api/v1/domains/recent?days=30"
```

***

### CertStream domains

Requires the **CertStream** permission (Basic and above).

#### List monitored domains

`GET /api/v1/certstream/domains`

**Query parameters**

* `search` (optional) - substring filter.

**Response**

```json
{
  "success": true,
  "data": [
    {
      "id": "uuid",
      "domain": "example.com",
      "is_active": true,
      "created_at": "2026-04-17T12:00:00+00:00"
    }
  ]
}
```

#### Add a monitored domain

`POST /api/v1/certstream/domains`

**Request body**

```json
{
  "domain": "example.com"
}
```

`*.` prefix is accepted and stripped automatically. Returns `403` if your CertStream quota is reached.

#### Rename a monitored domain

`PUT /api/v1/certstream/domains/{domain_id}`

**Request body**

```json
{
  "domain": "new.example.com"
}
```

Returns `409` if the new domain already exists.

#### Activate or deactivate a domain

`PATCH /api/v1/certstream/domains/{domain_id}/status`

**Request body**

```json
{
  "is_active": true
}
```

#### Delete a monitored domain

`DELETE /api/v1/certstream/domains/{domain_id}`

Removes the domain and its associated detections.

***

### URLs Management

Manage URLs discovered during scans (web crawlers, JavaScript scanner, HTTP probing).

See [URLs Management API](https://github.com/elweth-sec/BugRecon/tree/dev/documentation/gitbook/api-endpoints-urls.md) for full endpoint documentation.

***

## Example workflow

Create a project, add a scope, run a subdomain scan, and pull the results:

```bash
TOKEN="br_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
BASE="https://api.bugrecon.me/api/v1"

# 1. Create a project
PROJECT=$(curl -s -X POST "$BASE/projects" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Example", "description": "Test project"}')
PROJECT_ID=$(echo "$PROJECT" | jq -r '.data.id')

# 2. Create a scope
SCOPE=$(curl -s -X POST "$BASE/projects/$PROJECT_ID/scopes" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"target_domain": "example.com", "subdomains": ["*.example.com"]}')
SCOPE_ID=$(echo "$SCOPE" | jq -r '.data.id')

# 3. Start a subdomain scan
curl -s -X POST "$BASE/scopes/$SCOPE_ID/scan" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"scan_type": "subdomain", "subdomains": ["*.example.com"]}'

# 4. Poll until completed
while true; do
  STATUS=$(curl -s "$BASE/scopes/$SCOPE_ID" \
    -H "Authorization: Bearer $TOKEN" | jq -r '.data.status')
  echo "status: $STATUS"
  [ "$STATUS" = "completed" ] && break
  [ "$STATUS" = "failed" ] && break
  sleep 10
done

# 5. Read the results
curl -s "$BASE/scopes/$SCOPE_ID" -H "Authorization: Bearer $TOKEN"
```

***

## Good practices

* **Store tokens in a secret manager** (CI variables, Vault, 1Password, etc.), never in plain files or repositories.
* **Use one token per integration** so you can revoke individual ones without breaking everything.
* **Rotate tokens regularly**, especially for long-running automations.
* **Poll scan status** rather than firing concurrent scans on the same scope.
* **Check your quota** (`GET /api/v1/quota`) before scheduling large batches to avoid `403` responses mid-run.
* **Watch the rate-limit headers** returned on every response and back off when they approach zero.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://bugrecon.gitbook.io/docs/features-list/api.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
