# Gateway API Reference

**Service:** `adpilot-gateway` (`api.lucos.com`)  
**Base URL (local):** `http://localhost:3007`  
**UI developers:** start with [UI-API-INTEGRATION.md](./UI-API-INTEGRATION.md) and [openapi.yaml](./openapi.yaml)  
**Integration contract:** [GATEWAY-REPO-SYNC-CONTRACT.md](./GATEWAY-REPO-SYNC-CONTRACT.md)

---

## Quick start (curl)

Full Tier 2 flow — authenticate, sync catalog, list repos, import, poll status:

```bash
BASE_URL="http://localhost:3007"

# 1. Sign in — save token
TOKEN=$(curl -sS -X POST "${BASE_URL}/api/v1/auth/authenticate" \
  -H "Content-Type: application/json" \
  -d '{"authType":"email","action":"sign-in","email":"you@example.com","password":"your-password"}' \
  | jq -r '.token')

# 2. Check provider connection
curl -sS "${BASE_URL}/api/v1/integrations/status" \
  -H "Authorization: Bearer ${TOKEN}" | jq .

# 3. Connect GitHub (owner) — open redirectUrl in browser if not connected
curl -sS -X POST "${BASE_URL}/api/v1/integrations/github/connect" \
  -H "Authorization: Bearer ${TOKEN}" | jq .

# 4. Sync repo catalog from GitHub into gateway DB
curl -sS -X POST "${BASE_URL}/api/v1/integrations/github/repos/sync" \
  -H "Authorization: Bearer ${TOKEN}" | jq .

# 5. List cached repos — copy _id or providerRepoId
curl -sS "${BASE_URL}/api/v1/integrations/github/repos" \
  -H "Authorization: Bearer ${TOKEN}" \
  | jq '.repos[] | {_id, providerRepoId, fullName, isImportable}'

# 6. Import (use _id from step 5)
PROVIDER_REPO_ID="665a1b2c3d4e5f6789012345"
curl -sS -X POST "${BASE_URL}/api/v1/repos/import" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d "{\"providerRepoIds\":[\"${PROVIDER_REPO_ID}\"]}" | jq .

# 7. Poll sync status (URL-encode repo_id: github:org/repo → github%3Aorg%2Frepo)
curl -sS "${BASE_URL}/api/v1/repos/github%3Amy-org%2Fmy-repo/status" \
  -H "Authorization: Bearer ${TOKEN}" | jq .
```

---

## Authentication

Most routes require a JWT in either:

- Header: `Authorization: Bearer <token>`
- Cookie: `token=<token>`

Obtain a token:

```bash
curl -sS -X POST http://localhost:3007/api/v1/auth/authenticate \
  -H "Content-Type: application/json" \
  -d '{
    "authType": "email",
    "action": "sign-in",
    "email": "you@example.com",
    "password": "your-password"
  }'
```

**Request (email sign-in):**

```json
{
  "authType": "email",
  "action": "sign-in",
  "email": "you@example.com",
  "password": "your-password"
}
```

**Response (200 OK):**

```json
{
  "success": true,
  "user": {
    "id": "507f1f77bcf86cd799439011",
    "email": "you@example.com",
    "role": "user"
  },
  "token": "eyJhbGciOiJIUzI1NiIs..."
}
```

**Response (201 Created):** same shape when `action` is `sign-up` and a new user is created.

---

## Error format

All gateway errors use this envelope:

```json
{
  "success": false,
  "message": "Human-readable error",
  "details": {
    "code": "ERROR_CODE",
    "...": "optional fields"
  }
}
```


| Field     | Type    | Description                     |
| --------- | ------- | ------------------------------- |
| `success` | boolean | Always `false` on error         |
| `message` | string  | Error summary                   |
| `details` | object  | Optional; often includes `code` |


Common `details.code` values for import/integration flows:


