Skip to main content

Integrations

The integrations API manages provider connections across every provider in the integration catalog — not just GitHub. It covers the generic per-provider connect route (OAuth or token paste), scope templates for the credential broker, project repo links, the inbound delivery log with replay, admission grants for account-shared connections, and the GitHub token broker that exchanges your Orunbase credential for a short-lived, repo-scoped installation token. The GitHub install flow, sharing model, and event taxonomy are described in GitHub integration; the dual-posture connect model in Connecting providers.

Connecting GitHub is gated by the feature.integrations.github entitlement; repo links by limit.repo_links (see Plans and entitlements). Ids are prefixed: connections int_…, inbound deliveries igd_…, repo links repl_….

Endpoints

MethodPathPermissionDescription
POST/v1/organizations/{orgId}/integrations/github/connectorganization.integration.connectStart the App install flow
POST/v1/organizations/{orgId}/integrations/{provider}/connectorganization.integration.connectGeneric connect — OAuth, or token paste with parentToken (see below)
GET/v1/organizations/{orgId}/integrations/providers/{provider}/scope-templatesorganization.integration.readList credential-broker scope templates (declared + custom, retired included)
POST/v1/organizations/{orgId}/integrations/providers/{provider}/scope-templatesorganization.integration.manageCreate a custom scope template
PATCH/v1/organizations/{orgId}/integrations/providers/{provider}/scope-templates/{templateId}organization.integration.manageUpdate a template — displayName, description, status: "active" | "retired" (retirement is soft; no hard delete exists). templateId matches [a-z0-9-]+
GET/v1/organizations/{orgId}/integrationsorganization.integration.readList connections
GET/v1/organizations/{orgId}/integrations/{connectionId}organization.integration.readGet a connection
PATCH/v1/organizations/{orgId}/integrations/{connectionId}organization.integration.manageUpdate the connection (shareMode)
DELETE/v1/organizations/{orgId}/integrations/{connectionId}organization.integration.manageRevoke a connection
POST/v1/organizations/{orgId}/integrations/github/tokenorganization.integration.token.issueBroker a scoped installation token
GET/v1/organizations/{orgId}/integrations/{connectionId}/repositoriesorganization.integration.readBrowse repositories the installation can see
GET/v1/organizations/{orgId}/integrations/{connectionId}/deliveriesorganization.integration.readList inbound deliveries
POST/v1/organizations/{orgId}/integrations/{connectionId}/deliveries/{deliveryId}/replayorganization.integration.manageRe-run normalize/emit from the stored delivery
GET/v1/organizations/{orgId}/integrations/{connectionId}/grantsorganization.integration.manageList admission grants
POST/v1/organizations/{orgId}/integrations/{connectionId}/grantsorganization.integration.manageAdmit a workspace to a shared connection
DELETE/v1/organizations/{orgId}/integrations/{connectionId}/grants/{workspaceOrgId}organization.integration.manageRevoke an admission grant
GET/v1/organizations/{orgId}/projects/{projectId}/repo-linksorganization.integration.readList a project's repo links
POST/v1/organizations/{orgId}/projects/{projectId}/repo-linksproject.repo_link.writeLink a repository to the project
PATCH/v1/organizations/{orgId}/projects/{projectId}/repo-links/{repoLinkId}project.repo_link.writeUpdate branchEnvMap / defaultBranch
DELETE/v1/organizations/{orgId}/projects/{projectId}/repo-links/{repoLinkId}project.repo_link.writeUnlink the repository
GET/ingress/github/setup— (provider only)App install callback — see below
POST/ingress/github/webhook— (provider only)Inbound GitHub webhook — see below

Connect GitHub

Connect creates a pending connection and returns an installUrl carrying a signed single-use state. Open it (the console uses a popup); GitHub redirects the installing user back through the setup ingress, which verifies the state and activates the connection.

curl -X POST https://api.orunbase.com/v1/organizations/org_7c1f4b2a9d3e48f0a6b5c4d3e2f1a0b9/integrations/github/connect \
-H "Authorization: Bearer $ORUN_CLOUD_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "displayName": "acme-github" }'
{
"data": {
"connection": {
"id": "int_5b4a39281706f5e4d3c2b1a09f8e7d6c",
"orgId": "org_7c1f4b2a9d3e48f0a6b5c4d3e2f1a0b9",
"provider": "github",
"status": "pending",
"scope": "account",
"shareMode": "auto",
"displayName": "acme-github",
"externalAccountLogin": null,
"externalAccountType": null,
"repositorySelection": null,
"createdBy": "usr_3c2b1a0f9e8d7c6b5a49382716050403",
"connectedAt": null,
"revokedAt": null,
"suspendedAt": null,
"createdAt": "2026-07-02T09:50:00.000Z",
"updatedAt": "2026-07-02T09:50:00.000Z"
},
"installUrl": "https://github.com/apps/orun-cloud/installations/new?state=…"
},
"meta": { "requestId": "req_5f2d1c0b9a8e7f6d5c4b3a31", "cursor": null }
}

