Gitops
/api/v1/gitops/github/webhookReceive 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
Response Body
application/json
application/json
curl -X POST "https://example.com/api/v1/gitops/github/webhook"{}/api/v1/me/gitops/importReturn {"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 In: header
Response Body
application/json
curl -X GET "https://example.com/api/v1/me/gitops/import"{}/api/v1/me/gitops/connectionThe org's connection, or 404 when it has none.
Authorization
HTTPBearer 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"}/api/v1/me/gitops/connectionDelete 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 In: header
Response Body
curl -X DELETE "https://example.com/api/v1/me/gitops/connection"/api/v1/me/gitops/connectionCreate-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 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"}/api/v1/me/gitops/sync-runsSync runs for the active organization, newest first.
Authorization
HTTPBearer In: header
Query Parameters
Filter by run status
Opaque cursor from a previous page's next_cursor
Page size, max 100
1 <= value <= 10020Response 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"}/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 In: header
Path Parameters
uuidResponse 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" } ]}/api/v1/me/gitops/sync-runs/{run_id}/eventsThe 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 In: header
Path Parameters
uuidQuery Parameters
Opaque cursor from a previous page's next_cursor
Page size, max 100
1 <= value <= 10020Response 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"}/api/v1/me/gitops/github/install/startMint 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 In: header
Response Body
application/json
curl -X POST "https://example.com/api/v1/me/gitops/github/install/start"{ "url": "string"}/api/v1/me/gitops/github/setupConfirm 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 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"}/api/v1/me/gitops/github/installationsWhat 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 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" }]/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 In: header
Path Parameters
Response Body
application/json
curl -X DELETE "https://example.com/api/v1/me/gitops/github/installations/0"/api/v1/me/gitops/source-repos/install/startMint 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 In: header
Response Body
application/json
curl -X POST "https://example.com/api/v1/me/gitops/source-repos/install/start"{ "url": "string"}/api/v1/me/gitops/source-reposAuthorization
HTTPBearer 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" ] }]/api/v1/me/gitops/source-reposConnect 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 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" ]}/api/v1/me/gitops/source-repos/{connection_id}Authorization
HTTPBearer In: header
Path Parameters
uuidResponse 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" ]}/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 In: header
Path Parameters
uuidRequest 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" ]}/api/v1/me/gitops/source-repos/{connection_id}Disconnect a repo. Cascades its service mappings
(ON DELETE CASCADE).
Authorization
HTTPBearer In: header
Path Parameters
uuidResponse Body
application/json
curl -X DELETE "https://example.com/api/v1/me/gitops/source-repos/497f6eca-6276-4993-bfeb-53cbbbba6f08"/api/v1/me/gitops/source-repos/{connection_id}/mappingsMap 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 In: header
Path Parameters
uuidRequest 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"}/api/v1/me/gitops/source-repos/{connection_id}/mappings/{mapping_id}Authorization
HTTPBearer In: header
Path Parameters
uuiduuidResponse 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"