| Code                      | HTTP | Meaning                                  |
| ------------------------- | ---- | ---------------------------------------- |
| `INVALID_PAYLOAD`         | 400  | Missing or invalid request body          |
| `PROVIDER_REPO_NOT_FOUND` | 404  | `providerRepoIds` not found for your org |
| `NOT_IMPORTABLE`          | 400  | Repo marked not importable               |
| `CONNECTION_INACTIVE`     | 400  | No active provider connection            |
| `TOKEN_MINT_FAILED`       | 503  | Could not mint clone credentials         |
| `REPO_LIMIT_EXCEEDED`     | 403  | Plan indexed-repo limit reached          |


---

## How to get `providerRepoIds` for import (Tier 2 E2E)

`providerRepoIds` in `POST /api/v1/repos/import` are **not** GitHub/Bitbucket repo names. They refer to `**ProviderRepo` documents** stored in gateway MongoDB after you connect a provider and sync the repo catalog.

### Flow

```
1. Connect provider     →  POST /api/v1/integrations/{provider}/connect
2. Complete OAuth       →  browser callback (GitHub App / Bitbucket OAuth)
3. Sync repo catalog    →  POST /api/v1/integrations/{provider}/repos/sync
4. List cached repos    →  GET  /api/v1/integrations/{provider}/repos
5. Import selected repo →  POST /api/v1/repos/import
```

### Step 1 — Check connection status

```bash
curl -sS http://localhost:3007/api/v1/integrations/status \
  -H "Authorization: Bearer ${TOKEN}"
```

**Response (200 OK):**

```json
{
  "providers": {
    "github": {
      "connected": true,
      "connectedAt": "2026-06-18T10:00:00.000Z",
      "accountName": "my-org",
      "lastSyncedAt": "2026-06-18T11:00:00.000Z"
    },
    "bitbucket": {
      "connected": false,
      "connectedAt": null,
      "accountName": null,
      "lastSyncedAt": null
    },
    "bitbucket_server": {
      "connected": false,
      "connectedAt": null,
      "accountName": null,
      "lastSyncedAt": null
    }
  }
}
```

If `connected` is `false`, connect first (Step 2). For Bitbucket Server, sync bootstraps the connection automatically (no OAuth UI).

### Step 2 — Connect GitHub or Bitbucket

After OAuth completes, the gateway **automatically syncs the repo catalog** so repos are available without a separate sync click.

**GitHub:**

```bash
curl -sS -X POST http://localhost:3007/api/v1/integrations/github/connect \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"returnUrl":"/app/settings/integrations?connected=true"}'
```

Optional body field `returnUrl` — relative path or full URL on `FRONTEND_URL` / `CORS_ORIGINS`. Stored in signed OAuth `state` and used after provider callback. If omitted, gateway redirects to `FRONTEND_URL` + `FRONTEND_INTEGRATION_REDIRECT_PATH` (default: `/organization?tab=repos&provider={provider}`).

**Response (200 OK):**

```json
{
  "redirectUrl": "https://github.com/apps/your-app/installations/new?state=..."
}
```

Open `redirectUrl` in a browser and complete installation. Requires org **owner** role.

**Bitbucket Cloud:**

```bash
curl -sS -X POST http://localhost:3007/api/v1/integrations/bitbucket/connect \
  -H "Authorization: Bearer ${TOKEN}"
```

Same response shape with Bitbucket OAuth URL.

**Bitbucket Server (admedia orgs only):**

No connect endpoint. The org must have `features.bitbucketServer: true` (auto-set on signup for `@admedia.com` emails). Gateway uses env service-account credentials (`BITBUCKET_SERVER_`*). Connection is created automatically on admedia signup when env is configured; existing orgs can use the backfill script or first sync:

```bash
node scripts/backfill-bitbucket-server-connections.js
```

Or trigger catalog sync (also bootstraps if missing):

```bash
curl -sS -X POST http://localhost:3007/api/v1/integrations/bitbucket-server/repos/sync \
  -H "Authorization: Bearer ${TOKEN}"
```

Requires org **owner** role. Errors: `403 BITBUCKET_SERVER_NOT_ENABLED`, `503 BITBUCKET_SERVER_NOT_CONFIGURED`.