Account-owned connections are shared with child workspaces: shareMode: "auto" admits every workspace under the account; switch to "granted" (via PATCH) to admit only workspaces with an explicit grant. Child workspaces see the shared row with inherited: true plus sharedByWorkspaceRef/sharedByName provenance.

Connect any provider (token paste)

POST /v1/organizations/{orgId}/integrations/{provider}/connect is the generic connect. Without a token it starts the provider's OAuth flow (a pending connection plus an authorize URL); with a parentToken it takes the token-paste path even when OAuth is live — the token is verified against the provider's API in the same request and the connection returns active immediately, no callback:

curl -X POST https://api.orunbase.com/v1/organizations/org_7c1f4b2a9d3e48f0a6b5c4d3e2f1a0b9/integrations/supabase/connect \
-H "Authorization: Bearer $ORUN_CLOUD_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "parentToken": "sbp_…", "displayName": "acme-supabase", "scope": "account" }'

Failures are 412 precondition_failed with a structured reason (no_org_visible when the token verifies but sees no provider org; gate: "supabase_custody", reason: "not_configured" when custody is not configured). Pasted tokens are never echoed in errors. See Connecting providers for re-authorization and health semantics.

Cross-workspace rule: the same provider account (a Cloudflare account, a Supabase organization) may back one connection per workspace — the duplicate-binding guard is org-scoped, so another workspace's binding never blocks yours. The exceptions are webhook-routed identities, which stay globally unique across all workspaces: a GitHub App installation and a Slack team_id each bind exactly one tenant, because one inbound event must route to one workspace.

Broker a scoped GitHub token

POST …/integrations/github/token exchanges your control-plane credential for a short-lived installation token. Every requested repository must match an active repo link in a project you can access, and requested permissions must be a subset of the App's grant — deny-by-default.

curl -X POST https://api.orunbase.com/v1/organizations/org_7c1f4b2a9d3e48f0a6b5c4d3e2f1a0b9/integrations/github/token \
-H "Authorization: Bearer $ORUN_CLOUD_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"repositories": ["456789123"],
"permissions": { "contents": "read", "checks": "write" }
}'
{
"data": {
"token": "ghs_…",
"expiresAt": "2026-07-02T10:55:00.000Z",
"repositories": ["456789123"],
"permissions": { "contents": "read", "checks": "write" }
},
"meta": { "requestId": "req_5f2d1c0b9a8e7f6d5c4b3a32", "cursor": null }
}
warning

The token is revealed exactly once, never cached or logged platform-side, and expires within 1 hour. Treat it like a password and let it expire.

Browse candidate repositories via GET …/integrations/{connectionId}/repositories (optional query substring filter), then create the link. branchEnvMap maps provider branches to environment slugs, validated against the project's live environments.

const { repoLink } = await client.integrations.createRepoLink(
"org_7c1f4b2a9d3e48f0a6b5c4d3e2f1a0b9",
"prj_3e2d1c0b4a5968f7a6b5c4d3e2f1a0b9",
{
connectionId: "int_5b4a39281706f5e4d3c2b1a09f8e7d6c",
repoExternalId: "456789123",
repoFullName: "acme/storefront",
defaultBranch: "main",
branchEnvMap: { main: "prod", staging: "stage" },
},
{ idempotencyKey: "link-storefront-1" },
);

Inbound deliveries attributed to a connection are inspectable at GET …/{connectionId}/deliveries (status received | attributed | emitted | skipped | failed, signatureOk, safe failureReason); POST …/deliveries/{deliveryId}/replay re-runs normalization from the persisted inbox row — it never re-trusts the wire.

Provider ingress (provider only)

Two unauthenticated routes exist solely for GitHub to call — do not call them yourself:

RouteAuth mechanism
GET /ingress/github/setupSigned single-use state in the query string — the install-callback redirect of the installing user's browser after an App install. Verified by the state secret's owner; no bearer token.
POST /ingress/github/webhookx-hub-signature-256 HMAC over the raw body, verified against the App webhook secret before any parse (fails closed). x-github-delivery and x-github-event headers are forwarded with the raw bytes.

Both are allowlist-routed and rate-limited per source at the edge.