Endpoints

Gitops

POST/api/v1/gitops/github/webhook

Receive a GitHub App webhook delivery.

Order matters and matches §7.2: signature verified before anything else is parsed; replay-checked next (so a redelivery of an already-accepted payload is acknowledged, not reprocessed, without needing to re-parse or re-match it); only then is the body parsed and matched to a connection.

Header Parameters

X-Hub-Signature-256?X-Hub-Signature-256
X-GitHub-Delivery?X-Github-Delivery
X-GitHub-Event?X-Github-Event

Response Body

application/json

application/json

curl -X POST "https://example.com/api/v1/gitops/github/webhook"
{}
GET/api/v1/me/gitops/import

Return {"files": {...}, "excluded_kinds": {...}}.

files covers every exportable Kind currently registered (Organization, Monitor, Tag, Rule — see import_service.py's module docstring for why the list stops there). excluded_kinds is a per-Kind live-resource count for every Kind this import cannot express yet (import_service.EXCLUDED_KINDS), so the caller can tell the customer what a snapshot silently leaves out rather than letting an incomplete inventory pass as a complete one.

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Response Body

application/json

curl -X GET "https://example.com/api/v1/me/gitops/import"
{}
GET/api/v1/me/gitops/connection

The org's connection, or 404 when it has none.

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Response Body

application/json

curl -X GET "https://example.com/api/v1/me/gitops/connection"
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",  "installation_id": 0,  "repo": "string",  "branch": "string",  "sync_mode": "direct",  "service_account_id": "81f92c7b-60ae-411e-bc6f-e2dc93db8886",  "created_at": "2019-08-24T14:15:22Z",  "updated_at": "2019-08-24T14:15:22Z"}
DELETE/api/v1/me/gitops/connection

Delete the org's connection (#974: "a connection created by mistake ... is permanent" — there was no route for this at all, for Projects).

Deleting the connection does not delete anything it previously applied (monitors, etc.) — those belong to the org directly (§7.1) and are unaffected. It also does not touch the GitHub App installation underneath it — disconnecting the installation itself is a separate action (DELETE /me/gitops/github/installations/{id}, gitops_github_install.py).

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Response Body

curl -X DELETE "https://example.com/api/v1/me/gitops/connection"
Empty
PUT/api/v1/me/gitops/connection

Create-or-replace the org's connection. service_account_id is required (no default) — §7.4: "a connection cannot be created without an explicit choice."

400 when service_account_id doesn't resolve to an active account in this org (GitOpsConnectionValidationError); 409 when this (installation_id, repo) pair is already bound to a different org's connection (gitops_connection's own uniqueness constraint — see app/models/gitops_connection.py).

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Schema for PUT /me/gitops/connection — create-or-replace.

Response Body

application/json

application/json

curl -X PUT "https://example.com/api/v1/me/gitops/connection" \  -H "Content-Type: application/json" \  -d '{    "installation_id": 1,    "repo": "string",    "service_account_id": "81f92c7b-60ae-411e-bc6f-e2dc93db8886"  }'
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",  "installation_id": 0,  "repo": "string",  "branch": "string",  "sync_mode": "direct",  "service_account_id": "81f92c7b-60ae-411e-bc6f-e2dc93db8886",  "created_at": "2019-08-24T14:15:22Z",  "updated_at": "2019-08-24T14:15:22Z"}
GET/api/v1/me/gitops/sync-runs

Sync runs for the active organization, newest first.

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Query Parameters

status?|

Filter by run status

cursor?|

Opaque cursor from a previous page's next_cursor

limit?Limit

Page size, max 100

Range1 <= value <= 100
Default20

Response Body

application/json

application/json

curl -X GET "https://example.com/api/v1/me/gitops/sync-runs"
{  "items": [    {      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",      "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",      "connection_id": "d3547de1-d1f2-4344-b4c2-17169b7526f9",      "provider": "string",      "workflow_id": "string",      "workflow_run_id": "string",      "iteration": 0,      "trigger": "push",      "trigger_delivery_id": "string",      "actor": "string",      "repo": "string",      "branch": "string",      "sync_mode": "direct",      "commit_sha": "string",      "pr_number": 0,      "status": "running",      "phase": "resolve",      "plan_summary": {},      "error_code": "string",      "error_message": "string",      "started_at": "2019-08-24T14:15:22Z",      "finished_at": "2019-08-24T14:15:22Z",      "duration_ms": 0,      "created_at": "2019-08-24T14:15:22Z",      "updated_at": "2019-08-24T14:15:22Z"    }  ],  "next_cursor": "string"}
GET/api/v1/me/gitops/sync-runs/{run_id}

One run plus its resource outcomes, in plan/apply order. 404 across tenants — see module docstring.

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Path Parameters

run_id*Run Id
Formatuuid

Response Body

application/json

application/json

curl -X GET "https://example.com/api/v1/me/gitops/sync-runs/497f6eca-6276-4993-bfeb-53cbbbba6f08"
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",  "connection_id": "d3547de1-d1f2-4344-b4c2-17169b7526f9",  "provider": "string",  "workflow_id": "string",  "workflow_run_id": "string",  "iteration": 0,  "trigger": "push",  "trigger_delivery_id": "string",  "actor": "string",  "repo": "string",  "branch": "string",  "sync_mode": "direct",  "commit_sha": "string",  "pr_number": 0,  "status": "running",  "phase": "resolve",  "plan_summary": {},  "plan_rendered": "string",  "error_code": "string",  "error_message": "string",  "started_at": "2019-08-24T14:15:22Z",  "finished_at": "2019-08-24T14:15:22Z",  "duration_ms": 0,  "created_at": "2019-08-24T14:15:22Z",  "updated_at": "2019-08-24T14:15:22Z",  "resources": [    {      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",      "sequence": 0,      "kind": "string",      "name": "string",      "manifest_path": "string",      "action": "create",      "status": "planned",      "detail": "string",      "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f"    }  ]}
GET/api/v1/me/gitops/sync-runs/{run_id}/events

The run's event log, oldest first. 404 across tenants — the run lookup is the same org-scoped check get_sync_run uses, so an events request for another org's run never even reaches the events query.

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Path Parameters

run_id*Run Id
Formatuuid

Query Parameters

cursor?|

Opaque cursor from a previous page's next_cursor

limit?Limit

Page size, max 100

Range1 <= value <= 100
Default20

Response Body

application/json

application/json

curl -X GET "https://example.com/api/v1/me/gitops/sync-runs/497f6eca-6276-4993-bfeb-53cbbbba6f08/events"
{  "items": [    {      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",      "occurred_at": "2019-08-24T14:15:22Z",      "sequence": 0,      "level": "info",      "phase": "resolve",      "message": "string",      "data": {}    }  ],  "next_cursor": "string"}
POST/api/v1/me/gitops/github/install/start

Mint a state bound to the caller's org and return the GitHub App installation URL — the console sends the browser there (window.location.assign), matching billing.py's create_portal_session {"url": ...} + redirect precedent.

503 when GITHUB_APP_SLUG isn't configured — a deployment-configuration gap, not a caller error, matching GitHubConfigurationError's existing use for the App id/private key in providers/github/client.py.

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Response Body

application/json

curl -X POST "https://example.com/api/v1/me/gitops/github/install/start"
{  "url": "string"}
POST/api/v1/me/gitops/github/setup

Confirm a GitHub App installation once the console's own Setup URL route has received GitHub's redirect (§7.4). Called server-side by console/app/src/routes/api/gitops/github/setup.ts with this session's bearer attached — never by the browser directly.

Every failure raises a plain HTTPException whose detail is one of a fixed set of short codes (missing_state, invalid_state, org_mismatch, missing_installation, not_configured, github_error) — the console route maps these to github_install_error=<code> on its own redirect, the same shape the old public callback used, and the settings page already renders a message per code (INSTALL_ERROR_MESSAGES).

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Body for POST /me/gitops/github/setup — the raw query params GitHub's Setup URL redirect carried, forwarded here by the console's own Setup URL route once it has read them. All optional at the schema level (a missing state/installation_id is a real, expected case GitHub can send) — the route body distinguishes and rejects each with its own error code rather than a blanket 422.

Response Body

application/json

application/json

curl -X POST "https://example.com/api/v1/me/gitops/github/setup" \  -H "Content-Type: application/json" \  -d '{}'
{  "installation_id": 0,  "setup_action": "string",  "repos": [    "string"  ],  "org_id": "string"}
GET/api/v1/me/gitops/github/installations

What GitHub App installation this org's connection is bound to, and its live state (#974, GITOPS_ARCHITECTURE.md §7.4). Closes the gap the settings page has today: nothing tells an admin an installation exists, which account it belongs to, whether it covers "all repositories" or a named subset, or whether GitHub has suspended it.

How this learns which installation exists, and why: the candidate installation id is whatever installation_id this org's own gitops_connection row records — there is no separate installations table, and this deliberately does not add one. GitHub's own installation-listing endpoint (GET /app/installations) is App-wide, not org-scoped — it would return every installation of this deployment's App across every customer, which this endpoint has no business exposing to one org's admin. gitops_connection is the only thing this backend already persists that ties an installation to a specific org, so it is the correct (and only safe) source for "which installation does this org have," and its live details (account, repository_selection, suspended_at, current repos) are then fetched fresh from GitHub — never cached, since a stale "installed" reading is exactly what #974 reports as missing today.

Returns a list of zero or one entries — #995 collapsed "an org's Projects, each possibly on a different installation" into "an org has at most one connection, therefore at most one installation." Kept as a list (rather than an Optional[GitOpsInstallationResponse]) so the console's existing rendering, built for a list, needs no reshaping (#996).

An installation GitHub can't be asked about — deleted, or simply fake test data, like the dev org's installation_id=99999002 #974 itself names — comes back with reachable=False rather than failing outright.

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Response Body

application/json

curl -X GET "https://example.com/api/v1/me/gitops/github/installations"
[  {    "installation_id": 0,    "account_login": "string",    "repository_selection": "string",    "suspended_at": "string",    "repos": [],    "reachable": true,    "error": "string"  }]
DELETE/api/v1/me/gitops/github/installations/{installation_id}

Disconnect (uninstall) a GitHub App installation (#974).

Semantics decided here, not left to the button: uninstalling on GitHub's side (DELETE /app/installations/{id}) is destructive and irreversible from this side. Rather than delete or orphan the org's connection as a side effect, this route refuses the disconnect outright while the caller's org's connection still references the installation (409) — the admin removes the connection first (DELETE /me/gitops/connection), an explicit, reviewable action, and only then can disconnect the now-unreferenced installation. This is the conservative option GITOPS_ARCHITECTURE.md leaves open rather than settles: it never silently breaks the org's sync, at the cost of one extra step when a real installation removal is intended.

Org-scoped the same way connection creation/deletion are: only the caller's org's connection counts toward the block, so this cannot be used to discover or affect another org's binding to the same installation id.

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Path Parameters

installation_id*Installation Id

Response Body

application/json

curl -X DELETE "https://example.com/api/v1/me/gitops/github/installations/0"
Empty
POST/api/v1/me/gitops/source-repos/install/start

Mint a state token bound to the caller's org, tagged purpose="source_repo_connection", and return the GitHub App installation URL — the same mechanism gitops_github_install.py's start_github_install uses for the GitOps connection flow (§7.4: "They can also share the connect mechanism ... precisely so a second, source_repo_connection-purposed flow can mint and consume its own state through the same code").

The Setup URL callback (POST /me/gitops/github/setup, gitops_github_install.py) is shared, unmodified. It resolves an installation's repos regardless of which purpose minted its state because it never checks purpose at all — see this module's own docstring and providers/github/install_state.py's for why that is currently harmless (the real gate is require_admin_or_above on the write endpoints, not this token).

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Response Body

application/json

curl -X POST "https://example.com/api/v1/me/gitops/source-repos/install/start"
{  "url": "string"}
GET/api/v1/me/gitops/source-repos

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Response Body

application/json

curl -X GET "https://example.com/api/v1/me/gitops/source-repos"
[  {    "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",    "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",    "installation_id": 0,    "repo": "string",    "path_prefix": "string",    "created_at": "2019-08-24T14:15:22Z",    "mappings": [],    "event_families": [      "string"    ]  }]
POST/api/v1/me/gitops/source-repos

Connect a repo. Creates only a source_repo_connections row — no gitops_connection row, no service account requirement (§7.4).

409 when repo is already connected for this org (UNIQUE(org_id, repo)).

event_families (#1004) is optional; omitting it preserves the safe default (no source_repo_deployment_configs row, nothing ingested).

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Body for POST /me/gitops/source-repos — connect a repo.

Response Body

application/json

application/json

curl -X POST "https://example.com/api/v1/me/gitops/source-repos" \  -H "Content-Type: application/json" \  -d '{    "installation_id": 1,    "repo": "string"  }'
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",  "installation_id": 0,  "repo": "string",  "path_prefix": "string",  "created_at": "2019-08-24T14:15:22Z",  "mappings": [],  "event_families": [    "string"  ]}
GET/api/v1/me/gitops/source-repos/{connection_id}

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Path Parameters

connection_id*Connection Id
Formatuuid

Response Body

application/json

application/json

curl -X GET "https://example.com/api/v1/me/gitops/source-repos/497f6eca-6276-4993-bfeb-53cbbbba6f08"
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",  "installation_id": 0,  "repo": "string",  "path_prefix": "string",  "created_at": "2019-08-24T14:15:22Z",  "mappings": [],  "event_families": [    "string"  ]}
PATCH/api/v1/me/gitops/source-repos/{connection_id}

Set (replace) which GitHub deployment event families this connection ingests (#1004) — the write path app/services/gitops/ deployment_ingest.py's read (_get_enabled_families) has had no counterpart until now. A non-empty event_families upserts the source_repo_deployment_configs row; event_families: [] deletes it (never stores an empty-array row — see SourceRepoConnectionService's module docstring) without deleting the connection itself.

404 when connection_id doesn't resolve to a connection in this org. 422 when event_families contains anything other than deployment, workflow_run, or release — validated at the schema layer (app/schemas/source_repo_connection.py), not left to the database CHECK constraint alone.

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Path Parameters

connection_id*Connection Id
Formatuuid

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Body for PATCH /me/gitops/source-repos/{connection_id} — set (or replace) which event families this connection ingests. event_families is required here: unlike creation, there is no "leave it as the safe default" meaning for an explicit PATCH call — the caller states the full desired set each time (not a merge/append).

Response Body

application/json

application/json

curl -X PATCH "https://example.com/api/v1/me/gitops/source-repos/497f6eca-6276-4993-bfeb-53cbbbba6f08" \  -H "Content-Type: application/json" \  -d '{    "event_families": [      "string"    ]  }'
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b",  "installation_id": 0,  "repo": "string",  "path_prefix": "string",  "created_at": "2019-08-24T14:15:22Z",  "mappings": [],  "event_families": [    "string"  ]}
DELETE/api/v1/me/gitops/source-repos/{connection_id}

Disconnect a repo. Cascades its service mappings (ON DELETE CASCADE).

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Path Parameters

connection_id*Connection Id
Formatuuid

Response Body

application/json

curl -X DELETE "https://example.com/api/v1/me/gitops/source-repos/497f6eca-6276-4993-bfeb-53cbbbba6f08"
Empty
POST/api/v1/me/gitops/source-repos/{connection_id}/mappings

Map service_name to this connection's repo.

404 when connection_id doesn't resolve to a connection in this org. 409 when service_name is already mapped to a different connection in this org — checked at this layer, not a database constraint, since the uniqueness spans connections (see app/services/gitops/source_repo_service.py's module docstring). Also 409 on the (same-connection) UNIQUE(source_repo_connection_id, service_name) database constraint, for a duplicate mapping onto the same connection.

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Path Parameters

connection_id*Connection Id
Formatuuid

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Body for POST /me/gitops/source-repos/{connection_id}/mappings.

Response Body

application/json

application/json

curl -X POST "https://example.com/api/v1/me/gitops/source-repos/497f6eca-6276-4993-bfeb-53cbbbba6f08/mappings" \  -H "Content-Type: application/json" \  -d '{    "service_name": "string"  }'
{  "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",  "source_repo_connection_id": "39df0546-a20c-47f3-a3c4-aae1c9156827",  "service_name": "string"}
DELETE/api/v1/me/gitops/source-repos/{connection_id}/mappings/{mapping_id}

Authorization

HTTPBearer
AuthorizationBearer <token>

In: header

Path Parameters

connection_id*Connection Id
Formatuuid
mapping_id*Mapping Id
Formatuuid

Response Body

application/json

curl -X DELETE "https://example.com/api/v1/me/gitops/source-repos/497f6eca-6276-4993-bfeb-53cbbbba6f08/mappings/497f6eca-6276-4993-bfeb-53cbbbba6f08"
Empty