### Step 3 — Sync repo catalog from provider

Fetches repos from GitHub/Bitbucket API and upserts `ProviderRepo` records in gateway MongoDB.

```bash
curl -sS -X POST http://localhost:3007/api/v1/integrations/github/repos/sync \
  -H "Authorization: Bearer ${TOKEN}"
```

**Response (200 OK):**

```json
{
  "provider": "github",
  "repoCount": 42,
  "upserted": 3,
  "modified": 39
}
```

Repeat for `bitbucket` if connected, or `bitbucket-server` for on-prem repos (`fullName` format: `PROJECT/slug`, e.g. `AD/my-repo`).

### Step 4 — List cached repos (get IDs)

**Unified list (all connected providers):**

```bash
curl -sS "http://localhost:3007/api/v1/integrations/repos?limit=50&offset=0" \
  -H "Authorization: Bearer ${TOKEN}"
```


| Query      | Required | Description                                  |
| ---------- | -------- | -------------------------------------------- |
| `provider` | no       | `github`, `bitbucket`, or `bitbucket-server` |
| `limit`    | no       | 1–100, default 50                            |
| `offset`   | no       | Pagination offset, default 0                 |


Includes **Bitbucket Server** repos when the org has `features.bitbucketServer: true` and the catalog has been synced via `POST /integrations/bitbucket-server/repos/sync`.

**Response (200 OK):**

```json
{
  "repos": [ { "...": "same shape as per-provider list below" } ],
  "providers": ["github", "bitbucket", "bitbucket_server"],
  "count": 50,
  "total": 142,
  "limit": 50,
  "offset": 0,
  "hasMore": true
}
```

**Per-provider list:**

```bash
curl -sS http://localhost:3007/api/v1/integrations/github/repos \
  -H "Authorization: Bearer ${TOKEN}"
```

**Response (200 OK):**

```json
{
  "provider": "github",
  "count": 42,
  "repos": [
    {
      "_id": "665a1b2c3d4e5f6789012345",
      "orgId": "507f1f77bcf86cd799439011",
      "connectionId": "507f1f77bcf86cd799439012",
      "provider": "github",
      "providerRepoId": "123456789",
      "fullName": "my-org/my-repo",
      "defaultBranch": "main",
      "visibility": "private",
      "isImportable": true,
      "lastSeenAt": "2026-06-18T11:00:00.000Z",
      "createdAt": "2026-06-01T08:00:00.000Z",
      "updatedAt": "2026-06-18T11:00:00.000Z"
    }
  ]
}
```

**Use either value in `providerRepoIds`:**


| Field            | Example                      | Notes                                        |
| ---------------- | ---------------------------- | -------------------------------------------- |
| `_id`            | `"665a1b2c3d4e5f6789012345"` | Gateway MongoDB ObjectId (recommended)       |
| `providerRepoId` | `"123456789"`                | Provider-native ID (GitHub numeric ID, etc.) |


Both resolve to the same repo during import.

### Step 4b — Search repos (optional)

```bash
curl -sS "http://localhost:3007/api/v1/integrations/repos/search?q=my-repo&provider=github&limit=20" \
  -H "Authorization: Bearer ${TOKEN}"
```


| Query      | Required | Description                                           |
| ---------- | -------- | ----------------------------------------------------- |
| `q`        | yes      | Keyword matched against `fullName` (case-insensitive) |
| `provider` | no       | `github`, `bitbucket`, or `bitbucket-server`          |
| `limit`    | no       | 1–100, default 50                                     |


**Response (200 OK):**

```json
{
  "query": "my-repo",
  "providers": ["github"],
  "count": 2,
  "repos": [ { "...": "same shape as list endpoint" } ]
}
```

### Step 5 — Import

```bash
curl -sS -X POST http://localhost:3007/api/v1/repos/import \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"providerRepoIds":["665a1b2c3d4e5f6789012345"]}'
```

