# Projects API

Indexing, monitoring, verification, and search-engine insights

Website indexing and monitoring projects. Manage domain ownership, sitemap and URL inventory, IndexNow submissions, credit-funded URL inspections, daily statistics, and Google Search Console and Bing connections.

## Available Endpoints

| Method   | Endpoint                                                                                                                                                            | Description                                |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
| `GET`    | [`/api/user/organizations/{organizationId}/projects`](#get-apiuserorganizations{organizationId}projects)                                                            | List organization projects                 |
| `POST`   | [`/api/user/organizations/{organizationId}/projects`](#post-apiuserorganizations{organizationId}projects)                                                           | Create a new project                       |
| `GET`    | [`/api/user/organizations/{organizationId}/projects/{id}/activation-preview`](#get-apiuserorganizations{organizationId}projects{id}activationpreview)               | Preview scheduled inspection activation    |
| `PUT`    | [`/api/user/organizations/{organizationId}/projects/{id}/automation`](#put-apiuserorganizations{organizationId}projects{id}automation)                              | Configure scheduled inspections            |
| `GET`    | [`/api/user/organizations/{organizationId}/projects/{id}`](#get-apiuserorganizations{organizationId}projects{id})                                                   | Get a specific project                     |
| `DELETE` | [`/api/user/organizations/{organizationId}/projects/{id}`](#delete-apiuserorganizations{organizationId}projects{id})                                                | Delete a project                           |
| `PATCH`  | [`/api/user/organizations/{organizationId}/projects/{id}`](#patch-apiuserorganizations{organizationId}projects{id})                                                 | Update a project                           |
| `PATCH`  | [`/api/user/organizations/{organizationId}/projects/{id}/status`](#patch-apiuserorganizations{organizationId}projects{id}status)                                    | Pause or resume a project                  |
| `GET`    | [`/api/user/organizations/{organizationId}/projects/{id}/urls`](#get-apiuserorganizations{organizationId}projects{id}urls)                                          | List tracked URLs for a project            |
| `POST`   | [`/api/user/organizations/{organizationId}/projects/{id}/urls`](#post-apiuserorganizations{organizationId}projects{id}urls)                                         | Manually admit URLs                        |
| `GET`    | [`/api/user/organizations/{organizationId}/projects/{id}/urls/{urlId}`](#get-apiuserorganizations{organizationId}projects{id}urls{urlId})                           | Get a tracked URL                          |
| `GET`    | [`/api/user/organizations/{organizationId}/projects/{id}/imports`](#get-apiuserorganizations{organizationId}projects{id}imports)                                    | List URL import history                    |
| `POST`   | [`/api/user/organizations/{organizationId}/projects/{id}/imports`](#post-apiuserorganizations{organizationId}projects{id}imports)                                   | Upload a CSV/txt URL list for preview      |
| `GET`    | [`/api/user/organizations/{organizationId}/projects/{id}/imports/{importId}`](#get-apiuserorganizations{organizationId}projects{id}imports{importId})               | Get import preview / progress              |
| `POST`   | [`/api/user/organizations/{organizationId}/projects/{id}/imports/{importId}/commit`](#post-apiuserorganizations{organizationId}projects{id}imports{importId}commit) | Commit a previewed import                  |
| `GET`    | [`/api/user/organizations/{organizationId}/projects/{id}/sitemaps`](#get-apiuserorganizations{organizationId}projects{id}sitemaps)                                  | List discovered sitemaps for a project     |
| `POST`   | [`/api/user/organizations/{organizationId}/projects/{id}/sync`](#post-apiuserorganizations{organizationId}projects{id}sync)                                         | Manually sync project sitemaps             |
| `POST`   | [`/api/user/organizations/{organizationId}/projects/{id}/availability/probe`](#post-apiuserorganizations{organizationId}projects{id}availabilityprobe)              | Probe robots / llms / sitemap availability |
| `GET`    | [`/api/user/organizations/{organizationId}/projects/{id}/verification`](#get-apiuserorganizations{organizationId}projects{id}verification)                          | Get project verification status            |
| `POST`   | [`/api/user/organizations/{organizationId}/projects/{id}/dns-token`](#post-apiuserorganizations{organizationId}projects{id}dnstoken)                                | Generate or rotate DNS verification token  |
| `POST`   | [`/api/user/organizations/{organizationId}/projects/{id}/verify-dns`](#post-apiuserorganizations{organizationId}projects{id}verifydns)                              | Verify DNS TXT ownership                   |
| `POST`   | [`/api/user/organizations/{organizationId}/projects/{id}/indexnow-key`](#post-apiuserorganizations{organizationId}projects{id}indexnowkey)                          | Generate or set IndexNow key               |
| `POST`   | [`/api/user/organizations/{organizationId}/projects/{id}/verify-key`](#post-apiuserorganizations{organizationId}projects{id}verifykey)                              | Verify IndexNow key file                   |
| `GET`    | [`/api/user/organizations/{organizationId}/projects/{id}/submissions`](#get-apiuserorganizations{organizationId}projects{id}submissions)                            | List IndexNow submission history           |
| `GET`    | [`/api/user/organizations/{organizationId}/projects/{id}/issues`](#get-apiuserorganizations{organizationId}projects{id}issues)                                      | List issue groups for a project            |
| `GET`    | [`/api/user/organizations/{organizationId}/projects/{id}/issues/{issueId}`](#get-apiuserorganizations{organizationId}projects{id}issues{issueId})                   | Get an issue group                         |
| `PATCH`  | [`/api/user/organizations/{organizationId}/projects/{id}/issues/{issueId}`](#patch-apiuserorganizations{organizationId}projects{id}issues{issueId})                 | Update issue group status                  |
| `GET`    | [`/api/user/organizations/{organizationId}/projects/{id}/issues/{issueId}/urls`](#get-apiuserorganizations{organizationId}projects{id}issues{issueId}urls)          | List memberships for an issue group        |
| `GET`    | [`/api/user/organizations/{organizationId}/projects/{id}/stats`](#get-apiuserorganizations{organizationId}projects{id}stats)                                        | Get live website stats                     |
| `GET`    | [`/api/user/organizations/{organizationId}/projects/{id}/snapshots`](#get-apiuserorganizations{organizationId}projects{id}snapshots)                                | List daily website snapshots               |
| `GET`    | [`/api/user/organizations/{organizationId}/projects/{id}/gsc/oauth-url`](#get-apiuserorganizations{organizationId}projects{id}gscoauthurl)                          | Get GSC OAuth authorize URL                |
| `POST`   | [`/api/user/organizations/{organizationId}/projects/{id}/gsc/callback`](#post-apiuserorganizations{organizationId}projects{id}gsccallback)                          | Complete GSC OAuth and connect property    |
| `DELETE` | [`/api/user/organizations/{organizationId}/projects/{id}/gsc`](#delete-apiuserorganizations{organizationId}projects{id}gsc)                                         | Disconnect GSC                             |
| `POST`   | [`/api/user/organizations/{organizationId}/projects/{id}/bing`](#post-apiuserorganizations{organizationId}projects{id}bing)                                         | Connect Bing Webmaster API key             |
| `DELETE` | [`/api/user/organizations/{organizationId}/projects/{id}/bing`](#delete-apiuserorganizations{organizationId}projects{id}bing)                                       | Disconnect Bing                            |

## Endpoints

### GET /api/user/organizations/{organizationId}/projects

List organization projects

Get a paginated list of projects owned by the organization.

**Auth:** bearer · **Scopes:** `projects:read`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Query parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `offset` | number \| null | no | Number of items to skip | `0` |
| `limit` | number | no | Number of items to return (max 100) | `20` |
| `q` | string | no | Search query |  |

#### Request example

**curl**

```bash
curl -X GET "https://api.indexhog.com/api/user/organizations/{organizationId}/projects" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects", {
method: "GET",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Projects retrieved successfully

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Items fetched successfully",
"data": [
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "organizationId": "550e8400-e29b-41d4-a716-446655440001",
    "createdAt": "2024-01-01T00:00:00.000Z",
    "updatedAt": "2024-01-15T10:30:00.000Z",
    "createdByUserId": "550e8400-e29b-41d4-a716-446655440002",
    "name": "acme.com",
    "domain": "acme.com",
    "sitemapUrl": "https://acme.com/sitemap.xml",
    "status": "active",
    "inspectionCadence": "hourly",
    "automatedInspectionsEnabled": true,
    "verified": false,
    "indexNowKey": "a1b2c3d4e5f6",
    "indexNowKeySource": "generated",
    "indexNowKeyVerifiedAt": null,
    "dnsVerificationToken": null,
    "dnsVerifiedAt": null,
    "gscPropertyUrl": null,
    "gscConnectedAt": null,
    "bingConnectedAt": null,
    "robotsTxtPresent": null,
    "llmsTxtPresent": null,
    "lastAvailabilityCheckAt": null,
    "lastSitemapSyncAt": null,
    "lastEngineSyncAt": null
  }
],
"meta": {
  "total": 100,
  "offset": 0,
  "limit": 10,
  "hasMore": true
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### POST /api/user/organizations/{organizationId}/projects

Create a new project

Create an active project for a bare domain. Consumes a slot. Returns 409 on duplicate live domain.

**Auth:** bearer · **Scopes:** `projects:write`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Body parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `domain` | string | yes | Bare host (trim + lowercase). Immutable after create. | `acme.com` |
| `name` | string | no | Display name; defaults to domain | `Acme Site` |
| `sitemapUrl` | string | no | Defaults to https://{domain}/sitemap.xml | `https://acme.com/sitemap.xml` |

#### Request example

**curl**

```bash
curl -X POST "https://api.indexhog.com/api/user/organizations/{organizationId}/projects" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"domain":"acme.com","name":"Acme Site","sitemapUrl":"https://acme.com/sitemap.xml"}'
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects", {
method: "POST",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
body: JSON.stringify({
  "domain": "acme.com",
  "name": "Acme Site",
  "sitemapUrl": "https://acme.com/sitemap.xml"
}),
});

const data = await response.json();
console.log(data);
```

#### Response `201` — Project created successfully

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "organizationId": "550e8400-e29b-41d4-a716-446655440001",
  "createdAt": "2024-01-01T00:00:00.000Z",
  "updatedAt": "2024-01-15T10:30:00.000Z",
  "createdByUserId": "550e8400-e29b-41d4-a716-446655440002",
  "name": "acme.com",
  "domain": "acme.com",
  "sitemapUrl": "https://acme.com/sitemap.xml",
  "status": "active",
  "inspectionCadence": "hourly",
  "automatedInspectionsEnabled": true,
  "verified": false,
  "indexNowKey": "a1b2c3d4e5f6",
  "indexNowKeySource": "generated",
  "indexNowKeyVerifiedAt": null,
  "dnsVerificationToken": null,
  "dnsVerifiedAt": null,
  "gscPropertyUrl": null,
  "gscConnectedAt": null,
  "bingConnectedAt": null,
  "robotsTxtPresent": null,
  "llmsTxtPresent": null,
  "lastAvailabilityCheckAt": null,
  "lastSitemapSyncAt": null,
  "lastEngineSyncAt": null
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### GET /api/user/organizations/{organizationId}/projects/{id}/activation-preview

Preview scheduled inspection activation

Returns the current inventory and credits without consuming credits.

**Auth:** bearer · **Scopes:** `projects:read`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Request example

**curl**

```bash
curl -X GET "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/activation-preview" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/activation-preview", {
method: "GET",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Activation preview fetched

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "verified": true,
  "trackedUrlCount": 0,
  "creditBalance": 0,
  "creditsForFullCycle": 0,
  "urlsInspectableNow": 0,
  "cadence": "hourly",
  "automatedInspectionsEnabled": true
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### PUT /api/user/organizations/{organizationId}/projects/{id}/automation

Configure scheduled inspections

Enabling requires verified ownership, at least one tracked URL, and available credits.

**Auth:** bearer · **Scopes:** `projects:write`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Body parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `enabled` | boolean | yes |  |  |
| `cadence` | InspectionCadence | yes |  |  |

#### Request example

**curl**

```bash
curl -X PUT "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/automation" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"enabled":true,"cadence":"hourly"}'
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/automation", {
method: "PUT",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
body: JSON.stringify({
  "enabled": true,
  "cadence": "hourly"
}),
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Project automation updated

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "organizationId": "550e8400-e29b-41d4-a716-446655440001",
  "createdAt": "2024-01-01T00:00:00.000Z",
  "updatedAt": "2024-01-15T10:30:00.000Z",
  "createdByUserId": "550e8400-e29b-41d4-a716-446655440002",
  "name": "acme.com",
  "domain": "acme.com",
  "sitemapUrl": "https://acme.com/sitemap.xml",
  "status": "active",
  "inspectionCadence": "hourly",
  "automatedInspectionsEnabled": true,
  "verified": false,
  "indexNowKey": "a1b2c3d4e5f6",
  "indexNowKeySource": "generated",
  "indexNowKeyVerifiedAt": null,
  "dnsVerificationToken": null,
  "dnsVerifiedAt": null,
  "gscPropertyUrl": null,
  "gscConnectedAt": null,
  "bingConnectedAt": null,
  "robotsTxtPresent": null,
  "llmsTxtPresent": null,
  "lastAvailabilityCheckAt": null,
  "lastSitemapSyncAt": null,
  "lastEngineSyncAt": null
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### GET /api/user/organizations/{organizationId}/projects/{id}

Get a specific project

Get details of a specific project owned by the organization.

**Auth:** bearer · **Scopes:** `projects:read`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Request example

**curl**

```bash
curl -X GET "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}", {
method: "GET",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Project retrieved successfully

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "organizationId": "550e8400-e29b-41d4-a716-446655440001",
  "createdAt": "2024-01-01T00:00:00.000Z",
  "updatedAt": "2024-01-15T10:30:00.000Z",
  "createdByUserId": "550e8400-e29b-41d4-a716-446655440002",
  "name": "acme.com",
  "domain": "acme.com",
  "sitemapUrl": "https://acme.com/sitemap.xml",
  "status": "active",
  "inspectionCadence": "hourly",
  "automatedInspectionsEnabled": true,
  "verified": false,
  "indexNowKey": "a1b2c3d4e5f6",
  "indexNowKeySource": "generated",
  "indexNowKeyVerifiedAt": null,
  "dnsVerificationToken": null,
  "dnsVerifiedAt": null,
  "gscPropertyUrl": null,
  "gscConnectedAt": null,
  "bingConnectedAt": null,
  "robotsTxtPresent": null,
  "llmsTxtPresent": null,
  "lastAvailabilityCheckAt": null,
  "lastSitemapSyncAt": null,
  "lastEngineSyncAt": null
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### DELETE /api/user/organizations/{organizationId}/projects/{id}

Delete a project

Soft delete a project (paused + deletedAt). Idempotent.

**Auth:** bearer · **Scopes:** `projects:write`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Request example

**curl**

```bash
curl -X DELETE "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}", {
method: "DELETE",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Project deleted successfully

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "organizationId": "550e8400-e29b-41d4-a716-446655440001",
  "createdAt": "2024-01-01T00:00:00.000Z",
  "updatedAt": "2024-01-15T10:30:00.000Z",
  "createdByUserId": "550e8400-e29b-41d4-a716-446655440002",
  "name": "acme.com",
  "domain": "acme.com",
  "sitemapUrl": "https://acme.com/sitemap.xml",
  "status": "active",
  "inspectionCadence": "hourly",
  "automatedInspectionsEnabled": true,
  "verified": false,
  "indexNowKey": "a1b2c3d4e5f6",
  "indexNowKeySource": "generated",
  "indexNowKeyVerifiedAt": null,
  "dnsVerificationToken": null,
  "dnsVerifiedAt": null,
  "gscPropertyUrl": null,
  "gscConnectedAt": null,
  "bingConnectedAt": null,
  "robotsTxtPresent": null,
  "llmsTxtPresent": null,
  "lastAvailabilityCheckAt": null,
  "lastSitemapSyncAt": null,
  "lastEngineSyncAt": null
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### PATCH /api/user/organizations/{organizationId}/projects/{id}

Update a project

Update name and/or sitemapUrl. Domain is immutable.

**Auth:** bearer · **Scopes:** `projects:write`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Body parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `name` | string | no | Project display name | `Acme Site` |
| `sitemapUrl` | string | no | Sitemap URL (domain is immutable) | `https://acme.com/sitemap.xml` |

#### Request example

**curl**

```bash
curl -X PATCH "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"name":"Acme Site","sitemapUrl":"https://acme.com/sitemap.xml"}'
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}", {
method: "PATCH",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
body: JSON.stringify({
  "name": "Acme Site",
  "sitemapUrl": "https://acme.com/sitemap.xml"
}),
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Project updated successfully

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "organizationId": "550e8400-e29b-41d4-a716-446655440001",
  "createdAt": "2024-01-01T00:00:00.000Z",
  "updatedAt": "2024-01-15T10:30:00.000Z",
  "createdByUserId": "550e8400-e29b-41d4-a716-446655440002",
  "name": "acme.com",
  "domain": "acme.com",
  "sitemapUrl": "https://acme.com/sitemap.xml",
  "status": "active",
  "inspectionCadence": "hourly",
  "automatedInspectionsEnabled": true,
  "verified": false,
  "indexNowKey": "a1b2c3d4e5f6",
  "indexNowKeySource": "generated",
  "indexNowKeyVerifiedAt": null,
  "dnsVerificationToken": null,
  "dnsVerifiedAt": null,
  "gscPropertyUrl": null,
  "gscConnectedAt": null,
  "bingConnectedAt": null,
  "robotsTxtPresent": null,
  "llmsTxtPresent": null,
  "lastAvailabilityCheckAt": null,
  "lastSitemapSyncAt": null,
  "lastEngineSyncAt": null
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### PATCH /api/user/organizations/{organizationId}/projects/{id}/status

Pause or resume a project

Set status to active or paused. Pausing does not free workspace capacity.

**Auth:** bearer · **Scopes:** `projects:write`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Body parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `status` | ProjectStatus | yes |  |  |

#### Request example

**curl**

```bash
curl -X PATCH "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/status" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"status":"paused"}'
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/status", {
method: "PATCH",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
body: JSON.stringify({
  "status": "paused"
}),
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Project status updated successfully

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "organizationId": "550e8400-e29b-41d4-a716-446655440001",
  "createdAt": "2024-01-01T00:00:00.000Z",
  "updatedAt": "2024-01-15T10:30:00.000Z",
  "createdByUserId": "550e8400-e29b-41d4-a716-446655440002",
  "name": "acme.com",
  "domain": "acme.com",
  "sitemapUrl": "https://acme.com/sitemap.xml",
  "status": "active",
  "inspectionCadence": "hourly",
  "automatedInspectionsEnabled": true,
  "verified": false,
  "indexNowKey": "a1b2c3d4e5f6",
  "indexNowKeySource": "generated",
  "indexNowKeyVerifiedAt": null,
  "dnsVerificationToken": null,
  "dnsVerifiedAt": null,
  "gscPropertyUrl": null,
  "gscConnectedAt": null,
  "bingConnectedAt": null,
  "robotsTxtPresent": null,
  "llmsTxtPresent": null,
  "lastAvailabilityCheckAt": null,
  "lastSitemapSyncAt": null,
  "lastEngineSyncAt": null
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### GET /api/user/organizations/{organizationId}/projects/{id}/urls

List tracked URLs for a project

Paginated inventory with optional health/indexability/engine/source/stale filters.

**Auth:** bearer · **Scopes:** `projects:read`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Query parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `offset` | number \| null | no | Number of items to skip | `0` |
| `limit` | number | no | Number of items to return (max 100) | `20` |
| `healthState` | HealthState | no | Filter by health state |  |
| `indexability` | Indexability | no | Filter by indexability |  |
| `googleState` | GoogleState | no | Filter by Google state |  |
| `bingState` | BingState | no | Filter by Bing state |  |
| `source` | UrlSource | no | Filter by discovery source |  |
| `hasIssues` | string | no | Filter URLs that currently belong to an open issue group |  |
| `isStale` | string | no | Filter URLs with staleAt set (missing from sitemap) |  |
| `disagreement` | Disagreement | no | Filter audit-vs-engine disagreements without collapsing facts. indexable_not_in_engine = our audit indexable + Google/Bing non-indexed; nonindexable_but_indexed = our audit non-indexable + an engine reports indexed; any = either. |  |

#### Request example

**curl**

```bash
curl -X GET "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/urls" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/urls", {
method: "GET",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Tracked URLs retrieved successfully

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Items fetched successfully",
"data": [
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "organizationId": "550e8400-e29b-41d4-a716-446655440000",
    "projectId": "550e8400-e29b-41d4-a716-446655440000",
    "url": "https://acme.com/about",
    "source": "sitemap",
    "discoveredAt": "string",
    "firstSeenInSitemapAt": "string",
    "lastSeenInSitemapAt": "string",
    "lastmod": "string",
    "staleAt": "string",
    "healthState": "unknown",
    "httpStatus": 0,
    "redirectTarget": "string",
    "redirectHops": 0,
    "healthError": "string",
    "lastHealthCheckAt": "string",
    "indexability": "unknown",
    "canonicalUrl": "string",
    "metaRobots": "string",
    "xRobotsTag": "string",
    "lastAuditAt": "string",
    "lastInspectionAttemptAt": "string",
    "lastInspectionSuccessAt": "string",
    "lastInspectionFailureAt": "string",
    "lastInspectionFailureCode": "string",
    "consecutiveInspectionFailures": 0,
    "lastIndexNowSubmittedAt": "string",
    "indexNowAttempts": 0,
    "googleState": "unknown",
    "bingState": "unknown",
    "indexedAt": "string",
    "lastEngineSyncAt": "string",
    "nextInspectionAt": "string",
    "createdAt": "string",
    "updatedAt": "string"
  }
],
"meta": {
  "total": 100,
  "offset": 0,
  "limit": 10,
  "hasMore": true
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### POST /api/user/organizations/{organizationId}/projects/{id}/urls

Manually admit URLs

Normalize, same-host filter, dedup, and insert without consuming credits. One invalid URL rejects the batch (400).

**Auth:** bearer · **Scopes:** `projects:write`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Body parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `urls` | string[] | yes | Raw URLs to admit (1–1000). Normalized before dedup. | `https://acme.com/about,https://acme.com/pricing` |

#### Request example

**curl**

```bash
curl -X POST "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/urls" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"urls":["https://acme.com/about","https://acme.com/pricing"]}'
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/urls", {
method: "POST",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
body: JSON.stringify({
  "urls": [
    "https://acme.com/about",
    "https://acme.com/pricing"
  ]
}),
});

const data = await response.json();
console.log(data);
```

#### Response `200` — URLs admitted successfully

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "inserted": 3,
  "skipped": 2,
  "skippedOffHost": [
    {
      "url": "https://other.com/page",
      "reason": "off_host"
    }
  ]
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### GET /api/user/organizations/{organizationId}/projects/{id}/urls/{urlId}

Get a tracked URL

Detail for one live tracked URL in the project.

**Auth:** bearer · **Scopes:** `projects:read`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |
| `urlId` | string | yes | Tracked URL ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Request example

**curl**

```bash
curl -X GET "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/urls/{urlId}" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/urls/{urlId}", {
method: "GET",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Tracked URL retrieved successfully

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "organizationId": "550e8400-e29b-41d4-a716-446655440000",
  "projectId": "550e8400-e29b-41d4-a716-446655440000",
  "url": "https://acme.com/about",
  "source": "sitemap",
  "discoveredAt": "string",
  "firstSeenInSitemapAt": "string",
  "lastSeenInSitemapAt": "string",
  "lastmod": "string",
  "staleAt": "string",
  "healthState": "unknown",
  "httpStatus": 0,
  "redirectTarget": "string",
  "redirectHops": 0,
  "healthError": "string",
  "lastHealthCheckAt": "string",
  "indexability": "unknown",
  "canonicalUrl": "string",
  "metaRobots": "string",
  "xRobotsTag": "string",
  "lastAuditAt": "string",
  "lastInspectionAttemptAt": "string",
  "lastInspectionSuccessAt": "string",
  "lastInspectionFailureAt": "string",
  "lastInspectionFailureCode": "string",
  "consecutiveInspectionFailures": 0,
  "lastIndexNowSubmittedAt": "string",
  "indexNowAttempts": 0,
  "googleState": "unknown",
  "bingState": "unknown",
  "indexedAt": "string",
  "lastEngineSyncAt": "string",
  "nextInspectionAt": "string",
  "createdAt": "string",
  "updatedAt": "string"
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### GET /api/user/organizations/{organizationId}/projects/{id}/imports

List URL import history

Paginated import previews and commits for a project.

**Auth:** bearer · **Scopes:** `projects:read`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Query parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `offset` | number \| null | no | Number of items to skip | `0` |
| `limit` | number | no | Number of items to return (max 100) | `20` |

#### Request example

**curl**

```bash
curl -X GET "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/imports" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/imports", {
method: "GET",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Imports retrieved successfully

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Items fetched successfully",
"data": [
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "organizationId": "550e8400-e29b-41d4-a716-446655440000",
    "projectId": "550e8400-e29b-41d4-a716-446655440000",
    "status": "pending",
    "fileName": "urls.csv",
    "fileSizeBytes": 0,
    "contentSha256": "string",
    "headerDetected": true,
    "totalRows": 0,
    "validRows": 0,
    "invalidRows": 0,
    "duplicateInFileRows": 0,
    "alreadyTrackedRows": 0,
    "netNewRows": 0,
    "rowErrors": [
      {
        "line": 12,
        "reason": "URL is malformed",
        "value": "not-a-url"
      }
    ],
    "processedRows": 0,
    "insertedRows": 0,
    "skippedRows": 0,
    "error": "string",
    "expiresAt": "string",
    "committedAt": "string",
    "completedAt": "string",
    "createdAt": "string",
    "updatedAt": "string"
  }
],
"meta": {
  "total": 100,
  "offset": 0,
  "limit": 10,
  "hasMore": true
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### POST /api/user/organizations/{organizationId}/projects/{id}/imports

Upload a CSV/txt URL list for preview

Stores the file and builds a preview only — zero tracked-URL writes and zero credit debit. Limits: 50,000 rows / 5 MiB. CSV first column; plain text one URL per line; optional header auto-detected.

**Auth:** bearer · **Scopes:** `projects:write`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Request example

**curl**

```bash
curl -X POST "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/imports" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/imports", {
method: "POST",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `201` — Import preview created

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "organizationId": "550e8400-e29b-41d4-a716-446655440000",
  "projectId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "pending",
  "fileName": "urls.csv",
  "fileSizeBytes": 0,
  "contentSha256": "string",
  "headerDetected": true,
  "totalRows": 0,
  "validRows": 0,
  "invalidRows": 0,
  "duplicateInFileRows": 0,
  "alreadyTrackedRows": 0,
  "netNewRows": 0,
  "rowErrors": [
    {
      "line": 12,
      "reason": "URL is malformed",
      "value": "not-a-url"
    }
  ],
  "processedRows": 0,
  "insertedRows": 0,
  "skippedRows": 0,
  "error": "string",
  "expiresAt": "string",
  "committedAt": "string",
  "completedAt": "string",
  "createdAt": "string",
  "updatedAt": "string"
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### GET /api/user/organizations/{organizationId}/projects/{id}/imports/{importId}

Get import preview / progress

Preview totals, per-row errors, and commit progress/failure.

**Auth:** bearer · **Scopes:** `projects:read`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |
| `importId` | string | yes | URL import ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Request example

**curl**

```bash
curl -X GET "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/imports/{importId}" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/imports/{importId}", {
method: "GET",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Import retrieved successfully

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "organizationId": "550e8400-e29b-41d4-a716-446655440000",
  "projectId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "pending",
  "fileName": "urls.csv",
  "fileSizeBytes": 0,
  "contentSha256": "string",
  "headerDetected": true,
  "totalRows": 0,
  "validRows": 0,
  "invalidRows": 0,
  "duplicateInFileRows": 0,
  "alreadyTrackedRows": 0,
  "netNewRows": 0,
  "rowErrors": [
    {
      "line": 12,
      "reason": "URL is malformed",
      "value": "not-a-url"
    }
  ],
  "processedRows": 0,
  "insertedRows": 0,
  "skippedRows": 0,
  "error": "string",
  "expiresAt": "string",
  "committedAt": "string",
  "completedAt": "string",
  "createdAt": "string",
  "updatedAt": "string"
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### POST /api/user/organizations/{organizationId}/projects/{id}/imports/{importId}/commit

Commit a previewed import

Requires explicit confirm:true. Rechecks net-new rows, then enqueues a background commit chunked at 1,000. Importing URLs does not consume credits. Expired/stale preview → 409.

**Auth:** bearer · **Scopes:** `projects:write`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |
| `importId` | string | yes | URL import ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Body parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `confirm` | boolean | yes | Explicit consent to import the valid rows from this preview | `true` |

#### Request example

**curl**

```bash
curl -X POST "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/imports/{importId}/commit" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"confirm":true}'
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/imports/{importId}/commit", {
method: "POST",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
body: JSON.stringify({
  "confirm": true
}),
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Import commit accepted

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "organizationId": "550e8400-e29b-41d4-a716-446655440000",
  "projectId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "pending",
  "fileName": "urls.csv",
  "fileSizeBytes": 0,
  "contentSha256": "string",
  "headerDetected": true,
  "totalRows": 0,
  "validRows": 0,
  "invalidRows": 0,
  "duplicateInFileRows": 0,
  "alreadyTrackedRows": 0,
  "netNewRows": 0,
  "rowErrors": [
    {
      "line": 12,
      "reason": "URL is malformed",
      "value": "not-a-url"
    }
  ],
  "processedRows": 0,
  "insertedRows": 0,
  "skippedRows": 0,
  "error": "string",
  "expiresAt": "string",
  "committedAt": "string",
  "completedAt": "string",
  "createdAt": "string",
  "updatedAt": "string"
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### GET /api/user/organizations/{organizationId}/projects/{id}/sitemaps

List discovered sitemaps for a project

Returns sitemap documents recorded for the project (root, robots-declared, and children).

**Auth:** bearer · **Scopes:** `projects:read`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Request example

**curl**

```bash
curl -X GET "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/sitemaps" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/sitemaps", {
method: "GET",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Sitemaps retrieved successfully

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": [
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "url": "https://acme.com/sitemap.xml",
    "type": "index",
    "parentSitemapId": "550e8400-e29b-41d4-a716-446655440000",
    "lastFetchedAt": "string",
    "lastSuccessAt": "string",
    "fetchStatus": "ok",
    "urlCount": 0,
    "error": "string"
  }
]
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### POST /api/user/organizations/{organizationId}/projects/{id}/sync

Manually sync project sitemaps

Walks sitemap roots, admits on-host URLs (1 credit each), marks stale. 15-minute debounce → 429. Insufficient credits → 402.

**Auth:** bearer · **Scopes:** `projects:write`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Request example

**curl**

```bash
curl -X POST "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/sync" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/sync", {
method: "POST",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Sitemap sync completed

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "urlsDiscovered": 0,
  "urlsUpdated": 0,
  "urlsSkipped": 0,
  "sitemapsFetched": 0,
  "staleMarked": 0,
  "staleCleared": 0,
  "skippedOffHost": [
    {
      "url": "https://other.com/page",
      "reason": "off_host"
    }
  ]
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### POST /api/user/organizations/{organizationId}/projects/{id}/availability/probe

Probe robots / llms / sitemap availability

Runs the P5 availability probe and updates project presence flags. Ungated — runs before verification.

**Auth:** bearer · **Scopes:** `projects:write`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Request example

**curl**

```bash
curl -X POST "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/availability/probe" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/availability/probe", {
method: "POST",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Availability probed successfully

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "robotsTxtPresent": true,
  "llmsTxtPresent": true,
  "sitemapPresent": true,
  "robotsSitemapUrls": [
    "string"
  ]
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### GET /api/user/organizations/{organizationId}/projects/{id}/verification

Get project verification status

Returns DNS / IndexNow / GSC / Bing verification projection. Token and key are shown for setup; they are not credentials.

**Auth:** bearer · **Scopes:** `projects:read`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Request example

**curl**

```bash
curl -X GET "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/verification" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/verification", {
method: "GET",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Verification status retrieved

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "projectId": "550e8400-e29b-41d4-a716-446655440000",
  "domain": "acme.com",
  "verified": true,
  "dnsRecordName": "_indexhog.acme.com",
  "dnsRecordValue": "indexhog-site-verification=abc…",
  "dnsVerificationToken": "string",
  "dnsVerifiedAt": "string",
  "indexNowKey": "string",
  "indexNowKeySource": "generated",
  "indexNowKeyFileUrl": "https://acme.com/{key}.txt",
  "indexNowKeyVerifiedAt": "string",
  "gscConnectedAt": "string",
  "bingConnectedAt": "string"
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### POST /api/user/organizations/{organizationId}/projects/{id}/dns-token

Generate or rotate DNS verification token

Creates a new 64-hex DNS token and clears dnsVerifiedAt. Publish as TXT at `_indexhog.{domain}` with value `indexhog-site-verification={token}`.

**Auth:** bearer · **Scopes:** `projects:write`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Request example

**curl**

```bash
curl -X POST "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/dns-token" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/dns-token", {
method: "POST",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — DNS token rotated

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "projectId": "550e8400-e29b-41d4-a716-446655440000",
  "domain": "acme.com",
  "verified": true,
  "dnsRecordName": "_indexhog.acme.com",
  "dnsRecordValue": "indexhog-site-verification=abc…",
  "dnsVerificationToken": "string",
  "dnsVerifiedAt": "string",
  "indexNowKey": "string",
  "indexNowKeySource": "generated",
  "indexNowKeyFileUrl": "https://acme.com/{key}.txt",
  "indexNowKeyVerifiedAt": "string",
  "gscConnectedAt": "string",
  "bingConnectedAt": "string"
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### POST /api/user/organizations/{organizationId}/projects/{id}/verify-dns

Verify DNS TXT ownership

Resolves `_indexhog.{domain}` TXT live (no negative cache) and exact-matches after trim.

**Auth:** bearer · **Scopes:** `projects:write`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Request example

**curl**

```bash
curl -X POST "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/verify-dns" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/verify-dns", {
method: "POST",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — DNS verification attempted

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "verified": true,
  "error": "string",
  "verification": {
    "projectId": "550e8400-e29b-41d4-a716-446655440000",
    "domain": "acme.com",
    "verified": true,
    "dnsRecordName": "_indexhog.acme.com",
    "dnsRecordValue": "indexhog-site-verification=abc…",
    "dnsVerificationToken": "string",
    "dnsVerifiedAt": "string",
    "indexNowKey": "string",
    "indexNowKeySource": "generated",
    "indexNowKeyFileUrl": "https://acme.com/{key}.txt",
    "indexNowKeyVerifiedAt": "string",
    "gscConnectedAt": "string",
    "bingConnectedAt": "string"
  }
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### POST /api/user/organizations/{organizationId}/projects/{id}/indexnow-key

Generate or set IndexNow key

Empty body generates a 64-hex key; `{key}` provides 8–128 `[a-zA-Z0-9-]`. Clears indexNowKeyVerifiedAt.

**Auth:** bearer · **Scopes:** `projects:write`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Body parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `key` | string | no | Omit or empty to generate a 64-hex key; otherwise provide 8–128 chars [a-zA-Z0-9-] | `my-indexnow-key-01` |

#### Request example

**curl**

```bash
curl -X POST "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/indexnow-key" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"key":"my-indexnow-key-01"}'
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/indexnow-key", {
method: "POST",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
body: JSON.stringify({
  "key": "my-indexnow-key-01"
}),
});

const data = await response.json();
console.log(data);
```

#### Response `200` — IndexNow key set

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "projectId": "550e8400-e29b-41d4-a716-446655440000",
  "domain": "acme.com",
  "verified": true,
  "dnsRecordName": "_indexhog.acme.com",
  "dnsRecordValue": "indexhog-site-verification=abc…",
  "dnsVerificationToken": "string",
  "dnsVerifiedAt": "string",
  "indexNowKey": "string",
  "indexNowKeySource": "generated",
  "indexNowKeyFileUrl": "https://acme.com/{key}.txt",
  "indexNowKeyVerifiedAt": "string",
  "gscConnectedAt": "string",
  "bingConnectedAt": "string"
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### POST /api/user/organizations/{organizationId}/projects/{id}/verify-key

Verify IndexNow key file

Fetches `https://{domain}/{key}.txt` (SSRF-safe) and exact-matches the body after trim.

**Auth:** bearer · **Scopes:** `projects:write`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Request example

**curl**

```bash
curl -X POST "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/verify-key" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/verify-key", {
method: "POST",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Key verification attempted

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "verified": true,
  "error": "string",
  "verification": {
    "projectId": "550e8400-e29b-41d4-a716-446655440000",
    "domain": "acme.com",
    "verified": true,
    "dnsRecordName": "_indexhog.acme.com",
    "dnsRecordValue": "indexhog-site-verification=abc…",
    "dnsVerificationToken": "string",
    "dnsVerifiedAt": "string",
    "indexNowKey": "string",
    "indexNowKeySource": "generated",
    "indexNowKeyFileUrl": "https://acme.com/{key}.txt",
    "indexNowKeyVerifiedAt": "string",
    "gscConnectedAt": "string",
    "bingConnectedAt": "string"
  }
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### GET /api/user/organizations/{organizationId}/projects/{id}/submissions

List IndexNow submission history

Append-only IndexNow batch history for the project, newest first. Automatic submissions only — there is no manual submit endpoint. Batch summaries are retained forever; per-URL submission detail joins are retained for 90 days.

**Auth:** bearer · **Scopes:** `projects:read`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Query parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `offset` | number \| null | no | Number of items to skip | `0` |
| `limit` | number | no | Number of items to return (max 100) | `20` |

#### Request example

**curl**

```bash
curl -X GET "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/submissions" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/submissions", {
method: "GET",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Submissions retrieved successfully

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Items fetched successfully",
"data": [
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "organizationId": "550e8400-e29b-41d4-a716-446655440000",
    "projectId": "550e8400-e29b-41d4-a716-446655440000",
    "engine": "indexnow",
    "urlCount": 0,
    "status": "pending",
    "responseCode": 0,
    "responseBody": "string",
    "error": "string",
    "createdAt": "string"
  }
],
"meta": {
  "total": 100,
  "offset": 0,
  "limit": 10,
  "hasMore": true
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### GET /api/user/organizations/{organizationId}/projects/{id}/issues

List issue groups for a project

Paginated (project, type) issue groups. Ordered severity DESC, affectedUrlCount DESC. Filters: type, severity, status.

**Auth:** bearer · **Scopes:** `projects:read`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Query parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `offset` | number \| null | no | Number of items to skip | `0` |
| `limit` | number | no | Number of items to return (max 100) | `20` |
| `type` | IssueType | no | Filter by issue type |  |
| `severity` | IssueSeverity | no | Filter by severity |  |
| `status` | IssueStatus | no | Filter by status |  |

#### Request example

**curl**

```bash
curl -X GET "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/issues" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/issues", {
method: "GET",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Issues retrieved successfully

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Items fetched successfully",
"data": [
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "organizationId": "550e8400-e29b-41d4-a716-446655440000",
    "projectId": "550e8400-e29b-41d4-a716-446655440000",
    "type": "broken_link",
    "severity": "critical",
    "status": "open",
    "affectedUrlCount": 412,
    "sampleUrls": [
      "string"
    ],
    "firstDetectedAt": "string",
    "lastDetectedAt": "string",
    "resolvedAt": "string",
    "metadata": {},
    "createdAt": "string",
    "updatedAt": "string"
  }
],
"meta": {
  "total": 100,
  "offset": 0,
  "limit": 10,
  "hasMore": true
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### GET /api/user/organizations/{organizationId}/projects/{id}/issues/{issueId}

Get an issue group

Detail for one (project, type) issue group.

**Auth:** bearer · **Scopes:** `projects:read`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |
| `issueId` | string | yes | Issue group ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Request example

**curl**

```bash
curl -X GET "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/issues/{issueId}" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/issues/{issueId}", {
method: "GET",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Issue retrieved successfully

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "organizationId": "550e8400-e29b-41d4-a716-446655440000",
  "projectId": "550e8400-e29b-41d4-a716-446655440000",
  "type": "broken_link",
  "severity": "critical",
  "status": "open",
  "affectedUrlCount": 412,
  "sampleUrls": [
    "string"
  ],
  "firstDetectedAt": "string",
  "lastDetectedAt": "string",
  "resolvedAt": "string",
  "metadata": {},
  "createdAt": "string",
  "updatedAt": "string"
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### PATCH /api/user/organizations/{organizationId}/projects/{id}/issues/{issueId}

Update issue group status

Manual status transition. ignored silences the whole type for the project (does not set resolvedAt). open reopens. resolved emits issue.resolved.

**Auth:** bearer · **Scopes:** `projects:write`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |
| `issueId` | string | yes | Issue group ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Body parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `status` | IssueStatus | yes |  |  |

#### Request example

**curl**

```bash
curl -X PATCH "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/issues/{issueId}" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"status":"open"}'
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/issues/{issueId}", {
method: "PATCH",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
body: JSON.stringify({
  "status": "open"
}),
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Issue status updated successfully

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "organizationId": "550e8400-e29b-41d4-a716-446655440000",
  "projectId": "550e8400-e29b-41d4-a716-446655440000",
  "type": "broken_link",
  "severity": "critical",
  "status": "open",
  "affectedUrlCount": 412,
  "sampleUrls": [
    "string"
  ],
  "firstDetectedAt": "string",
  "lastDetectedAt": "string",
  "resolvedAt": "string",
  "metadata": {},
  "createdAt": "string",
  "updatedAt": "string"
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### GET /api/user/organizations/{organizationId}/projects/{id}/issues/{issueId}/urls

List memberships for an issue group

Paginated drill-down into issue_urls membership. Open memberships (resolvedAt null) first.

**Auth:** bearer · **Scopes:** `projects:read`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |
| `issueId` | string | yes | Issue group ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Query parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `offset` | number \| null | no | Number of items to skip | `0` |
| `limit` | number | no | Number of items to return (max 100) | `20` |

#### Request example

**curl**

```bash
curl -X GET "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/issues/{issueId}/urls" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/issues/{issueId}/urls", {
method: "GET",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Issue memberships retrieved successfully

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Items fetched successfully",
"data": [
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "organizationId": "550e8400-e29b-41d4-a716-446655440000",
    "issueId": "550e8400-e29b-41d4-a716-446655440000",
    "trackedUrlId": "550e8400-e29b-41d4-a716-446655440000",
    "url": "https://acme.com/about",
    "detectedAt": "string",
    "resolvedAt": "string",
    "createdAt": "string",
    "updatedAt": "string"
  }
],
"meta": {
  "total": 100,
  "offset": 0,
  "limit": 10,
  "hasMore": true
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### GET /api/user/organizations/{organizationId}/projects/{id}/stats

Get live website stats

Nine raw counters for the project. Returns zeros when no stats row exists. Ratios are not stored — compute client-side.

**Auth:** bearer · **Scopes:** `projects:read`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Request example

**curl**

```bash
curl -X GET "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/stats" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/stats", {
method: "GET",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Stats retrieved successfully

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "organizationId": "550e8400-e29b-41d4-a716-446655440000",
  "projectId": "550e8400-e29b-41d4-a716-446655440000",
  "totalUrls": 0,
  "healthyUrls": 0,
  "brokenUrls": 0,
  "redirectedUrls": 0,
  "indexedUrls": 0,
  "notIndexedUrls": 0,
  "staleUrls": 0,
  "openIssues": 0,
  "urlsWithIssues": 0,
  "lastReconciledAt": "string",
  "createdAt": "string",
  "updatedAt": "string"
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### GET /api/user/organizations/{organizationId}/projects/{id}/snapshots

List daily website snapshots

Immutable daily snapshots ascending by date. Window clamped to 1–365 days (default 30).

**Auth:** bearer · **Scopes:** `projects:read`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Query parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `days` | number | no | Snapshot window in days (1–365) | `30` |

#### Request example

**curl**

```bash
curl -X GET "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/snapshots" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/snapshots", {
method: "GET",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Snapshots retrieved successfully

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": [
  {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "organizationId": "550e8400-e29b-41d4-a716-446655440000",
    "projectId": "550e8400-e29b-41d4-a716-446655440000",
    "date": "2026-07-29",
    "totalUrls": 0,
    "healthyUrls": 0,
    "brokenUrls": 0,
    "redirectedUrls": 0,
    "indexedUrls": 0,
    "notIndexedUrls": 0,
    "staleUrls": 0,
    "openIssues": 0,
    "urlsWithIssues": 0,
    "createdAt": "string"
  }
]
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### GET /api/user/organizations/{organizationId}/projects/{id}/gsc/oauth-url

Get GSC OAuth authorize URL

Returns Google authorize URL with access_type=offline and prompt=consent. CSRF state is stored server-side. Requires GSC_OAUTH_* Infisical keys.

**Auth:** bearer · **Scopes:** `projects:write`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Query parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `state` | string | no | Optional client-supplied CSRF state (≥16 chars). When omitted, the server generates one. |  |

#### Request example

**curl**

```bash
curl -X GET "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/gsc/oauth-url" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/gsc/oauth-url", {
method: "GET",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — OAuth URL generated

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "authorizeUrl": "https://accounts.google.com/o/oauth2/v2/auth",
  "state": "string"
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### POST /api/user/organizations/{organizationId}/projects/{id}/gsc/callback

Complete GSC OAuth and connect property

Exchanges code for refresh token, validates property against sites.list and domain coverage, encrypts refresh token at rest. Stamps gscConnectedAt (verification signal). Never returns secrets.

**Auth:** bearer · **Scopes:** `projects:write`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Body parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `code` | string | yes | OAuth authorization code |  |
| `state` | string | yes | CSRF state from oauth-url |  |
| `propertyUrl` | string | yes | GSC property URL verbatim (sc-domain:… or https://…/); must cover project domain | `sc-domain:acme.com` |

#### Request example

**curl**

```bash
curl -X POST "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/gsc/callback" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"code":"string","state":"string","propertyUrl":"sc-domain:acme.com"}'
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/gsc/callback", {
method: "POST",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
body: JSON.stringify({
  "code": "string",
  "state": "string",
  "propertyUrl": "sc-domain:acme.com"
}),
});

const data = await response.json();
console.log(data);
```

#### Response `200` — GSC connected

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "projectId": "550e8400-e29b-41d4-a716-446655440000",
  "domain": "acme.com",
  "gscConnected": true,
  "gscConnectedAt": "string",
  "gscPropertyUrl": "string",
  "bingConnected": true,
  "bingConnectedAt": "string"
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### DELETE /api/user/organizations/{organizationId}/projects/{id}/gsc

Disconnect GSC

Clears encrypted refresh token, property URL, and gscConnectedAt. Preserves historical googleState on tracked URLs.

**Auth:** bearer · **Scopes:** `projects:write`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Request example

**curl**

```bash
curl -X DELETE "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/gsc" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/gsc", {
method: "DELETE",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — GSC disconnected

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "projectId": "550e8400-e29b-41d4-a716-446655440000",
  "domain": "acme.com",
  "gscConnected": true,
  "gscConnectedAt": "string",
  "gscPropertyUrl": "string",
  "bingConnected": true,
  "bingConnectedAt": "string"
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### POST /api/user/organizations/{organizationId}/projects/{id}/bing

Connect Bing Webmaster API key

Validates key length (≥16), pings Bing GetUrlInfo, encrypts key at rest, stamps bingConnectedAt. Never returns the key.

**Auth:** bearer · **Scopes:** `projects:write`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Body parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `apiKey` | string | yes | Bing Webmaster Tools API key (≥16 characters) |  |

#### Request example

**curl**

```bash
curl -X POST "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/bing" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"apiKey":"string"}'
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/bing", {
method: "POST",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
body: JSON.stringify({
  "apiKey": "string"
}),
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Bing connected

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "projectId": "550e8400-e29b-41d4-a716-446655440000",
  "domain": "acme.com",
  "gscConnected": true,
  "gscConnectedAt": "string",
  "gscPropertyUrl": "string",
  "bingConnected": true,
  "bingConnectedAt": "string"
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```

### DELETE /api/user/organizations/{organizationId}/projects/{id}/bing

Disconnect Bing

Clears encrypted API key and bingConnectedAt. Preserves historical bingState on tracked URLs.

**Auth:** bearer · **Scopes:** `projects:write`, `organization:read`

#### Path parameters

| Name | Type | Required | Description | Example |
| --- | --- | --- | --- | --- |
| `organizationId` | string | yes | Organization ID | `550e8400-e29b-41d4-a716-446655440000` |
| `id` | string | yes | Project ID | `550e8400-e29b-41d4-a716-446655440000` |

#### Request example

**curl**

```bash
curl -X DELETE "https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/bing" -H "Authorization: Bearer YOUR_ACCESS_TOKEN" -H "Content-Type: application/json"
```

**JavaScript**

```javascript
const response = await fetch("https://api.indexhog.com/api/user/organizations/{organizationId}/projects/{id}/bing", {
method: "DELETE",
headers: {
  "Authorization": "Bearer YOUR_ACCESS_TOKEN",
  "Content-Type": "application/json",
},
});

const data = await response.json();
console.log(data);
```

#### Response `200` — Bing disconnected

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "Operation completed successfully",
"data": {
  "projectId": "550e8400-e29b-41d4-a716-446655440000",
  "domain": "acme.com",
  "gscConnected": true,
  "gscConnectedAt": "string",
  "gscPropertyUrl": "string",
  "bingConnected": true,
  "bingConnectedAt": "string"
}
}
```

#### Response `401` — Unauthorized - Invalid or missing authentication

```json
{
"success": false,
"status": 401,
"code": "UNAUTHORIZED",
"message": "Authentication required"
}
```

#### Response `403` — Forbidden - Insufficient permissions

```json
{
"success": false,
"status": 403,
"code": "FORBIDDEN",
"message": "You do not have permission to perform this action"
}
```