See [Import repositories](#post-apiv1reposimport) below.

### MongoDB fallback (local debugging)

If the API is unavailable, query gateway Mongo directly:

```javascript
// mongosh lucos
db.providerrepos.find(
  { orgId: ObjectId("YOUR_ORG_ID") },
  { _id: 1, providerRepoId: 1, fullName: 1, defaultBranch: 1, isImportable: 1 }
).pretty()
```

Your org ID is available from the JWT payload or `GET /api/v1/auth/me`.

---

## Health

### `GET /api/v1/health`

**Auth:** none

```bash
curl -sS http://localhost:3007/api/v1/health
```

**Response (200 OK):**

```json
{
  "success": true,
  "message": "Gateway is healthy"
}
```

---

## Auth


| Method | Path                           | Auth | Description            |
| ------ | ------------------------------ | ---- | ---------------------- |
| POST   | `/api/v1/auth/authenticate`    | No   | Sign in / sign up      |
| GET/POST | `/api/v1/auth/google/ide-start` | No | Start Google OAuth for Lucos IDE (returns `{ redirectUrl }`) |
| GET    | `/api/v1/auth/google/ide-callback` | No | Google OAuth callback for IDE — redirects to `lucos://auth/callback?...` |
| GET    | `/api/v1/auth/me`              | Yes  | Current user profile   |
| POST   | `/api/v1/auth/logout`          | Yes  | Clear session cookie   |
| POST   | `/api/v1/auth/forgot-password` | No   | Request password reset |
| POST   | `/api/v1/auth/reset-password`  | No   | Reset with OTP         |
| DELETE | `/api/v1/auth/delete-account`  | Yes  | Delete account         |

### Google IDE OAuth

Desktop flow (system browser → gateway → IDE deep link):

1. IDE calls `GET /api/v1/auth/google/ide-start` (optional `loopbackPort` / `deepLink`).
2. Open returned `redirectUrl` in the system browser.
3. Google redirects to `GET /api/v1/auth/google/ide-callback`.
4. Gateway exchanges `code`, issues Lucos JWTs, redirects to:
   - `lucos://auth/callback?success=true&token=...&refreshToken=...&userId=...&email=...&isNewUser=true|false`
   - or `http://127.0.0.1:<loopbackPort>/callback?...` when `loopbackPort` was provided.

Required env: `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `GOOGLE_IDE_CALLBACK_URL` (must be registered in Google Cloud Console).

**Full frontend / IDE integration guide:** [GOOGLE-IDE-OAUTH.md](./GOOGLE-IDE-OAUTH.md)


---

## Integrations

Base path: `/api/v1/integrations`


| Method | Path                    | Role   | Description                                                     |
| ------ | ----------------------- | ------ | --------------------------------------------------------------- |
| GET    | `/status`               | member | Connection status per provider                                  |
| POST   | `/github/connect`       | owner  | Start GitHub App install                                        |
| GET    | `/github/callback`      | none   | OAuth callback (browser)                                        |
| POST   | `/bitbucket/connect`    | owner  | Start Bitbucket OAuth                                           |
| GET    | `/bitbucket/callback`   | none   | OAuth callback (browser)                                        |
| GET    | `/repos`                | member | List cached repos across connected providers                    |
| GET    | `/repos/search`         | member | Search cached repos by keyword                                  |
| GET    | `/:provider/repos`      | member | List cached repos (`github` | `bitbucket` | `bitbucket-server`) |
| POST   | `/:provider/repos/sync` | owner  | Refresh repo catalog from provider                              |
| DELETE | `/:provider`            | owner  | Disconnect provider                                             |


### `DELETE /api/v1/integrations/:provider`

```bash
curl -sS -X DELETE http://localhost:3007/api/v1/integrations/github \
  -H "Authorization: Bearer ${TOKEN}"
```

**Response (200 OK):**

```json
{
  "message": "github disconnected",
  "connectionId": "507f1f77bcf86cd799439012",
  "reposRemoved": 42,
  "affectedRepoIds": ["github:acme/my-repo"]
}
```

On disconnect the gateway:

1. Revokes the provider connection (GitHub App uninstall API; Bitbucket token revoke best-effort).
2. Deletes cached `ProviderRepo` catalog rows for that connection.
3. Marks matching `OrgIndexedRepo` rows as `provider_disconnected` (indexed vectors remain; new questions are blocked).

Re-connect the provider and re-import repos to restore query access.

**Query blocking:** After disconnect, `POST /api/v1/query`, `POST /api/v1/search`, and `POST /api/v1/chat/conversations/:id/messages` return **403** when the repo’s provider is no longer connected:

```json
{
  "success": false,
  "message": "Repository is not available for new questions",
  "details": {
    "code": "REPO_QUERY_UNAVAILABLE",
    "unavailableReason": "provider_disconnected",
    "repoId": "github:acme/my-repo"
  }
}
```

Chat history remains readable; only new questions are rejected.

---

## Repository import & sync (proxied to repo-sync)

Base path: `/api/v1`  
Requires: active subscription, `REPO_SYNC_URL` configured.


| Method | Path                    | Role   | Upstream                                |
| ------ | ----------------------- | ------ | --------------------------------------- |
| POST   | `/repos/import`         | owner  | repo-sync `POST /repos/import`          |
| GET    | `/repos/:id/status`     | member | repo-sync `GET /repos/{id}/status`      |
| POST   | `/admin/repos/:id/sync` | owner  | repo-sync `POST /admin/repos/{id}/sync` |
| POST   | `/admin/repos/:id/force-sync` | owner  | repo-sync `POST /admin/repos/{id}/force-sync` (no repo limit) |


After a successful gateway import, poll status with the **repo-sync repo_id** (not `providerRepoId`):

```
github:my-org/my-repo
```

URL-encode for the path: `github%3Amy-org%2Fmy-repo`

### `POST /api/v1/repos/import`

**Auth:** JWT, org **owner**, active subscription, plan repo limit.

```bash
curl -sS -X POST http://localhost:3007/api/v1/repos/import \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "providerRepoIds": ["665a1b2c3d4e5f6789012345", "123456789"]
  }'
```

**Request:**

```json
{
  "providerRepoIds": ["665a1b2c3d4e5f6789012345", "123456789"]
}
```


| Field             | Type     | Required | Description                                           |
| ----------------- | -------- | -------- | ----------------------------------------------------- |
| `providerRepoIds` | string[] | yes      | Mongo `_id` or `providerRepoId` from cached repo list |


**Response (202 Accepted)** — single connection:

```json
{
  "importJobId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "accepted",
  "jobs": [
    {
      "jobId": "job_a1b2c3d4e5f67890",
      "providerRepoId": "123456789",
      "fullName": "my-org/my-repo",
      "repoId": "github:my-org/my-repo",
      "status": "pending"
    }
  ]
}
```

**Response (207 Multi-Status)** — multiple connections (partial success possible):

```json
{
  "status": "partial",
  "imports": [
    {
      "connectionId": "507f1f77bcf86cd799439012",
      "provider": "github",
      "importJobId": "550e8400-e29b-41d4-a716-446655440000",
      "statusCode": 202,
      "status": "accepted",
      "jobs": [ "..." ]
    }
  ]
}
```

**Errors:**


| HTTP | Code                      | Cause                                            |
| ---- | ------------------------- | ------------------------------------------------ |
| 400  | `INVALID_PAYLOAD`         | Empty or missing `providerRepoIds`               |
| 403  | —                         | Not org owner                                    |
| 404  | `PROVIDER_REPO_NOT_FOUND` | ID not in your org's cache                       |
| 400  | `NOT_IMPORTABLE`          | Repo flagged not importable                      |
| 400  | `CONNECTION_INACTIVE`     | Provider disconnected                            |
| 503  | `TOKEN_MINT_FAILED`       | GitHub/Bitbucket token mint failed               |
| 503  | —                         | `REPO_SYNC_URL` not set or repo-sync unreachable |


### `GET /api/v1/indexed-repos`

**Auth:** JWT, active subscription. Org members see all repos imported by the org; team-only members see repos assigned via `TeamRepo`.

**Do not call** repo-sync `GET :6001/indexed-repos` from clients — that endpoint requires `X-Internal-Secret` and is gateway-only. Use this route with a Bearer token (or session cookie from the UI).

Returns gateway-imported repositories for the caller's org with indexing status from repo-sync. Legacy `ad/slug` repos are excluded.

```bash
curl -sS http://localhost:3007/api/v1/indexed-repos \
  -H "Authorization: Bearer ${TOKEN}"
```

**Response (200 OK):**

```json
{
  "repos": [
    {
      "repo_id": "github:my-org/my-repo",
      "repo_name": "my-repo",
      "git_url": "https://github.com/my-org/my-repo.git",
      "status": "ready",
      "last_indexed_at": "2026-06-18T14:00:00.000Z"
    }
  ]
}
```


| `status`   | Meaning                             |
| ---------- | ----------------------------------- |
| `indexing` | Pipeline in progress or not started |
| `ready`    | All required stages complete        |
| `failed`   | Sync or pipeline stage failed       |


**Note:** Repos imported before org registry shipped return an empty list until you run `node scripts/backfill-org-indexed-repos.js` or re-import. Restart **both** gateway and repo-sync after deploying this feature.

### `GET /api/v1/repos/:id/status`

**Auth:** JWT, org member (or team member with repo access).

**Path:** `:id` = repo-sync `repo_id` (e.g. `github:my-org/my-repo` — URL-encoded).

```bash
# github:my-org/my-repo → github%3Amy-org%2Fmy-repo
curl -sS "http://localhost:3007/api/v1/repos/github%3Amy-org%2Fmy-repo/status" \
  -H "Authorization: Bearer ${TOKEN}"
```

**Response:** Proxied from repo-sync. See [repo-sync API reference](../../adpilot-common-composer.com/services/adpilot-indexing-repo-sync.com/docs/API-REFERENCE.md#get-reposrepo_idstatus).

### `POST /api/v1/admin/repos/:id/sync`

**Auth:** JWT, org owner.

**Path:** `:id` = legacy Bitbucket Server `project/slug` repo_id.

**Query:**


| Param | Default  | Description          |
| ----- | -------- | -------------------- |
| `ref` | `master` | Branch or commit SHA |


```bash
curl -sS -X POST "http://localhost:3007/api/v1/admin/repos/ad%2Fmy-repo/sync?ref=master" \
  -H "Authorization: Bearer ${TOKEN}"
```

**Response (202 Accepted):** Proxied from repo-sync admin sync endpoint.

### `POST /api/v1/admin/repos/:id/force-sync`

**Auth:** JWT, org owner. Does **not** consume indexed-repo plan quota.

Purges cached pipeline data and re-indexes from scratch. Use for repositories stuck in `indexing` or `failed`.

**Query:**

| Param | Default  | Description          |
| ----- | -------- | -------------------- |
| `ref` | `master` | Branch or commit SHA |
| `force` | `false` | Admin override when latest run is `ready` |

```bash
curl -sS -X POST "http://localhost:3007/api/v1/admin/repos/ad%2Fmy-repo/force-sync?ref=master" \
  -H "Authorization: Bearer ${TOKEN}"
```

**Response (202 Accepted):** Proxied from repo-sync; includes `mode: "fresh"` and `purged` summary.

---

## Collaboration tools (proxied to integration service)

**Auth:** JWT, active subscription, org role in `COLLAB_TOOL_ALLOWED_ORG_ROLES` (default: `owner`, `admin`).

Gateway validates the tool name, wraps the request body, and forwards to **`slack-bot-service.com`** (`POST /internal/v1/tools/:tool`) with S2S auth.

### `POST /api/v1/tools/:tool`

**Path:** `:tool` = one of `jira_search_issues`, `jira_get_issue`, `jira_list_projects`, `jira_get_project`, `jira_list_boards`, `jira_list_sprints`, `jira_list_sprint_issues`, `jira_list_users`, `jira_list_versions`, `confluence_search_pages`, `confluence_get_page`, `confluence_get_comments`, `confluence_list_children`, `confluence_list_spaces`, `confluence_list_attachments`, `confluence_get_versions`, `confluence_search_users`, `confluence_get_space_permissions`, `confluence_get_user_space_access`, `slack_search_messages`, `slack_list_channels`, `slack_ask`, `slack_get_latest_messages`, `slack_get_file`, `slack_list_users`, `slack_get_user`, `slack_check_scopes`, `gdrive_search_files`, `gdrive_read_file`, `gdrive_query_sheet`, `gdrive_list_recent_activity`, `gdrive_list_workspaces`, `gdrive_drive_overview`, `gdrive_user_drive_info`, `gdrive_find_duplicates`, `gdrive_lookup_owned_file`, `gdrive_list_permissions`, `gdrive_list_revisions`, `gdrive_list_comments`, `gcal_list_events`, `gcal_get_event`, `gcal_list_resources`, `gcal_propose_rsvp`, `gcal_confirm_write`, `gcal_cancel_write`, `fireflies_search_meetings`, `fireflies_get_meeting`, `bb_list_projects`, `bb_list_repos`, `bb_list_prs`, `bb_get_pr`, `bb_list_branches`, `bb_list_commits`, `bb_file_diff`, `hubspot_list_accounts`, `hubspot_search_contacts`, `hubspot_get_contact`, `hubspot_get_contact_related`, `hubspot_search_companies`, `hubspot_get_company`, `hubspot_search_deals`, `hubspot_get_deal`, `hubspot_search_tickets`, `hubspot_get_ticket`, `hubspot_list_list_members`, `hubspot_list_lists`, `hubspot_list_owners`, `hubspot_list_pipelines`, `hubspot_list_properties`, `hubspot_get_owner`, `hubspot_search_notes`, `hubspot_get_note`, `hubspot_list_associations`, `hubspot_get_timeline`, `hubspot_search_activities`, `hubspot_get_attachment`, `hubspot_propose_create_note`, `hubspot_confirm_write`, `hubspot_cancel_write`. Names matching `jira_`, `confluence_`, `slack_`, `gdrive_`, `gcal_`, `fireflies_`, `bb_`, or `hubspot_` are also proxied.

**Body (from MCP — flat args):**

```json
{
  "environment": "staging",
  "query": "publisher onboarding",
  "limit": 25
}
```

Gateway forwards to `{COLLAB_INTEGRATION_SERVICE_URL}/internal/v1/tools/:tool` (slack-bot-service.com) as:

```json
{
  "environment": "staging",
  "args": {
    "query": "publisher onboarding",
    "limit": 25
  }
}
```

**Upstream headers (gateway → integration service):**

| Header | Source |
| ------ | ------ |
| `Authorization` | `Bearer ${COLLAB_INTEGRATION_SERVICE_TOKEN}` |
| `X-Lucos-Org-Id` | Authenticated user's org |
| `X-Request-Id` | Request tracing id |
| `X-User-Id` | Authenticated user (audit) |

**Response:** Proxied from integration service. See [business-mcp collaboration API contract](../../business-mcp.lucos.com/docs/COLLAB-INTEGRATION-SERVICE-API.md).

**Errors:**

| HTTP | `details.code` | When |
| ---- | -------------- | ---- |
| 403 | `TOOL_ACCESS_DENIED` | Org role not allowed |
| 404 | `TOOL_NOT_SUPPORTED` | Unknown `:tool` |
| 503 | `COLLAB_SERVICE_NOT_CONFIGURED` | Missing `COLLAB_INTEGRATION_SERVICE_URL` or `COLLAB_INTEGRATION_SERVICE_TOKEN` |

---

## Internal API (service-to-service)

Base path: `/internal/v1`  
**Not for browser clients.**

### `GET /internal/v1/connections/:connectionId/clone-auth`

**Auth:** `X-Internal-Secret: <GATEWAY_INTERNAL_SERVICE_SECRET>`

**Query:**


| Param            | Required | Description                     |
| ---------------- | -------- | ------------------------------- |
| `fullName`       | yes      | `owner/repo` for clone URL      |
| `providerRepoId` | no       | Provider-native repo ID (audit) |


```bash
curl -sS "http://localhost:3007/internal/v1/connections/507f1f77bcf86cd799439012/clone-auth?fullName=my-org/my-repo" \
  -H "X-Internal-Secret: ${GATEWAY_INTERNAL_SERVICE_SECRET}"
```

**Response (200 OK):**

```json
{
  "connectionId": "507f1f77bcf86cd799439012",
  "provider": "github",
  "authMethod": "git_credential",
  "cloneUrl": "https://github.com/my-org/my-repo.git",
  "username": "x-access-token",
  "password": "<short-lived-token>",
  "expiresAt": "2026-06-18T15:30:00.000Z"
}
```

Used for re-sync / on-demand auth (optional; MVP import uses direct `cloneAuth` pass-through).

---

## Trusted headers (gateway → repo-sync)

When proxying or calling repo-sync, the gateway forwards:


| Header                     | Description               |
| -------------------------- | ------------------------- |
| `X-Request-Id`             | Correlation ID            |
| `X-Org-Id`                 | Organization ID           |
| `X-User-Id`                | Acting user ID            |
| `X-User-Email`             | User email                |
| `X-Org-Role`               | `owner` | `developer` | … |
| `X-Plan-Code`              | Subscription plan code    |
| `X-Allowed-Model-Families` | Plan model access         |
| `X-Default-Model-Family`   | Default model             |


---

## Environment variables (integration E2E)


| Variable                          | Required   | Description                               |
| --------------------------------- | ---------- | ----------------------------------------- |
| `REPO_SYNC_URL`                   | Yes        | e.g. `http://localhost:6001`              |
| `PROVIDER_TOKEN_ENCRYPTION_KEY`   | Yes        | 64 hex chars for encrypted provider creds |
| `GITHUB_APP_SLUG`                 | GitHub E2E | App slug for connect URL                  |
| `GITHUB_APP_ID`                   | GitHub E2E | GitHub App ID                             |
| `GITHUB_APP_PRIVATE_KEY`          | GitHub E2E | PEM private key                           |
| `BITBUCKET_CLIENT_ID`             | BB E2E     | Bitbucket OAuth client                    |
| `BITBUCKET_CLIENT_SECRET`         | BB E2E     | Bitbucket OAuth secret                    |
| `GATEWAY_INTERNAL_SERVICE_SECRET` | Optional   | Internal clone-auth API                   |
| `COLLAB_INTEGRATION_SERVICE_URL`  | Collab E2E | Integration service base URL              |
| `COLLAB_INTEGRATION_SERVICE_TOKEN` | Collab E2E | S2S bearer for integration service       |
| `COLLAB_TOOL_ALLOWED_ORG_ROLES`   | Optional   | Default `owner,admin`                     |


---

## Related documents


| Document                                                                                                                   | Audience                                     |
| -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
| [UI-API-INTEGRATION.md](./UI-API-INTEGRATION.md)                                                                           | Frontend developers — flows, roles, examples |
| [openapi.yaml](./openapi.yaml)                                                                                             | OpenAPI 3.0 — Swagger / Postman import       |
| [GATEWAY-REPO-SYNC-CONTRACT.md](./GATEWAY-REPO-SYNC-CONTRACT.md)                                                           | Backend import contract v1.1                 |
| [SETUP.md](../SETUP.md)                                                                                                    | Local development setup                      |
| [repo-sync API reference](../../adpilot-common-composer.com/services/adpilot-indexing-repo-sync.com/docs/API-REFERENCE.md) | Repo-sync upstream shapes                    |


