API reference
Every endpoint of Branchscope's API, version 1, from its OpenAPI document. The conventions (errors, paging, safe retries, rate limits) are on the API page.
branchscope.runs
GET /v1/actions/id-token
An OIDC identity token for the running job · branchscope.runs.idToken()
For a job's steps, with its job token (BRANCHSCOPE_ID_TOKEN_REQUEST_TOKEN) at BRANCHSCOPE_ID_TOKEN_REQUEST_URL: a JSON Web Token signed with RS256 by this server's issuer, valid for 10 minutes, to trade with a cloud provider for short-lived credentials. Only while the job runs, for a job with permissions: id-token: write, in a run whose secrets aren't withheld, once its environment's protection passed. Off (unavailable) without OIDC_SIGNING_KEY.
No credential needed; what you see depends on the one you send.
OAuth apps can't call this.
Parameters
audiencequery | string · Who the token is for: sts.amazonaws.com for AWS, api://AzureADTokenExchange for Azure, the workload identity provider's URL for Google Cloud. The repository owner's URL if left out. |
Response 200
{
value: string // The token: never log it; it's masked in the job's log.
}Errors
400 invalid_request · 403 forbidden · 429 rate_limited · 500 internal · 503 unavailable
Example
curl "$BRANCHSCOPE_API/v1/actions/id-token" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.runs.idToken();GET /v1/repos/{owner}/{name}/runs
A repository's workflow runs, newest first · branchscope.runs.list()
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
changequery | integer · Only this change's runs. |
triggerquery | "push" | "change" | "schedule" | "manual" | "tag" | "release" · Only runs this trigger started. |
Response 200
{
data: ({
id: integer
workflow: string // Its file, e.g. .branchscope/workflows/ci.yml.
name: string
sha: string // A commit ID (40 or 64 hex characters).
ref: string // The branch it ran for, or the tag for tag and release runs.
trigger: "push" | "change" | "schedule" | "manual" | "tag" | "release"
change: integer | null
status: "queued" | "running" | "passed" | "failed" | "cancelled"
actor: string | null // Who pushed, opened the change or started it; null for scheduled runs.
agent: string | null // The agent or app that set it off for its actor, or whose change or run it's for; null for a person's own run.
secrets: "given" | "withheld" | "approved" // given: its jobs get the secrets they name and identity tokens. withheld (see withheldBecause): jobs that name secrets or ask for identity tokens wait until an administrator approves the run, as a person. approved: an administrator did (secretsApprovedBy).
withheldBecause: "agent" | "access" | null // Why its secrets are withheld (or were, until approved): agent, set off by an agent or app, or for an agent's change, and not at the head of a branch or tag agents can't update; access, set off by someone without write access to the repository. null when given.
secretsApprovedBy: string | null
createdAt: string (time)
startedAt: string (time) | null
completedAt: string (time) | null
jobs: RunJob[]
artifacts: Artifact[] // What its jobs kept (M17) and hasn't expired yet.
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 401 unauthorized · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/runs" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.runs.list({ owner: "acme", name: "web" });POST /v1/repos/{owner}/{name}/runs
Run a workflow by hand · branchscope.runs.create()
For workflows with `on: manual`, at the head of a branch. People and agents with write access.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `changes:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
workflow: string // Its file, e.g. .branchscope/workflows/ci.yml.
ref: string // A branch.
}Response 201
{
id: integer
workflow: string // Its file, e.g. .branchscope/workflows/ci.yml.
name: string
sha: string // A commit ID (40 or 64 hex characters).
ref: string // The branch it ran for, or the tag for tag and release runs.
trigger: "push" | "change" | "schedule" | "manual" | "tag" | "release"
change: integer | null
status: "queued" | "running" | "passed" | "failed" | "cancelled"
actor: string | null // Who pushed, opened the change or started it; null for scheduled runs.
agent: string | null // The agent or app that set it off for its actor, or whose change or run it's for; null for a person's own run.
secrets: "given" | "withheld" | "approved" // given: its jobs get the secrets they name and identity tokens. withheld (see withheldBecause): jobs that name secrets or ask for identity tokens wait until an administrator approves the run, as a person. approved: an administrator did (secretsApprovedBy).
withheldBecause: "agent" | "access" | null // Why its secrets are withheld (or were, until approved): agent, set off by an agent or app, or for an agent's change, and not at the head of a branch or tag agents can't update; access, set off by someone without write access to the repository. null when given.
secretsApprovedBy: string | null
createdAt: string (time)
startedAt: string (time) | null
completedAt: string (time) | null
jobs: ({
id: integer
name: string // The job's name, with its matrix values, e.g. test (node: 24).
status: "waiting" | "queued" | "running" | "passed" | "failed" | "cancelled" | "timed_out" // waiting: for the jobs it needs, for its environment's reviewers or wait timer (see review), or (in a run whose secrets are withheld) for an administrator to approve the secrets it names.
runsOn: string
size: "2cpu" | "4cpu" | "8cpu" | null // On hosted runners: 2cpu (2 vCPU, 4 GB), 4cpu or 8cpu. null on self-hosted runners.
needs: string[]
check: string // The name of its check on the commit.
runner: string | null // The self-hosted runner that ran it, or "Hosted (EU)" for Branchscope's own; null until it starts.
exitCode: integer | null
environment: string | null // The environment it deploys to (environment: in the workflow), or null.
secrets: string[] // The secrets it names (secrets: in the workflow), never their values.
secretsGiven: ({
name: string
level: "organization" | "repository" | "environment"
})[] // The secrets it was given when it started, by name and where each came from (an environment's wins over its repository's, a repository's over its organization's); never their values.
idToken: boolean // It may ask for OIDC identity tokens (permissions: id-token: write).
idTokens: ({
audience: string
issuedAt: string (time)
expiresAt: string (time)
})[] // The identity tokens it was issued, by audience; never the tokens.
review: DeploymentReview | null // Its environment's reviewers and wait timer (M29), once they held it; null when they never did.
secretsApprovedBy: string | null // The administrator whose approval of its deployment, as a reviewer, also approved its withheld secrets (M29).
cache: JobCache | null // Its cache (M17): null when the workflow names none.
summary: string // Why Branchscope ended it, when it did: refused before its first step (an environment's protection, hosted runners), or cancelled. Empty otherwise.
startedAt: string (time) | null
completedAt: string (time) | null
})[]
artifacts: ({
id: integer
name: string
job: string // The job that kept it, with its matrix values.
jobId: integer
sizeBytes: integer
createdAt: string (time)
expiresAt: string (time) // When it's deleted: 30 days after it was kept unless its retention-days says otherwise, at most 90.
})[] // What its jobs kept (M17) and hasn't expired yet.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/runs" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"workflow":"workflow","ref":"main"}'await branchscope.runs.create({ owner: "acme", name: "web", workflow: "workflow", ref: "main" });GET /v1/repos/{owner}/{name}/runs/{id}
A workflow run and its jobs · branchscope.runs.get()
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
idpath, required | integer |
Response 200
{
id: integer
workflow: string // Its file, e.g. .branchscope/workflows/ci.yml.
name: string
sha: string // A commit ID (40 or 64 hex characters).
ref: string // The branch it ran for, or the tag for tag and release runs.
trigger: "push" | "change" | "schedule" | "manual" | "tag" | "release"
change: integer | null
status: "queued" | "running" | "passed" | "failed" | "cancelled"
actor: string | null // Who pushed, opened the change or started it; null for scheduled runs.
agent: string | null // The agent or app that set it off for its actor, or whose change or run it's for; null for a person's own run.
secrets: "given" | "withheld" | "approved" // given: its jobs get the secrets they name and identity tokens. withheld (see withheldBecause): jobs that name secrets or ask for identity tokens wait until an administrator approves the run, as a person. approved: an administrator did (secretsApprovedBy).
withheldBecause: "agent" | "access" | null // Why its secrets are withheld (or were, until approved): agent, set off by an agent or app, or for an agent's change, and not at the head of a branch or tag agents can't update; access, set off by someone without write access to the repository. null when given.
secretsApprovedBy: string | null
createdAt: string (time)
startedAt: string (time) | null
completedAt: string (time) | null
jobs: ({
id: integer
name: string // The job's name, with its matrix values, e.g. test (node: 24).
status: "waiting" | "queued" | "running" | "passed" | "failed" | "cancelled" | "timed_out" // waiting: for the jobs it needs, for its environment's reviewers or wait timer (see review), or (in a run whose secrets are withheld) for an administrator to approve the secrets it names.
runsOn: string
size: "2cpu" | "4cpu" | "8cpu" | null // On hosted runners: 2cpu (2 vCPU, 4 GB), 4cpu or 8cpu. null on self-hosted runners.
needs: string[]
check: string // The name of its check on the commit.
runner: string | null // The self-hosted runner that ran it, or "Hosted (EU)" for Branchscope's own; null until it starts.
exitCode: integer | null
environment: string | null // The environment it deploys to (environment: in the workflow), or null.
secrets: string[] // The secrets it names (secrets: in the workflow), never their values.
secretsGiven: ({
name: string
level: "organization" | "repository" | "environment"
})[] // The secrets it was given when it started, by name and where each came from (an environment's wins over its repository's, a repository's over its organization's); never their values.
idToken: boolean // It may ask for OIDC identity tokens (permissions: id-token: write).
idTokens: ({
audience: string
issuedAt: string (time)
expiresAt: string (time)
})[] // The identity tokens it was issued, by audience; never the tokens.
review: DeploymentReview | null // Its environment's reviewers and wait timer (M29), once they held it; null when they never did.
secretsApprovedBy: string | null // The administrator whose approval of its deployment, as a reviewer, also approved its withheld secrets (M29).
cache: JobCache | null // Its cache (M17): null when the workflow names none.
summary: string // Why Branchscope ended it, when it did: refused before its first step (an environment's protection, hosted runners), or cancelled. Empty otherwise.
startedAt: string (time) | null
completedAt: string (time) | null
})[]
artifacts: ({
id: integer
name: string
job: string // The job that kept it, with its matrix values.
jobId: integer
sizeBytes: integer
createdAt: string (time)
expiresAt: string (time) // When it's deleted: 30 days after it was kept unless its retention-days says otherwise, at most 90.
})[] // What its jobs kept (M17) and hasn't expired yet.
}Errors
400 invalid_request · 401 unauthorized · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/runs/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.runs.get({ owner: "acme", name: "web", id: 7 });POST /v1/repos/{owner}/{name}/runs/{id}/approve
Approve a run's secrets, and run its jobs that wait for them · branchscope.runs.approve()
For a run whose secrets and identity tokens are withheld (set off by an agent or app, or by someone without write access): its jobs that name secrets or ask for identity tokens get them and run. Read its workflow at the run's commit first: it can do anything with them. Repository administrators, with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
idpath, required | integer |
Response 200
{
id: integer
workflow: string // Its file, e.g. .branchscope/workflows/ci.yml.
name: string
sha: string // A commit ID (40 or 64 hex characters).
ref: string // The branch it ran for, or the tag for tag and release runs.
trigger: "push" | "change" | "schedule" | "manual" | "tag" | "release"
change: integer | null
status: "queued" | "running" | "passed" | "failed" | "cancelled"
actor: string | null // Who pushed, opened the change or started it; null for scheduled runs.
agent: string | null // The agent or app that set it off for its actor, or whose change or run it's for; null for a person's own run.
secrets: "given" | "withheld" | "approved" // given: its jobs get the secrets they name and identity tokens. withheld (see withheldBecause): jobs that name secrets or ask for identity tokens wait until an administrator approves the run, as a person. approved: an administrator did (secretsApprovedBy).
withheldBecause: "agent" | "access" | null // Why its secrets are withheld (or were, until approved): agent, set off by an agent or app, or for an agent's change, and not at the head of a branch or tag agents can't update; access, set off by someone without write access to the repository. null when given.
secretsApprovedBy: string | null
createdAt: string (time)
startedAt: string (time) | null
completedAt: string (time) | null
jobs: ({
id: integer
name: string // The job's name, with its matrix values, e.g. test (node: 24).
status: "waiting" | "queued" | "running" | "passed" | "failed" | "cancelled" | "timed_out" // waiting: for the jobs it needs, for its environment's reviewers or wait timer (see review), or (in a run whose secrets are withheld) for an administrator to approve the secrets it names.
runsOn: string
size: "2cpu" | "4cpu" | "8cpu" | null // On hosted runners: 2cpu (2 vCPU, 4 GB), 4cpu or 8cpu. null on self-hosted runners.
needs: string[]
check: string // The name of its check on the commit.
runner: string | null // The self-hosted runner that ran it, or "Hosted (EU)" for Branchscope's own; null until it starts.
exitCode: integer | null
environment: string | null // The environment it deploys to (environment: in the workflow), or null.
secrets: string[] // The secrets it names (secrets: in the workflow), never their values.
secretsGiven: ({
name: string
level: "organization" | "repository" | "environment"
})[] // The secrets it was given when it started, by name and where each came from (an environment's wins over its repository's, a repository's over its organization's); never their values.
idToken: boolean // It may ask for OIDC identity tokens (permissions: id-token: write).
idTokens: ({
audience: string
issuedAt: string (time)
expiresAt: string (time)
})[] // The identity tokens it was issued, by audience; never the tokens.
review: DeploymentReview | null // Its environment's reviewers and wait timer (M29), once they held it; null when they never did.
secretsApprovedBy: string | null // The administrator whose approval of its deployment, as a reviewer, also approved its withheld secrets (M29).
cache: JobCache | null // Its cache (M17): null when the workflow names none.
summary: string // Why Branchscope ended it, when it did: refused before its first step (an environment's protection, hosted runners), or cancelled. Empty otherwise.
startedAt: string (time) | null
completedAt: string (time) | null
})[]
artifacts: ({
id: integer
name: string
job: string // The job that kept it, with its matrix values.
jobId: integer
sizeBytes: integer
createdAt: string (time)
expiresAt: string (time) // When it's deleted: 30 days after it was kept unless its retention-days says otherwise, at most 90.
})[] // What its jobs kept (M17) and hasn't expired yet.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/runs/7/approve" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.runs.approve({ owner: "acme", name: "web", id: 7 });GET /v1/repos/{owner}/{name}/runs/{id}/artifacts/{artifact}
Download an artifact · branchscope.runs.artifact()
A .tar.gz of the paths its job kept (M17), until it expires. Anyone who can read the repository, agents too. Files a step wrote are in it as they were: a secret written to one is in it too.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
idpath, required | integer |
artifactpath, required | integer · The artifact's id, from the run's artifacts. |
Response 200
application/gzip · The artifact, as <name>.tar.gz.Errors
400 invalid_request · 401 unauthorized · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/runs/7/artifacts/artifact" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.runs.artifact({ owner: "acme", name: "web", id: 7, artifact: 1 });POST /v1/repos/{owner}/{name}/runs/{id}/cancel
Cancel a run · branchscope.runs.cancel()
Its queued jobs are cancelled at once, and running ones at their runner's next heartbeat.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `changes:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
idpath, required | integer |
Response 201
{
id: integer
workflow: string // Its file, e.g. .branchscope/workflows/ci.yml.
name: string
sha: string // A commit ID (40 or 64 hex characters).
ref: string // The branch it ran for, or the tag for tag and release runs.
trigger: "push" | "change" | "schedule" | "manual" | "tag" | "release"
change: integer | null
status: "queued" | "running" | "passed" | "failed" | "cancelled"
actor: string | null // Who pushed, opened the change or started it; null for scheduled runs.
agent: string | null // The agent or app that set it off for its actor, or whose change or run it's for; null for a person's own run.
secrets: "given" | "withheld" | "approved" // given: its jobs get the secrets they name and identity tokens. withheld (see withheldBecause): jobs that name secrets or ask for identity tokens wait until an administrator approves the run, as a person. approved: an administrator did (secretsApprovedBy).
withheldBecause: "agent" | "access" | null // Why its secrets are withheld (or were, until approved): agent, set off by an agent or app, or for an agent's change, and not at the head of a branch or tag agents can't update; access, set off by someone without write access to the repository. null when given.
secretsApprovedBy: string | null
createdAt: string (time)
startedAt: string (time) | null
completedAt: string (time) | null
jobs: ({
id: integer
name: string // The job's name, with its matrix values, e.g. test (node: 24).
status: "waiting" | "queued" | "running" | "passed" | "failed" | "cancelled" | "timed_out" // waiting: for the jobs it needs, for its environment's reviewers or wait timer (see review), or (in a run whose secrets are withheld) for an administrator to approve the secrets it names.
runsOn: string
size: "2cpu" | "4cpu" | "8cpu" | null // On hosted runners: 2cpu (2 vCPU, 4 GB), 4cpu or 8cpu. null on self-hosted runners.
needs: string[]
check: string // The name of its check on the commit.
runner: string | null // The self-hosted runner that ran it, or "Hosted (EU)" for Branchscope's own; null until it starts.
exitCode: integer | null
environment: string | null // The environment it deploys to (environment: in the workflow), or null.
secrets: string[] // The secrets it names (secrets: in the workflow), never their values.
secretsGiven: ({
name: string
level: "organization" | "repository" | "environment"
})[] // The secrets it was given when it started, by name and where each came from (an environment's wins over its repository's, a repository's over its organization's); never their values.
idToken: boolean // It may ask for OIDC identity tokens (permissions: id-token: write).
idTokens: ({
audience: string
issuedAt: string (time)
expiresAt: string (time)
})[] // The identity tokens it was issued, by audience; never the tokens.
review: DeploymentReview | null // Its environment's reviewers and wait timer (M29), once they held it; null when they never did.
secretsApprovedBy: string | null // The administrator whose approval of its deployment, as a reviewer, also approved its withheld secrets (M29).
cache: JobCache | null // Its cache (M17): null when the workflow names none.
summary: string // Why Branchscope ended it, when it did: refused before its first step (an environment's protection, hosted runners), or cancelled. Empty otherwise.
startedAt: string (time) | null
completedAt: string (time) | null
})[]
artifacts: ({
id: integer
name: string
job: string // The job that kept it, with its matrix values.
jobId: integer
sizeBytes: integer
createdAt: string (time)
expiresAt: string (time) // When it's deleted: 30 days after it was kept unless its retention-days says otherwise, at most 90.
})[] // What its jobs kept (M17) and hasn't expired yet.
}Errors
400 invalid_request · 401 unauthorized · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/runs/7/cancel" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.runs.cancel({ owner: "acme", name: "web", id: 7 });GET /v1/repos/{owner}/{name}/runs/{id}/jobs/{job}/log
A job's log · branchscope.runs.jobLog()
From `after` (a character offset) on. While the job runs, ask again with `next` to follow it; `done` says it finished.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
idpath, required | integer |
jobpath, required | integer |
afterquery | integer · Characters already read. |
Response 200
{
text: string
next: integer
done: boolean
}Errors
400 invalid_request · 401 unauthorized · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/runs/7/jobs/job/log" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.runs.jobLog({ owner: "acme", name: "web", id: 7, job: 1 });POST /v1/repos/{owner}/{name}/runs/{id}/rerun
Run a workflow again at the same commit · branchscope.runs.rerun()
A new run of the same workflow file at the same commit; its checks replace the old ones.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `changes:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
idpath, required | integer |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Response 201
{
id: integer
workflow: string // Its file, e.g. .branchscope/workflows/ci.yml.
name: string
sha: string // A commit ID (40 or 64 hex characters).
ref: string // The branch it ran for, or the tag for tag and release runs.
trigger: "push" | "change" | "schedule" | "manual" | "tag" | "release"
change: integer | null
status: "queued" | "running" | "passed" | "failed" | "cancelled"
actor: string | null // Who pushed, opened the change or started it; null for scheduled runs.
agent: string | null // The agent or app that set it off for its actor, or whose change or run it's for; null for a person's own run.
secrets: "given" | "withheld" | "approved" // given: its jobs get the secrets they name and identity tokens. withheld (see withheldBecause): jobs that name secrets or ask for identity tokens wait until an administrator approves the run, as a person. approved: an administrator did (secretsApprovedBy).
withheldBecause: "agent" | "access" | null // Why its secrets are withheld (or were, until approved): agent, set off by an agent or app, or for an agent's change, and not at the head of a branch or tag agents can't update; access, set off by someone without write access to the repository. null when given.
secretsApprovedBy: string | null
createdAt: string (time)
startedAt: string (time) | null
completedAt: string (time) | null
jobs: ({
id: integer
name: string // The job's name, with its matrix values, e.g. test (node: 24).
status: "waiting" | "queued" | "running" | "passed" | "failed" | "cancelled" | "timed_out" // waiting: for the jobs it needs, for its environment's reviewers or wait timer (see review), or (in a run whose secrets are withheld) for an administrator to approve the secrets it names.
runsOn: string
size: "2cpu" | "4cpu" | "8cpu" | null // On hosted runners: 2cpu (2 vCPU, 4 GB), 4cpu or 8cpu. null on self-hosted runners.
needs: string[]
check: string // The name of its check on the commit.
runner: string | null // The self-hosted runner that ran it, or "Hosted (EU)" for Branchscope's own; null until it starts.
exitCode: integer | null
environment: string | null // The environment it deploys to (environment: in the workflow), or null.
secrets: string[] // The secrets it names (secrets: in the workflow), never their values.
secretsGiven: ({
name: string
level: "organization" | "repository" | "environment"
})[] // The secrets it was given when it started, by name and where each came from (an environment's wins over its repository's, a repository's over its organization's); never their values.
idToken: boolean // It may ask for OIDC identity tokens (permissions: id-token: write).
idTokens: ({
audience: string
issuedAt: string (time)
expiresAt: string (time)
})[] // The identity tokens it was issued, by audience; never the tokens.
review: DeploymentReview | null // Its environment's reviewers and wait timer (M29), once they held it; null when they never did.
secretsApprovedBy: string | null // The administrator whose approval of its deployment, as a reviewer, also approved its withheld secrets (M29).
cache: JobCache | null // Its cache (M17): null when the workflow names none.
summary: string // Why Branchscope ended it, when it did: refused before its first step (an environment's protection, hosted runners), or cancelled. Empty otherwise.
startedAt: string (time) | null
completedAt: string (time) | null
})[]
artifacts: ({
id: integer
name: string
job: string // The job that kept it, with its matrix values.
jobId: integer
sizeBytes: integer
createdAt: string (time)
expiresAt: string (time) // When it's deleted: 30 days after it was kept unless its retention-days says otherwise, at most 90.
})[] // What its jobs kept (M17) and hasn't expired yet.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/runs/7/rerun" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.runs.rerun({ owner: "acme", name: "web", id: 7 });POST /v1/repos/{owner}/{name}/runs/{id}/review
Approve or reject the run's deployments waiting for review · branchscope.runs.review()
Each job deploying to an environment with required reviewers waits for one of them (M29). This decides every one in the run that you may decide, or those to the environments named. A listed reviewer of the environment, as it is now, who can read the repository, with a session or a personal write token: never an agent or app. Not whoever set off the run when the environment prevents self-review (403, with the reason). Approved: the job's wait timer starts, and it runs once that's over; a repository administrator's approval also approves that job's withheld secrets. Rejected: the job fails with the reviewer's name and comment, its deployment fails, and the jobs that need it are cancelled. The first decision counts (409 after).
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
idpath, required | integer |
Request body
{
decision: "approve" | "reject"
comment?: string // Shown on the run and the audit log; a rejection's is the job's reason.
environments?: string[] // Only the jobs deploying to these.
}Response 200
{
id: integer
workflow: string // Its file, e.g. .branchscope/workflows/ci.yml.
name: string
sha: string // A commit ID (40 or 64 hex characters).
ref: string // The branch it ran for, or the tag for tag and release runs.
trigger: "push" | "change" | "schedule" | "manual" | "tag" | "release"
change: integer | null
status: "queued" | "running" | "passed" | "failed" | "cancelled"
actor: string | null // Who pushed, opened the change or started it; null for scheduled runs.
agent: string | null // The agent or app that set it off for its actor, or whose change or run it's for; null for a person's own run.
secrets: "given" | "withheld" | "approved" // given: its jobs get the secrets they name and identity tokens. withheld (see withheldBecause): jobs that name secrets or ask for identity tokens wait until an administrator approves the run, as a person. approved: an administrator did (secretsApprovedBy).
withheldBecause: "agent" | "access" | null // Why its secrets are withheld (or were, until approved): agent, set off by an agent or app, or for an agent's change, and not at the head of a branch or tag agents can't update; access, set off by someone without write access to the repository. null when given.
secretsApprovedBy: string | null
createdAt: string (time)
startedAt: string (time) | null
completedAt: string (time) | null
jobs: ({
id: integer
name: string // The job's name, with its matrix values, e.g. test (node: 24).
status: "waiting" | "queued" | "running" | "passed" | "failed" | "cancelled" | "timed_out" // waiting: for the jobs it needs, for its environment's reviewers or wait timer (see review), or (in a run whose secrets are withheld) for an administrator to approve the secrets it names.
runsOn: string
size: "2cpu" | "4cpu" | "8cpu" | null // On hosted runners: 2cpu (2 vCPU, 4 GB), 4cpu or 8cpu. null on self-hosted runners.
needs: string[]
check: string // The name of its check on the commit.
runner: string | null // The self-hosted runner that ran it, or "Hosted (EU)" for Branchscope's own; null until it starts.
exitCode: integer | null
environment: string | null // The environment it deploys to (environment: in the workflow), or null.
secrets: string[] // The secrets it names (secrets: in the workflow), never their values.
secretsGiven: ({
name: string
level: "organization" | "repository" | "environment"
})[] // The secrets it was given when it started, by name and where each came from (an environment's wins over its repository's, a repository's over its organization's); never their values.
idToken: boolean // It may ask for OIDC identity tokens (permissions: id-token: write).
idTokens: ({
audience: string
issuedAt: string (time)
expiresAt: string (time)
})[] // The identity tokens it was issued, by audience; never the tokens.
review: DeploymentReview | null // Its environment's reviewers and wait timer (M29), once they held it; null when they never did.
secretsApprovedBy: string | null // The administrator whose approval of its deployment, as a reviewer, also approved its withheld secrets (M29).
cache: JobCache | null // Its cache (M17): null when the workflow names none.
summary: string // Why Branchscope ended it, when it did: refused before its first step (an environment's protection, hosted runners), or cancelled. Empty otherwise.
startedAt: string (time) | null
completedAt: string (time) | null
})[]
artifacts: ({
id: integer
name: string
job: string // The job that kept it, with its matrix values.
jobId: integer
sizeBytes: integer
createdAt: string (time)
expiresAt: string (time) // When it's deleted: 30 days after it was kept unless its retention-days says otherwise, at most 90.
})[] // What its jobs kept (M17) and hasn't expired yet.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/runs/7/review" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"decision":"approve"}'await branchscope.runs.review({ owner: "acme", name: "web", id: 7, decision: "approve" });POST /v1/repos/{owner}/{name}/runs/{id}/start-now
End the run's wait timers now · branchscope.runs.startNow()
Start now (M29): the jobs whose environment's wait timer is running are queued at once; on the audit log. It doesn't approve anything: a job still waiting for review keeps waiting. Repository administrators, with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
idpath, required | integer |
Response 200
{
id: integer
workflow: string // Its file, e.g. .branchscope/workflows/ci.yml.
name: string
sha: string // A commit ID (40 or 64 hex characters).
ref: string // The branch it ran for, or the tag for tag and release runs.
trigger: "push" | "change" | "schedule" | "manual" | "tag" | "release"
change: integer | null
status: "queued" | "running" | "passed" | "failed" | "cancelled"
actor: string | null // Who pushed, opened the change or started it; null for scheduled runs.
agent: string | null // The agent or app that set it off for its actor, or whose change or run it's for; null for a person's own run.
secrets: "given" | "withheld" | "approved" // given: its jobs get the secrets they name and identity tokens. withheld (see withheldBecause): jobs that name secrets or ask for identity tokens wait until an administrator approves the run, as a person. approved: an administrator did (secretsApprovedBy).
withheldBecause: "agent" | "access" | null // Why its secrets are withheld (or were, until approved): agent, set off by an agent or app, or for an agent's change, and not at the head of a branch or tag agents can't update; access, set off by someone without write access to the repository. null when given.
secretsApprovedBy: string | null
createdAt: string (time)
startedAt: string (time) | null
completedAt: string (time) | null
jobs: ({
id: integer
name: string // The job's name, with its matrix values, e.g. test (node: 24).
status: "waiting" | "queued" | "running" | "passed" | "failed" | "cancelled" | "timed_out" // waiting: for the jobs it needs, for its environment's reviewers or wait timer (see review), or (in a run whose secrets are withheld) for an administrator to approve the secrets it names.
runsOn: string
size: "2cpu" | "4cpu" | "8cpu" | null // On hosted runners: 2cpu (2 vCPU, 4 GB), 4cpu or 8cpu. null on self-hosted runners.
needs: string[]
check: string // The name of its check on the commit.
runner: string | null // The self-hosted runner that ran it, or "Hosted (EU)" for Branchscope's own; null until it starts.
exitCode: integer | null
environment: string | null // The environment it deploys to (environment: in the workflow), or null.
secrets: string[] // The secrets it names (secrets: in the workflow), never their values.
secretsGiven: ({
name: string
level: "organization" | "repository" | "environment"
})[] // The secrets it was given when it started, by name and where each came from (an environment's wins over its repository's, a repository's over its organization's); never their values.
idToken: boolean // It may ask for OIDC identity tokens (permissions: id-token: write).
idTokens: ({
audience: string
issuedAt: string (time)
expiresAt: string (time)
})[] // The identity tokens it was issued, by audience; never the tokens.
review: DeploymentReview | null // Its environment's reviewers and wait timer (M29), once they held it; null when they never did.
secretsApprovedBy: string | null // The administrator whose approval of its deployment, as a reviewer, also approved its withheld secrets (M29).
cache: JobCache | null // Its cache (M17): null when the workflow names none.
summary: string // Why Branchscope ended it, when it did: refused before its first step (an environment's protection, hosted runners), or cancelled. Empty otherwise.
startedAt: string (time) | null
completedAt: string (time) | null
})[]
artifacts: ({
id: integer
name: string
job: string // The job that kept it, with its matrix values.
jobId: integer
sizeBytes: integer
createdAt: string (time)
expiresAt: string (time) // When it's deleted: 30 days after it was kept unless its retention-days says otherwise, at most 90.
})[] // What its jobs kept (M17) and hasn't expired yet.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/runs/7/start-now" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.runs.startNow({ owner: "acme", name: "web", id: 7 });branchscope.activity
GET /v1/activity
Push and merge decisions · branchscope.activity.list()
On repositories you can read, newest first.
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
repoquery | string · owner/name |
actorquery | string · The person (a username) who pushed, merged or used the agent. |
agentquery | string · The agent's name. |
sincequery | string (time) · At or after this time (ISO 8601). |
untilquery | string (time) · Before this time. |
affiliationquery | "all" | "mine" · mine: only what you or your agents did, and what happened in your repositories (your namespace's, your organizations', ones you collaborate on). |
refquery | string · Only pushes and merges that moved this branch (its name), or this full ref (refs/tags/v1). |
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
id: integer
repo: string
source: "push" | "merge"
decision: "allowed" | "denied" | "unavailable"
message?: string
cause?: string
refs: ({
old: string
new: string
ref: string
})[]
at: string (time)
pusher?: string // Who pushed or merged.
agent?: string // The agent, when an agent credential was used.
evaluations?: RulesetEvaluation[] // What rulesets in evaluate mode would have refused, had they been active; the push or merge itself was allowed.
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/activity" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.activity.list();GET /v1/activity/agents
Agents' MCP sessions and writes · branchscope.activity.listAgents()
A session is shown to the agent's person and site administrators; a tool call to anyone who can read its repository.
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
repoquery | string · owner/name |
actorquery | string · The person (a username) who pushed, merged or used the agent. |
agentquery | string · The agent's name. |
sincequery | string (time) · At or after this time (ISO 8601). |
untilquery | string (time) · Before this time. |
affiliationquery | "all" | "mine" · mine: only what you or your agents did, and what happened in your repositories (your namespace's, your organizations', ones you collaborate on). |
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
id: integer
at: string (time)
username: string
agent: string
session: string
kind: "session" | "tool"
tool: string
repo: string | null
summary: string
outcome: "ok" | "refused" | "error"
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/activity/agents" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.activity.listAgents();branchscope.admin
POST /v1/admin/claim
Give unclaimed repositories an owner · branchscope.admin.claim()
Repositories made before owners existed: the namespace becomes an organization owned by `owner` (or stays that user's), and its repositories are registered as private.
Site administrators only.
OAuth apps can't call this.
Request body
{
namespace: string
owner: string
}Response 201
{
namespace: string
owner: string
claimed: string[]
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/admin/claim" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"namespace":"namespace","owner":"acme"}'await branchscope.admin.claim({ namespace: "namespace", owner: "acme" });branchscope.hosted
GET /v1/admin/hosted-access
Namespaces allowed to use hosted runners · branchscope.hosted.listAccess()
Site administrators only.
OAuth apps can't call this.
Response 200
{
data: ({
namespace: string
maxConcurrent: integer
running: integer
allowedBy: string
allowedAt: string (time)
})[]
}Errors
401 unauthorized · 403 forbidden · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/admin/hosted-access" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.hosted.listAccess();PUT /v1/admin/hosted-access/{namespace}
Allow a namespace to use hosted runners · branchscope.hosted.setAccess()
An organization or a person; at most `maxConcurrent` hosted jobs at once (2 by default).
Site administrators only.
OAuth apps can't call this.
Parameters
namespacepath, required | string |
Request body
{
maxConcurrent?: integer
}Response 200
{
namespace: string
maxConcurrent: integer
running: integer
allowedBy: string
allowedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/admin/hosted-access/namespace" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'await branchscope.hosted.setAccess({ namespace: "namespace" });DELETE /v1/admin/hosted-access/{namespace}
Stop a namespace using hosted runners · branchscope.hosted.deleteAccess()
Its queued hosted jobs wait; running ones finish.
Site administrators only.
OAuth apps can't call this.
Parameters
namespacepath, required | string |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/admin/hosted-access/namespace" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.hosted.deleteAccess({ namespace: "namespace" });GET /v1/admin/hosted-runners
The hosted runner machines · branchscope.hosted.listRunners()
Site administrators only.
OAuth apps can't call this.
Response 200
{
data: ({
id: integer
name: string
memoryMb: integer // What it may give jobs at once.
usedMb: integer // What its running jobs use.
jobs: integer // Jobs running on it.
draining: boolean // Takes no new jobs; the running ones finish.
online: boolean
createdAt: string (time)
lastSeenAt: string (time) | null
})[]
}Errors
401 unauthorized · 403 forbidden · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/admin/hosted-runners" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.hosted.listRunners();PUT /v1/admin/hosted-runners/{id}
Drain a hosted runner machine, or let it take jobs again · branchscope.hosted.updateRunner()
Draining, it takes no new jobs and finishes the ones it runs (before maintenance, or taking it out).
Site administrators only.
OAuth apps can't call this.
Parameters
idpath, required | integer |
Request body
{
draining: boolean
}Response 200
{
id: integer
name: string
memoryMb: integer // What it may give jobs at once.
usedMb: integer // What its running jobs use.
jobs: integer // Jobs running on it.
draining: boolean // Takes no new jobs; the running ones finish.
online: boolean
createdAt: string (time)
lastSeenAt: string (time) | null
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/admin/hosted-runners/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"draining":true}'await branchscope.hosted.updateRunner({ id: 7, draining: true });DELETE /v1/admin/hosted-runners/{id}
Remove a hosted runner machine · branchscope.hosted.deleteRunner()
Its credential stops working at once; its running jobs are queued again.
Site administrators only.
OAuth apps can't call this.
Parameters
idpath, required | integer |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/admin/hosted-runners/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.hosted.deleteRunner({ id: 7 });POST /v1/admin/hosted-runners/registration-tokens
A token to register hosted runner machines with · branchscope.hosted.createRegistrationToken()
For the sandbox agent on a new machine; it works for an hour.
Site administrators only.
OAuth apps can't call this.
Response 201
{
token: string // Shown once. `bscope-runner register --token …` trades it for the runner's own credential; it works for an hour, for any number of runners.
expiresAt: string (time)
}Errors
401 unauthorized · 403 forbidden · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/admin/hosted-runners/registration-tokens" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.hosted.createRegistrationToken();branchscope.apps
GET /v1/apps
The apps you manage · branchscope.apps.list()
Yours, and those of organizations you own.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
slug: string
name: string
description: string
homepage: string | null
owner: string // The user or organization that registered it.
scopes: string[] // What it may ask for.
iconUrl: string | null // Its icon, served by Branchscope; null when it has none.
firstParty: boolean // Built into Branchscope (the Branchscope CLI).
agent: boolean // An agent app (M14): organizations can delegate work to it, and see it on their Agents page.
dynamic: boolean // Registered by a client itself (RFC 7591), such as an MCP client: no one at Branchscope or an organization vouches for it.
clientId: string
public: boolean // A public client (in a browser or on a desktop) has no secret and must use PKCE.
redirectUris: string[]
webhookUrl: string | null // Gets the events of every repository the app is installed in.
webhookId: integer | null // The app's webhook, for its delivery log (/webhooks/{id}/deliveries).
createdAt: string (time)
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 401 unauthorized · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/apps" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.apps.list();POST /v1/apps
Register an app · branchscope.apps.create()
For yourself or an organization you own. Its client secret (confidential apps) and webhook secret come back once. The person who registered the app, or the owners of the organization that did; only from the web app (a session).
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
name: string
description?: string
homepage?: string | null
redirectUris: string[]
scopes: ("repo:read" | "repo:write" | "changes:write" | "reviews:read" | "graphs:read" | "webhooks:admin" | "org:read" | "checks:write" | "deployments:write")[] // What it may ask for.
webhookUrl?: string | null
agent?: boolean // An agent app (M14): organizations that install it can delegate work to it.
owner: string // Your username, or an organization you own.
slug: string // Unique; it names the app in activity (app:<slug>) and its bot (app-<slug>).
public?: boolean
}Response 201
{
slug: string
name: string
description: string
homepage: string | null
owner: string // The user or organization that registered it.
scopes: string[] // What it may ask for.
iconUrl: string | null // Its icon, served by Branchscope; null when it has none.
firstParty: boolean // Built into Branchscope (the Branchscope CLI).
agent: boolean // An agent app (M14): organizations can delegate work to it, and see it on their Agents page.
dynamic: boolean // Registered by a client itself (RFC 7591), such as an MCP client: no one at Branchscope or an organization vouches for it.
clientId: string
public: boolean // A public client (in a browser or on a desktop) has no secret and must use PKCE.
redirectUris: string[]
webhookUrl: string | null // Gets the events of every repository the app is installed in.
webhookId: integer | null // The app's webhook, for its delivery log (/webhooks/{id}/deliveries).
createdAt: string (time)
clientSecret: string | null // bsc_cs_…: shown only now. null for public clients.
webhookSecret: string | null // whsec_…: for verifying the app's webhook deliveries; shown only now.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/apps" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"web","redirectUris":[],"scopes":[],"owner":"acme","slug":"slug"}'await branchscope.apps.create({ name: "web", redirectUris: [], scopes: [], owner: "acme", slug: "slug" });GET /v1/apps/{slug}
An app, as anyone sees it · branchscope.apps.get()
No credential needed; what you see depends on the one you send.
Parameters
slugpath, required | string |
Response 200
{
slug: string
name: string
description: string
homepage: string | null
owner: string // The user or organization that registered it.
scopes: string[] // What it may ask for.
iconUrl: string | null // Its icon, served by Branchscope; null when it has none.
firstParty: boolean // Built into Branchscope (the Branchscope CLI).
agent: boolean // An agent app (M14): organizations can delegate work to it, and see it on their Agents page.
dynamic: boolean // Registered by a client itself (RFC 7591), such as an MCP client: no one at Branchscope or an organization vouches for it.
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/apps/slug" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.apps.get({ slug: "slug" });PUT /v1/apps/{slug}
Change an app · branchscope.apps.update()
Only the fields sent change. Setting a webhook URL where there was none makes a webhook secret, returned once. The person who registered the app, or the owners of the organization that did; only from the web app (a session).
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
slugpath, required | string |
Request body
{
name?: string
description?: string
homepage?: string | null
redirectUris?: string[]
scopes?: ("repo:read" | "repo:write" | "changes:write" | "reviews:read" | "graphs:read" | "webhooks:admin" | "org:read" | "checks:write" | "deployments:write")[] // What it may ask for.
webhookUrl?: string | null
agent?: boolean // An agent app (M14): organizations that install it can delegate work to it.
}Response 200
{
slug: string
name: string
description: string
homepage: string | null
owner: string // The user or organization that registered it.
scopes: string[] // What it may ask for.
iconUrl: string | null // Its icon, served by Branchscope; null when it has none.
firstParty: boolean // Built into Branchscope (the Branchscope CLI).
agent: boolean // An agent app (M14): organizations can delegate work to it, and see it on their Agents page.
dynamic: boolean // Registered by a client itself (RFC 7591), such as an MCP client: no one at Branchscope or an organization vouches for it.
clientId: string
public: boolean // A public client (in a browser or on a desktop) has no secret and must use PKCE.
redirectUris: string[]
webhookUrl: string | null // Gets the events of every repository the app is installed in.
webhookId: integer | null // The app's webhook, for its delivery log (/webhooks/{id}/deliveries).
createdAt: string (time)
webhookSecret: string | null
}Errors
400 invalid_request · 401 unauthorized · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/apps/slug" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'await branchscope.apps.update({ slug: "slug" });DELETE /v1/apps/{slug}
Delete an app · branchscope.apps.delete()
Its authorizations, installations and tokens end at once. The person who registered the app, or the owners of the organization that did; only from the web app (a session).
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
slugpath, required | string |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/apps/slug" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.apps.delete({ slug: "slug" });GET /v1/apps/{slug}/icon
An app's icon · branchscope.apps.getIcon()
No credential needed; what you see depends on the one you send.
Parameters
slugpath, required | string |
Response 200
image/png · The image (PNG, JPEG or WebP), served by Branchscope.Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/apps/slug/icon" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.apps.getIcon({ slug: "slug" });PUT /v1/apps/{slug}/icon
Upload an app's icon · branchscope.apps.setIcon()
PNG, JPEG or WebP, up to 256 KB, sent as base64. Branchscope stores and serves it, so pages never load images from elsewhere. The person who registered the app, or the owners of the organization that did; only from the web app (a session).
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
slugpath, required | string |
Request body
{
image: string // The image's bytes, base64.
}Response 200
{
slug: string
name: string
description: string
homepage: string | null
owner: string // The user or organization that registered it.
scopes: string[] // What it may ask for.
iconUrl: string | null // Its icon, served by Branchscope; null when it has none.
firstParty: boolean // Built into Branchscope (the Branchscope CLI).
agent: boolean // An agent app (M14): organizations can delegate work to it, and see it on their Agents page.
dynamic: boolean // Registered by a client itself (RFC 7591), such as an MCP client: no one at Branchscope or an organization vouches for it.
clientId: string
public: boolean // A public client (in a browser or on a desktop) has no secret and must use PKCE.
redirectUris: string[]
webhookUrl: string | null // Gets the events of every repository the app is installed in.
webhookId: integer | null // The app's webhook, for its delivery log (/webhooks/{id}/deliveries).
createdAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/apps/slug/icon" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"image":"image"}'await branchscope.apps.setIcon({ slug: "slug", image: "image" });DELETE /v1/apps/{slug}/icon
Remove an app's icon · branchscope.apps.deleteIcon()
The person who registered the app, or the owners of the organization that did; only from the web app (a session).
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
slugpath, required | string |
Response 200
{
slug: string
name: string
description: string
homepage: string | null
owner: string // The user or organization that registered it.
scopes: string[] // What it may ask for.
iconUrl: string | null // Its icon, served by Branchscope; null when it has none.
firstParty: boolean // Built into Branchscope (the Branchscope CLI).
agent: boolean // An agent app (M14): organizations can delegate work to it, and see it on their Agents page.
dynamic: boolean // Registered by a client itself (RFC 7591), such as an MCP client: no one at Branchscope or an organization vouches for it.
clientId: string
public: boolean // A public client (in a browser or on a desktop) has no secret and must use PKCE.
redirectUris: string[]
webhookUrl: string | null // Gets the events of every repository the app is installed in.
webhookId: integer | null // The app's webhook, for its delivery log (/webhooks/{id}/deliveries).
createdAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/apps/slug/icon" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.apps.deleteIcon({ slug: "slug" });POST /v1/apps/{slug}/rotate-secret
Give an app a new client secret, or webhook secret · branchscope.apps.rotateSecret()
The old one stops working at once; the new one comes back once. The person who registered the app, or the owners of the organization that did; only from the web app (a session).
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
slugpath, required | string |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
which?: "client" | "webhook"
}Response 201
{
secret: string
}Errors
400 invalid_request · 401 unauthorized · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/apps/slug/rotate-secret" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'await branchscope.apps.rotateSecret({ slug: "slug" });GET /v1/apps/{slug}/settings
An app, as the people who manage it see it · branchscope.apps.getSettings()
The person who registered the app, or the owners of the organization that did; only from the web app (a session).
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
slugpath, required | string |
Response 200
{
slug: string
name: string
description: string
homepage: string | null
owner: string // The user or organization that registered it.
scopes: string[] // What it may ask for.
iconUrl: string | null // Its icon, served by Branchscope; null when it has none.
firstParty: boolean // Built into Branchscope (the Branchscope CLI).
agent: boolean // An agent app (M14): organizations can delegate work to it, and see it on their Agents page.
dynamic: boolean // Registered by a client itself (RFC 7591), such as an MCP client: no one at Branchscope or an organization vouches for it.
clientId: string
public: boolean // A public client (in a browser or on a desktop) has no secret and must use PKCE.
redirectUris: string[]
webhookUrl: string | null // Gets the events of every repository the app is installed in.
webhookId: integer | null // The app's webhook, for its delivery log (/webhooks/{id}/deliveries).
createdAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/apps/slug/settings" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.apps.getSettings({ slug: "slug" });branchscope.authorizations
branchscope.chats
GET /v1/chats
Your conversations · branchscope.chats.list()
The most recent activity first; those with people you blocked are left out.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
id: integer
with: ChatPerson
lastMessage: ChatMessage | null
unread: integer // Messages from the other person after the one you read last.
read: {
mine: integer
theirs: integer
} // How far each of you has read, as message IDs (read receipts).
blocked: boolean // You blocked them.
updatedAt: string (time)
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/chats" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.chats.list();POST /v1/chats
Open a conversation with someone · branchscope.chats.open()
The conversation with them, made if there's none yet (at most 50 new ones a day).
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Request body
{
username: string
}Response 201
{
id: integer
with: {
username: string
name: string
avatarUrl: string | null
disabled: boolean // Their account is disabled: they can't read or answer.
}
lastMessage: {
id: integer
conversation: integer
author: string // The author's username.
body: string // Markdown, as comments render it; empty once deleted.
createdAt: string (time)
editedAt: string (time) | null
deleted: boolean
} | null
unread: integer // Messages from the other person after the one you read last.
read: {
mine: integer
theirs: integer
} // How far each of you has read, as message IDs (read receipts).
blocked: boolean // You blocked them.
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/chats" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"username":"bob"}'await branchscope.chats.open({ username: "bob" });GET /v1/chats/{id}
A conversation · branchscope.chats.get()
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
idpath, required | integer |
Response 200
{
id: integer
with: {
username: string
name: string
avatarUrl: string | null
disabled: boolean // Their account is disabled: they can't read or answer.
}
lastMessage: {
id: integer
conversation: integer
author: string // The author's username.
body: string // Markdown, as comments render it; empty once deleted.
createdAt: string (time)
editedAt: string (time) | null
deleted: boolean
} | null
unread: integer // Messages from the other person after the one you read last.
read: {
mine: integer
theirs: integer
} // How far each of you has read, as message IDs (read receipts).
blocked: boolean // You blocked them.
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/chats/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.chats.get({ id: 7 });GET /v1/chats/{id}/messages
A conversation's messages · branchscope.chats.listMessages()
Newest first.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
idpath, required | integer |
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
id: integer
conversation: integer
author: string // The author's username.
body: string // Markdown, as comments render it; empty once deleted.
createdAt: string (time)
editedAt: string (time) | null
deleted: boolean
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/chats/7/messages" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.chats.listMessages({ id: 7 });POST /v1/chats/{id}/messages
Send a message · branchscope.chats.send()
Up to 4,000 characters of Markdown; at most 30 messages a minute.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
idpath, required | integer |
Request body
{
body: string
}Response 201
{
id: integer
conversation: integer
author: string // The author's username.
body: string // Markdown, as comments render it; empty once deleted.
createdAt: string (time)
editedAt: string (time) | null
deleted: boolean
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/chats/7/messages" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"body":"body"}'await branchscope.chats.send({ id: 7, body: "body" });PUT /v1/chats/{id}/messages/{message}
Edit your message · branchscope.chats.editMessage()
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
idpath, required | integer |
messagepath, required | integer |
Request body
{
body: string
}Response 200
{
id: integer
conversation: integer
author: string // The author's username.
body: string // Markdown, as comments render it; empty once deleted.
createdAt: string (time)
editedAt: string (time) | null
deleted: boolean
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/chats/7/messages/message" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"body":"body"}'await branchscope.chats.editMessage({ id: 7, message: 1, body: "body" });DELETE /v1/chats/{id}/messages/{message}
Delete your message · branchscope.chats.deleteMessage()
It shows as deleted to both of you.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
idpath, required | integer |
messagepath, required | integer |
Response 200
{
id: integer
conversation: integer
author: string // The author's username.
body: string // Markdown, as comments render it; empty once deleted.
createdAt: string (time)
editedAt: string (time) | null
deleted: boolean
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/chats/7/messages/message" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.chats.deleteMessage({ id: 7, message: 1 });POST /v1/chats/{id}/messages/{message}/report
Report a message to the site administrators · branchscope.chats.report()
Each gets it, with your reason, in their conversation with you. Administrators can't read conversations they aren't in.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
idpath, required | integer |
messagepath, required | integer |
Request body
{
reason?: string
}Response 201
{
reported: true
to: integer // How many administrators it went to.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/chats/7/messages/message/report" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'await branchscope.chats.report({ id: 7, message: 1 });POST /v1/chats/{id}/read
Mark a conversation read · branchscope.chats.read()
Up to a message (never backwards); the other person sees it as Seen.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
idpath, required | integer |
Request body
{
upTo: integer
}Response 201
{
read: integer
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/chats/7/read" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"upTo":0}'await branchscope.chats.read({ id: 7, upTo: 0 });POST /v1/chats/{id}/typing
Say you're typing · branchscope.chats.typing()
Sent at most every few seconds while typing; not stored.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
idpath, required | integer |
Response 201
{
ok: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/chats/7/typing" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.chats.typing({ id: 7 });GET /v1/me/blocks
People you blocked · branchscope.chats.listBlocks()
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Response 200
{
data: ({
username: string
name: string
avatarUrl: string | null
disabled: boolean // Their account is disabled: they can't read or answer.
})[]
}Errors
401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/me/blocks" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.chats.listBlocks();PUT /v1/me/blocks/{username}
Block someone · branchscope.chats.block()
They can't message you, and you don't see what they sent, until you unblock them.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
usernamepath, required | string |
Response 200
{
username: string
blocked: boolean
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/me/blocks/bob" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.chats.block({ username: "bob" });DELETE /v1/me/blocks/{username}
Unblock someone · branchscope.chats.unblock()
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
usernamepath, required | string |
Response 200
{
username: string
blocked: boolean
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/me/blocks/bob" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.chats.unblock({ username: "bob" });GET /v1/people
Find people to chat with · branchscope.chats.findPeople()
People on Branchscope whose username or name contains the search (any case), exact and leading matches first; not you, disabled accounts or apps' bots. Organizations aren't people.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
qquery | string · A username or name, or part of one. Without it, no one. |
limitquery | integer · At most this many, 1-50. |
Response 200
{
data: ({
username: string
name: string
avatarUrl: string | null
disabled: boolean // Their account is disabled: they can't read or answer.
})[]
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/people" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.chats.findPeople();branchscope.cli
POST /v1/cli/codes
Approve a CLI sign-in · branchscope.cli.createCode()
From the web app: a one-time code for the CLI's loopback port, for a token or an SSH key. It works once, for 2 minutes, with its state.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Request body
{
state: string
name?: string // For a sign-in: the computer's name.
token?: {
name: string
scope?: "read" | "write"
repos?: string[] | null // null for all your repositories, or up to 100 owner/name.
expiresInDays?: integer | null
} // For `bscope token create`.
sshKey?: {
key: string // The public key, e.g. the contents of ~/.ssh/id_ed25519.pub.
title: string
scope?: "read" | "write"
repos?: string[] | null // null for all your repositories, or up to 100 owner/name.
expiresInDays?: integer | null
} // For `bscope ssh-key add`.
}Response 201
{
code: string
expiresAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/cli/codes" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"state":"state"}'await branchscope.cli.createCode({ state: "state" });POST /v1/cli/token
Exchange a CLI code · branchscope.cli.exchangeCode()
No credential: the code is one. Returns the token (or registers the SSH key) the person approved.
No credential needed; what you see depends on the one you send.
OAuth apps can't call this.
Request body
{
code: string
state: string
}Response 201
{
token: string
tokenInfo: {
id: integer
name: string
prefix: string // The token's first characters, to recognise it.
scope: "read" | "write"
repos: string[] | null // null: all the owner's repositories.
createdAt: string (time)
lastUsedAt: string (time) | null
expiresAt: string (time) | null
}
user: {
username: string
}
} | {
sshKey: {
id: integer
title: string
type: string
fingerprint: string
scope: "read" | "write"
repos: string[] | null // null: all the owner's repositories.
createdAt: string (time)
lastUsedAt: string (time) | null
expiresAt: string (time) | null
}
user: {
username: string
}
}Errors
400 invalid_request · 403 forbidden · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/cli/token" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"code":"code","state":"state"}'await branchscope.cli.exchangeCode({ code: "code", state: "state" });branchscope.events
GET /v1/events
Your live events · branchscope.events.stream()
Server-Sent Events, as they happen: `message` (sent, edited or deleted), `read` (the other person read up to a message) and `typing`, each with a JSON `data`. A comment line every 25 seconds keeps proxies from closing it. Nothing is replayed: after reconnecting, fetch what you missed from the chats API.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Response 200
text/event-stream · event: message|read|typing, data: the event as JSON.Errors
401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/events" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.events.stream();branchscope.externalTokens
GET /v1/external-tokens
Your saved tokens for other git hosts · branchscope.externalTokens.list()
Newest first, without their secrets. From the web app only: a session, never a token or an app.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
id: integer
name: string
provider: "github" | "gitlab" | "bitbucket" | "gitea" | "other" // The git host it's for: GitHub, GitLab, Bitbucket, Gitea/Forgejo, or any other.
description: string
createdAt: string (time)
updatedAt: string (time)
lastUsedAt: string (time) | null // The last import that used it.
})[]
nextCursor: string | null // null on the last page.
available: boolean // Whether this server can store them (its encryption key is set).
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/external-tokens" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.externalTokens.list();POST /v1/external-tokens
Save a token for another git host · branchscope.externalTokens.create()
Kept encrypted; only you can copy it again, and imports use it for its provider's host. From the web app only: a session, never a token or an app.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
name: string
provider: "github" | "gitlab" | "bitbucket" | "gitea" | "other" // The git host it's for: GitHub, GitLab, Bitbucket, Gitea/Forgejo, or any other.
token: string
description?: string
}Response 201
{
id: integer
name: string
provider: "github" | "gitlab" | "bitbucket" | "gitea" | "other" // The git host it's for: GitHub, GitLab, Bitbucket, Gitea/Forgejo, or any other.
description: string
createdAt: string (time)
updatedAt: string (time)
lastUsedAt: string (time) | null // The last import that used it.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 409 conflict · 429 rate_limited · 500 internal · 503 unavailable
Example
curl -X POST "$BRANCHSCOPE_API/v1/external-tokens" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"web","provider":"github","token":"token"}'await branchscope.externalTokens.create({ name: "web", provider: "github", token: "token" });GET /v1/external-tokens/{id}
A saved token for another git host · branchscope.externalTokens.get()
Without its secret. From the web app only: a session, never a token or an app.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
idpath, required | integer |
Response 200
{
id: integer
name: string
provider: "github" | "gitlab" | "bitbucket" | "gitea" | "other" // The git host it's for: GitHub, GitLab, Bitbucket, Gitea/Forgejo, or any other.
description: string
createdAt: string (time)
updatedAt: string (time)
lastUsedAt: string (time) | null // The last import that used it.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/external-tokens/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.externalTokens.get({ id: 7 });PUT /v1/external-tokens/{id}
Rename a saved token, or change its description · branchscope.externalTokens.update()
Its provider and secret stay: save a new one for another. From the web app only: a session, never a token or an app.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
idpath, required | integer |
Request body
{
name?: string
description?: string
}Response 200
{
id: integer
name: string
provider: "github" | "gitlab" | "bitbucket" | "gitea" | "other" // The git host it's for: GitHub, GitLab, Bitbucket, Gitea/Forgejo, or any other.
description: string
createdAt: string (time)
updatedAt: string (time)
lastUsedAt: string (time) | null // The last import that used it.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/external-tokens/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'await branchscope.externalTokens.update({ id: 7 });DELETE /v1/external-tokens/{id}
Delete a saved token · branchscope.externalTokens.delete()
From the web app only: a session, never a token or an app.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
idpath, required | integer |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/external-tokens/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.externalTokens.delete({ id: 7 });POST /v1/external-tokens/{id}/reveal
A saved token's secret, to copy · branchscope.externalTokens.reveal()
Only to its owner. From the web app only: a session, never a token or an app.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
idpath, required | integer |
Response 200
{
token: string
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal · 503 unavailable
Example
curl -X POST "$BRANCHSCOPE_API/v1/external-tokens/7/reveal" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.externalTokens.reveal({ id: 7 });branchscope.guard
GET /v1/guard
The push guard's state · branchscope.guard.get()
Site administrators only.
OAuth apps can't call this.
Response 200
{
denyAll: boolean
denyMessage: string
guard: {
reachable: boolean
configured: boolean
error?: string
}
}Errors
401 unauthorized · 403 forbidden · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/guard" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.guard.get();PUT /v1/guard
Turn deny-all on or off · branchscope.guard.update()
Stored, then applied; if the guard can't be reached, it's applied once it's back.
Site administrators only.
OAuth apps can't call this.
Request body
{
denyAll: boolean
denyMessage?: string
}Response 200
{
denyAll: boolean
denyMessage: string
guard: {
reachable: boolean
configured: boolean
error?: string
}
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/guard" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"denyAll":true}'await branchscope.guard.update({ denyAll: true });branchscope.imports
POST /v1/imports
Import a repository from GitHub, GitLab or any HTTPS git URL · branchscope.imports.start()
Once: all branches and tags, and open pull or merge requests as changes. The new repository is created at once; the import runs in the background and pushes through the push guard.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
url: string
owner?: string // Your own namespace if left out.
name?: string // From the source's name if left out.
visibility?: "private" | "public"
token?: string // For a private source; used for this import only, never stored.
externalToken?: integer // In place of token: one of your saved tokens (externalTokens), from the web app only. Its provider must fit the source's host.
username?: string // The username to send with token, for git servers that need one; GitHub and GitLab don't.
}Response 202
{
id: integer
repo: string
source: string
kind: "github" | "gitlab" | "git"
status: "queued" | "running" | "done" | "failed"
log: string[]
summary: Record<string, unknown> | null
error: string | null
createdAt: string (time)
startedAt: string (time) | null
finishedAt: string (time) | null
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal · 502 upstream_error · 503 unavailable
Example
curl -X POST "$BRANCHSCOPE_API/v1/imports" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url":"url"}'await branchscope.imports.start({ url: "url" });GET /v1/imports/{id}
An import's status and log · branchscope.imports.get()
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
idpath, required | integer |
Response 200
{
id: integer
repo: string
source: string
kind: "github" | "gitlab" | "git"
status: "queued" | "running" | "done" | "failed"
log: string[]
summary: Record<string, unknown> | null
error: string | null
createdAt: string (time)
startedAt: string (time) | null
finishedAt: string (time) | null
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/imports/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.imports.get({ id: 7 });GET /v1/repos/{owner}/{name}/import
The import that made a repository · branchscope.imports.forRepo()
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
Response 200
{
id: integer
repo: string
source: string
kind: "github" | "gitlab" | "git"
status: "queued" | "running" | "done" | "failed"
log: string[]
summary: Record<string, unknown> | null
error: string | null
createdAt: string (time)
startedAt: string (time) | null
finishedAt: string (time) | null
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/import" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.imports.forRepo({ owner: "acme", name: "web" });branchscope.aiKeys
POST /v1/installations/ai-keys/{provider}
Get the team's AI provider key, as the app it's for · branchscope.aiKeys.obtain()
The repository's key for this app and provider, or else its organization's, with this month's reported spend and cap. Refused (forbidden) once the spend reaches the cap; not_found when there's no key for this app. Each hand-over is counted, not logged. Only an installed app, with its installation token (bsc_ait_…): never a person, an agent signed in with the CLI, or an app acting for a person.
Needs a credential: a personal or agent token, or a web session.
Parameters
providerpath, required | "anthropic" | "openai" | "mistral" | "other" |
Request body
{
repository?: string | null // The repository it's working on (owner/name), one of its installation's: its own key wins over its organization's. null for the organization's.
}Response 200
{
month: string // YYYY-MM, in UTC.
spentCents: integer // What the app reported spending this month, in euro cents.
capCents: integer | null // The monthly cap, in euro cents; null for none.
remainingCents: integer | null // What's left before the cap; null without one.
provider: "anthropic" | "openai" | "mistral" | "other"
key: string // The key itself. Use it for this piece of work and don't store it: ask again next time, so a replaced or deleted key, or a reached cap, takes effect.
level: "organization" | "repository"
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal · 503 unavailable
Example
curl -X POST "$BRANCHSCOPE_API/v1/installations/ai-keys/provider" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'await branchscope.aiKeys.obtain({ provider: "anthropic" });POST /v1/installations/ai-keys/{provider}/usage
Report what you spent with the team's AI provider key · branchscope.aiKeys.reportUsage()
Added to this month's usage of the key the same request would get. Branchscope can't see the provider's billing: its caps and the usage it shows are what the app reports. Only an installed app, with its installation token (bsc_ait_…): never a person, an agent signed in with the CLI, or an app acting for a person.
Needs a credential: a personal or agent token, or a web session.
Parameters
providerpath, required | "anthropic" | "openai" | "mistral" | "other" |
Request body
{
repository?: string | null // The repository it's working on (owner/name), one of its installation's: its own key wins over its organization's. null for the organization's.
inputTokens?: integer
outputTokens?: integer
costCents: integer // What it cost, in euro cents, rounded up: convert from the provider's currency.
}Response 200
{
month: string // YYYY-MM, in UTC.
spentCents: integer // What the app reported spending this month, in euro cents.
capCents: integer | null // The monthly cap, in euro cents; null for none.
remainingCents: integer | null // What's left before the cap; null without one.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/installations/ai-keys/provider/usage" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"costCents":0}'await branchscope.aiKeys.reportUsage({ provider: "anthropic", costCents: 0 });GET /v1/orgs/{org}/ai-keys
An organization's AI provider keys · branchscope.aiKeys.listForOrg()
Never with their values, each with this month's reported usage. Repository administrators (a repository's keys) and organization owners (an organization's), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Response 200
{
data: ({
app: string // The slug of the app it's for.
appName: string
provider: "anthropic" | "openai" | "mistral" | "other"
level: "organization" | "repository"
monthlyCapCents: integer | null // Branchscope refuses to hand it over once the app's reported spend this month reaches this (euro cents); null for no cap.
usage: {
month: string // YYYY-MM, in UTC.
spentCents: integer // What the app reported spending this month, in euro cents.
capCents: integer | null // The monthly cap, in euro cents; null for none.
remainingCents: integer | null // What's left before the cap; null without one.
inputTokens: integer
outputTokens: integer
handOvers: integer // How many times the app was given it this month.
}
lastHandedOverAt: string (time) | null
createdBy: string | null // Who set it last.
createdAt: string (time)
updatedAt: string (time)
})[]
apps: ({
slug: string
name: string
})[] // The apps installed here, which a key can be for.
available: boolean // Whether this server can store keys (its SECRETS_KEY is set).
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/orgs/acme/ai-keys" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.aiKeys.listForOrg({ org: "acme" });PUT /v1/orgs/{org}/ai-keys/{app}/{provider}
Set an organization's AI provider key for an app · branchscope.aiKeys.setForOrg()
Made, or replaced if it exists (its usage this month stays), for every repository the app is installed on. The app must be installed in the organization. Repository administrators (a repository's keys) and organization owners (an organization's), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
orgpath, required | string |
apppath, required | string |
providerpath, required | "anthropic" | "openai" | "mistral" | "other" |
Request body
{
value?: string // The provider's API key. Needed to add one; left out to change only the cap. Kept encrypted; it can't be read back, only replaced.
monthlyCapCents: integer | null // Euro cents: once the app's reported spend this calendar month (UTC) reaches it, the key is refused. null for no cap.
}Response 200
{
app: string // The slug of the app it's for.
appName: string
provider: "anthropic" | "openai" | "mistral" | "other"
level: "organization" | "repository"
monthlyCapCents: integer | null // Branchscope refuses to hand it over once the app's reported spend this month reaches this (euro cents); null for no cap.
usage: {
month: string // YYYY-MM, in UTC.
spentCents: integer // What the app reported spending this month, in euro cents.
capCents: integer | null // The monthly cap, in euro cents; null for none.
remainingCents: integer | null // What's left before the cap; null without one.
inputTokens: integer
outputTokens: integer
handOvers: integer // How many times the app was given it this month.
}
lastHandedOverAt: string (time) | null
createdBy: string | null // Who set it last.
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal · 503 unavailable
Example
curl -X PUT "$BRANCHSCOPE_API/v1/orgs/acme/ai-keys/app/provider" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"monthlyCapCents":0}'await branchscope.aiKeys.setForOrg({ org: "acme", app: "app", provider: "anthropic", monthlyCapCents: 0 });DELETE /v1/orgs/{org}/ai-keys/{app}/{provider}
Delete an organization's AI provider key · branchscope.aiKeys.deleteForOrg()
Repository administrators (a repository's keys) and organization owners (an organization's), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
orgpath, required | string |
apppath, required | string |
providerpath, required | "anthropic" | "openai" | "mistral" | "other" |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/orgs/acme/ai-keys/app/provider" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.aiKeys.deleteForOrg({ org: "acme", app: "app", provider: "anthropic" });GET /v1/repos/{owner}/{name}/ai-keys
A repository's AI provider keys · branchscope.aiKeys.listForRepo()
Never with their values, each with this month's reported usage; organization lists its organization's (this repository's own win over them for the same app and provider). Repository administrators (a repository's keys) and organization owners (an organization's), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
Response 200
{
data: ({
app: string // The slug of the app it's for.
appName: string
provider: "anthropic" | "openai" | "mistral" | "other"
level: "organization" | "repository"
monthlyCapCents: integer | null // Branchscope refuses to hand it over once the app's reported spend this month reaches this (euro cents); null for no cap.
usage: {
month: string // YYYY-MM, in UTC.
spentCents: integer // What the app reported spending this month, in euro cents.
capCents: integer | null // The monthly cap, in euro cents; null for none.
remainingCents: integer | null // What's left before the cap; null without one.
inputTokens: integer
outputTokens: integer
handOvers: integer // How many times the app was given it this month.
}
lastHandedOverAt: string (time) | null
createdBy: string | null // Who set it last.
createdAt: string (time)
updatedAt: string (time)
})[]
organization: ({
app: string // The slug of the app it's for.
appName: string
provider: "anthropic" | "openai" | "mistral" | "other"
level: "organization" | "repository"
monthlyCapCents: integer | null // Branchscope refuses to hand it over once the app's reported spend this month reaches this (euro cents); null for no cap.
usage: {
month: string // YYYY-MM, in UTC.
spentCents: integer // What the app reported spending this month, in euro cents.
capCents: integer | null // The monthly cap, in euro cents; null for none.
remainingCents: integer | null // What's left before the cap; null without one.
inputTokens: integer
outputTokens: integer
handOvers: integer // How many times the app was given it this month.
}
lastHandedOverAt: string (time) | null
createdBy: string | null // Who set it last.
createdAt: string (time)
updatedAt: string (time)
})[]
apps: ({
slug: string
name: string
})[] // The apps installed here, which a key can be for.
available: boolean // Whether this server can store keys (its SECRETS_KEY is set).
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/ai-keys" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.aiKeys.listForRepo({ owner: "acme", name: "web" });PUT /v1/repos/{owner}/{name}/ai-keys/{app}/{provider}
Set a repository's AI provider key for an app · branchscope.aiKeys.setForRepo()
Made, or replaced if it exists (its usage this month stays). The app must be installed on the repository. Repository administrators (a repository's keys) and organization owners (an organization's), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
apppath, required | string |
providerpath, required | "anthropic" | "openai" | "mistral" | "other" |
Request body
{
value?: string // The provider's API key. Needed to add one; left out to change only the cap. Kept encrypted; it can't be read back, only replaced.
monthlyCapCents: integer | null // Euro cents: once the app's reported spend this calendar month (UTC) reaches it, the key is refused. null for no cap.
}Response 200
{
app: string // The slug of the app it's for.
appName: string
provider: "anthropic" | "openai" | "mistral" | "other"
level: "organization" | "repository"
monthlyCapCents: integer | null // Branchscope refuses to hand it over once the app's reported spend this month reaches this (euro cents); null for no cap.
usage: {
month: string // YYYY-MM, in UTC.
spentCents: integer // What the app reported spending this month, in euro cents.
capCents: integer | null // The monthly cap, in euro cents; null for none.
remainingCents: integer | null // What's left before the cap; null without one.
inputTokens: integer
outputTokens: integer
handOvers: integer // How many times the app was given it this month.
}
lastHandedOverAt: string (time) | null
createdBy: string | null // Who set it last.
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal · 503 unavailable
Example
curl -X PUT "$BRANCHSCOPE_API/v1/repos/acme/web/ai-keys/app/provider" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"monthlyCapCents":0}'await branchscope.aiKeys.setForRepo({ owner: "acme", name: "web", app: "app", provider: "anthropic", monthlyCapCents: 0 });DELETE /v1/repos/{owner}/{name}/ai-keys/{app}/{provider}
Delete a repository's AI provider key · branchscope.aiKeys.deleteForRepo()
The app gets its organization's for the provider, if there's one, or none. Repository administrators (a repository's keys) and organization owners (an organization's), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
apppath, required | string |
providerpath, required | "anthropic" | "openai" | "mistral" | "other" |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/repos/acme/web/ai-keys/app/provider" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.aiKeys.deleteForRepo({ owner: "acme", name: "web", app: "app", provider: "anthropic" });branchscope.integrations
GET /v1/integrations/{id}
An integration · branchscope.integrations.get()
Repository administrators (a repository's) and organization owners (an organization's), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
idpath, required | integer |
Response 200
{
id: integer // Also its webhook's: its deliveries are a webhook's, with the same retries and log.
scope: "repo" | "org"
target: string // owner/name for a repository, the name for an organization.
service: "slack" | "discord" | "teams" | "mattermost" | "sentry" | "linear"
events: ("change.opened" | "change.merged" | "review.completed" | "run.failed" | "run.succeeded" | "release.published" | "deployment.status" | "deployment.review_requested")[]
channel: string // The channel's name, for display only: a chat service's incoming webhook posts to the channel it was made for.
destination: string // Where it sends, without the secret: the webhook URL's (or Sentry's) host and the last 4 characters of the URL (or token).
active: boolean // Paused integrations send nothing; Branchscope pauses one whose deliveries failed for a day.
disabledReason: string | null
createdBy: string
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/integrations/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.integrations.get({ id: 7 });PUT /v1/integrations/{id}
Change an integration · branchscope.integrations.update()
Only the fields sent change; its service doesn't. Turning it back on (active) clears why Branchscope paused it. Repository administrators (a repository's) and organization owners (an organization's), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
idpath, required | integer |
Request body
{
url?: string // A chat service's incoming webhook URL (Slack, Discord, Teams' Workflows, Mattermost): a public HTTPS address. Kept encrypted; never returned.
sentry?: {
baseUrl?: string // Sentry's address: https://sentry.io, https://de.sentry.io for its EU region, or your own Sentry (or a compatible service).
org: string // The organization's slug.
projects: string[] // The projects' slugs that releases are made in.
token: string // An auth token that can manage releases (an organization token, or a personal one with project:releases). Kept encrypted; never returned.
} // Sentry only: where and with what token. All of it is kept encrypted; sent again whole to change any of it.
events?: ("change.opened" | "change.merged" | "review.completed" | "run.failed" | "run.succeeded" | "release.published" | "deployment.status" | "deployment.review_requested")[]
channel?: string // The channel's name, for display.
active?: boolean
}Response 200
{
id: integer // Also its webhook's: its deliveries are a webhook's, with the same retries and log.
scope: "repo" | "org"
target: string // owner/name for a repository, the name for an organization.
service: "slack" | "discord" | "teams" | "mattermost" | "sentry" | "linear"
events: ("change.opened" | "change.merged" | "review.completed" | "run.failed" | "run.succeeded" | "release.published" | "deployment.status" | "deployment.review_requested")[]
channel: string // The channel's name, for display only: a chat service's incoming webhook posts to the channel it was made for.
destination: string // Where it sends, without the secret: the webhook URL's (or Sentry's) host and the last 4 characters of the URL (or token).
active: boolean // Paused integrations send nothing; Branchscope pauses one whose deliveries failed for a day.
disabledReason: string | null
createdBy: string
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal · 503 unavailable
Example
curl -X PUT "$BRANCHSCOPE_API/v1/integrations/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'await branchscope.integrations.update({ id: 7 });DELETE /v1/integrations/{id}
Delete an integration · branchscope.integrations.delete()
Its deliveries go with it. Repository administrators (a repository's) and organization owners (an organization's), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
idpath, required | integer |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/integrations/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.integrations.delete({ id: 7 });GET /v1/integrations/{id}/deliveries
An integration's deliveries · branchscope.integrations.listDeliveries()
Newest first: each message (or Sentry's calls), its attempts and how the last one went. Repository administrators (a repository's) and organization owners (an organization's), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
idpath, required | integer |
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
id: integer
eventId: string // The webhook-id header: the same on every attempt and redelivery.
event: string
status: "pending" | "succeeded" | "failed"
attempts: integer
nextAttemptAt: string (time) | null
createdAt: string (time)
lastAttemptAt: string (time) | null
responseCode: integer | null
durationMs: integer | null
error: string | null // Why the last attempt failed: a timeout, a refused address, a status code.
response: string | null // The start of the receiver's last response.
payload: string | null // The body sent; kept for 7 days.
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/integrations/7/deliveries" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.integrations.listDeliveries({ id: 7 });POST /v1/integrations/{id}/deliveries/{delivery}/redeliver
Send an integration's delivery again · branchscope.integrations.redeliver()
The same message, as a new delivery. Only while its body is kept (7 days). Repository administrators (a repository's) and organization owners (an organization's), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
idpath, required | integer |
deliverypath, required | integer |
Response 201
{
id: integer
eventId: string // The webhook-id header: the same on every attempt and redelivery.
event: string
status: "pending" | "succeeded" | "failed"
attempts: integer
nextAttemptAt: string (time) | null
createdAt: string (time)
lastAttemptAt: string (time) | null
responseCode: integer | null
durationMs: integer | null
error: string | null // Why the last attempt failed: a timeout, a refused address, a status code.
response: string | null // The start of the receiver's last response.
payload: string | null // The body sent; kept for 7 days.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 410 gone · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/integrations/7/deliveries/delivery/redeliver" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.integrations.redeliver({ id: 7, delivery: 1 });POST /v1/integrations/{id}/test
Send an integration a test message · branchscope.integrations.test()
Sent now, once (no retries), and answered with how it went: a message in the chat channel, or for Sentry a look at each project's releases, which checks the token, organization and projects. In its delivery log too. Repository administrators (a repository's) and organization owners (an organization's), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
idpath, required | integer |
Response 200
{
id: integer
eventId: string // The webhook-id header: the same on every attempt and redelivery.
event: string
status: "pending" | "succeeded" | "failed"
attempts: integer
nextAttemptAt: string (time) | null
createdAt: string (time)
lastAttemptAt: string (time) | null
responseCode: integer | null
durationMs: integer | null
error: string | null // Why the last attempt failed: a timeout, a refused address, a status code.
response: string | null // The start of the receiver's last response.
payload: string | null // The body sent; kept for 7 days.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal · 503 unavailable
Example
curl -X POST "$BRANCHSCOPE_API/v1/integrations/7/test" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.integrations.test({ id: 7 });GET /v1/orgs/{org}/integrations
An organization's integrations · branchscope.integrations.listForOrg()
They get events from every repository in the organization. Never with their webhook URLs or tokens. Repository administrators (a repository's) and organization owners (an organization's), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Response 200
{
data: ({
id: integer // Also its webhook's: its deliveries are a webhook's, with the same retries and log.
scope: "repo" | "org"
target: string // owner/name for a repository, the name for an organization.
service: "slack" | "discord" | "teams" | "mattermost" | "sentry" | "linear"
events: ("change.opened" | "change.merged" | "review.completed" | "run.failed" | "run.succeeded" | "release.published" | "deployment.status" | "deployment.review_requested")[]
channel: string // The channel's name, for display only: a chat service's incoming webhook posts to the channel it was made for.
destination: string // Where it sends, without the secret: the webhook URL's (or Sentry's) host and the last 4 characters of the URL (or token).
active: boolean // Paused integrations send nothing; Branchscope pauses one whose deliveries failed for a day.
disabledReason: string | null
createdBy: string
createdAt: string (time)
updatedAt: string (time)
})[]
available: boolean // Whether this server can keep integrations' secrets (its SECRETS_KEY is set); without it none can be added or send.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/orgs/acme/integrations" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.integrations.listForOrg({ org: "acme" });POST /v1/orgs/{org}/integrations
Add an integration to an organization · branchscope.integrations.createForOrg()
It gets events from every repository in the organization. At most 20 per organization. Repository administrators (a repository's) and organization owners (an organization's), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Request body
{
service: "slack" | "discord" | "teams" | "mattermost" | "sentry" | "linear"
url?: string // A chat service's incoming webhook URL (Slack, Discord, Teams' Workflows, Mattermost): a public HTTPS address. Kept encrypted; never returned.
sentry?: {
baseUrl?: string // Sentry's address: https://sentry.io, https://de.sentry.io for its EU region, or your own Sentry (or a compatible service).
org: string // The organization's slug.
projects: string[] // The projects' slugs that releases are made in.
token: string // An auth token that can manage releases (an organization token, or a personal one with project:releases). Kept encrypted; never returned.
} // Sentry only: where and with what token. All of it is kept encrypted; sent again whole to change any of it.
events?: ("change.opened" | "change.merged" | "review.completed" | "run.failed" | "run.succeeded" | "release.published" | "deployment.status" | "deployment.review_requested")[] // Which events; left out: every one but run.succeeded for chat, release.published and deployment.status for Sentry.
channel?: string // The channel's name, for display.
active?: boolean
}Response 201
{
id: integer // Also its webhook's: its deliveries are a webhook's, with the same retries and log.
scope: "repo" | "org"
target: string // owner/name for a repository, the name for an organization.
service: "slack" | "discord" | "teams" | "mattermost" | "sentry" | "linear"
events: ("change.opened" | "change.merged" | "review.completed" | "run.failed" | "run.succeeded" | "release.published" | "deployment.status" | "deployment.review_requested")[]
channel: string // The channel's name, for display only: a chat service's incoming webhook posts to the channel it was made for.
destination: string // Where it sends, without the secret: the webhook URL's (or Sentry's) host and the last 4 characters of the URL (or token).
active: boolean // Paused integrations send nothing; Branchscope pauses one whose deliveries failed for a day.
disabledReason: string | null
createdBy: string
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal · 503 unavailable
Example
curl -X POST "$BRANCHSCOPE_API/v1/orgs/acme/integrations" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"service":"slack"}'await branchscope.integrations.createForOrg({ org: "acme", service: "slack" });GET /v1/repos/{owner}/{name}/integrations
A repository's integrations · branchscope.integrations.listForRepo()
Never with their webhook URLs or tokens. Repository administrators (a repository's) and organization owners (an organization's), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
Response 200
{
data: ({
id: integer // Also its webhook's: its deliveries are a webhook's, with the same retries and log.
scope: "repo" | "org"
target: string // owner/name for a repository, the name for an organization.
service: "slack" | "discord" | "teams" | "mattermost" | "sentry" | "linear"
events: ("change.opened" | "change.merged" | "review.completed" | "run.failed" | "run.succeeded" | "release.published" | "deployment.status" | "deployment.review_requested")[]
channel: string // The channel's name, for display only: a chat service's incoming webhook posts to the channel it was made for.
destination: string // Where it sends, without the secret: the webhook URL's (or Sentry's) host and the last 4 characters of the URL (or token).
active: boolean // Paused integrations send nothing; Branchscope pauses one whose deliveries failed for a day.
disabledReason: string | null
createdBy: string
createdAt: string (time)
updatedAt: string (time)
})[]
available: boolean // Whether this server can keep integrations' secrets (its SECRETS_KEY is set); without it none can be added or send.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/integrations" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.integrations.listForRepo({ owner: "acme", name: "web" });POST /v1/repos/{owner}/{name}/integrations
Add an integration to a repository · branchscope.integrations.createForRepo()
A chat service with its incoming webhook URL (url), or Sentry (sentry). At most 20 per repository. Repository administrators (a repository's) and organization owners (an organization's), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
Request body
{
service: "slack" | "discord" | "teams" | "mattermost" | "sentry" | "linear"
url?: string // A chat service's incoming webhook URL (Slack, Discord, Teams' Workflows, Mattermost): a public HTTPS address. Kept encrypted; never returned.
sentry?: {
baseUrl?: string // Sentry's address: https://sentry.io, https://de.sentry.io for its EU region, or your own Sentry (or a compatible service).
org: string // The organization's slug.
projects: string[] // The projects' slugs that releases are made in.
token: string // An auth token that can manage releases (an organization token, or a personal one with project:releases). Kept encrypted; never returned.
} // Sentry only: where and with what token. All of it is kept encrypted; sent again whole to change any of it.
events?: ("change.opened" | "change.merged" | "review.completed" | "run.failed" | "run.succeeded" | "release.published" | "deployment.status" | "deployment.review_requested")[] // Which events; left out: every one but run.succeeded for chat, release.published and deployment.status for Sentry.
channel?: string // The channel's name, for display.
active?: boolean
}Response 201
{
id: integer // Also its webhook's: its deliveries are a webhook's, with the same retries and log.
scope: "repo" | "org"
target: string // owner/name for a repository, the name for an organization.
service: "slack" | "discord" | "teams" | "mattermost" | "sentry" | "linear"
events: ("change.opened" | "change.merged" | "review.completed" | "run.failed" | "run.succeeded" | "release.published" | "deployment.status" | "deployment.review_requested")[]
channel: string // The channel's name, for display only: a chat service's incoming webhook posts to the channel it was made for.
destination: string // Where it sends, without the secret: the webhook URL's (or Sentry's) host and the last 4 characters of the URL (or token).
active: boolean // Paused integrations send nothing; Branchscope pauses one whose deliveries failed for a day.
disabledReason: string | null
createdBy: string
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal · 503 unavailable
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/integrations" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"service":"slack"}'await branchscope.integrations.createForRepo({ owner: "acme", name: "web", service: "slack" });branchscope.mcp
GET /v1/mcp
MCP: the server's event stream · branchscope.mcp.stream()
Needs a credential: a personal or agent token, or a web session.
Response 200
text/event-stream · Server-sent events.Errors
401 unauthorized · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/mcp" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.mcp.stream();POST /v1/mcp
MCP: send a request · branchscope.mcp.post()
The Model Context Protocol endpoint (Streamable HTTP) for agents. See the MCP docs for its tools.
Needs a credential: a personal or agent token, or a web session.
Response 201
application/json · A JSON-RPC response, or an event stream.Errors
400 invalid_request · 401 unauthorized · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/mcp" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.mcp.post();DELETE /v1/mcp
MCP: end a session · branchscope.mcp.end()
Needs a credential: a personal or agent token, or a web session.
Response 200
application/json · Empty.Errors
401 unauthorized · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/mcp" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.mcp.end();branchscope.me
GET /v1/me
Who the caller is · branchscope.me.get()
The user behind the credential, how they authenticated, the agent and scope of an app or limited token, and their organizations. Without a credential, user is null.
No credential needed; what you see depends on the one you send.
Response 200
{
user: {
id: integer
username: string
email: string
name: string
isAdmin: boolean
} | null // null when the request carries no credential.
via?: "session" | "personal" | "keycloak" | "oauth" | "installation" // How the caller authenticated: oauth and installation are OAuth apps (M13), the Branchscope CLI's agents among them.
agent?: string | null // The agent: an OAuth app (app:<slug>), or a person's own agent signed in with the Branchscope CLI (its name).
scope?: "read" | "write"
scopes?: string[] | null // For an OAuth app: the scopes it was granted.
orgs?: ({
org: string
name: string // Its name as written.
role: "owner" | "member"
avatarUrl: string | null // Its picture; null when it has none.
})[]
profile?: {
name: string // Their name as they set it on their profile; empty uses their sign-in's.
company: string
bio: string // A few words about them, up to 180 characters.
location: string
timezone: string // An IANA time zone their local time is shown in; empty when they don't show it.
links: ProfileLink[] // Up to 4 websites, and at most one account on each social network.
avatarUrl: string | null // Served by Branchscope; null without an avatar.
showEmail: boolean // Whether they show their email on their profile.
email: string | null // Their email, when they show it; otherwise null.
showRepositories?: boolean // An organization's: whether its page has a Repositories tab.
showMembers?: boolean // An organization's: whether its page has a Members tab.
} // A person's own profile.
unreadNotifications?: integer // How many of the person's notifications they haven't read.
}Errors
429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/me" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.me.get();PUT /v1/me/avatar
Upload your avatar · branchscope.me.setAvatar()
PNG, JPEG or WebP, up to 256 KB, sent as base64. Branchscope stores and serves it, so pages never load images from elsewhere.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Request body
{
image: string // The image's bytes, base64.
}Response 200
{
name: string // Their name as they set it on their profile; empty uses their sign-in's.
company: string
bio: string // A few words about them, up to 180 characters.
location: string
timezone: string // An IANA time zone their local time is shown in; empty when they don't show it.
links: ({
kind: "website" | "linkedin" | "mastodon" | "bluesky" | "x" | "facebook" | "instagram" // A website, or which social network the account is on.
url: string
})[] // Up to 4 websites, and at most one account on each social network.
avatarUrl: string | null // Served by Branchscope; null without an avatar.
showEmail: boolean // Whether they show their email on their profile.
email: string | null // Their email, when they show it; otherwise null.
showRepositories?: boolean // An organization's: whether its page has a Repositories tab.
showMembers?: boolean // An organization's: whether its page has a Members tab.
}Errors
400 invalid_request · 401 unauthorized · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/me/avatar" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"image":"image"}'await branchscope.me.setAvatar({ image: "image" });DELETE /v1/me/avatar
Remove your avatar · branchscope.me.deleteAvatar()
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Response 200
{
name: string // Their name as they set it on their profile; empty uses their sign-in's.
company: string
bio: string // A few words about them, up to 180 characters.
location: string
timezone: string // An IANA time zone their local time is shown in; empty when they don't show it.
links: ({
kind: "website" | "linkedin" | "mastodon" | "bluesky" | "x" | "facebook" | "instagram" // A website, or which social network the account is on.
url: string
})[] // Up to 4 websites, and at most one account on each social network.
avatarUrl: string | null // Served by Branchscope; null without an avatar.
showEmail: boolean // Whether they show their email on their profile.
email: string | null // Their email, when they show it; otherwise null.
showRepositories?: boolean // An organization's: whether its page has a Repositories tab.
showMembers?: boolean // An organization's: whether its page has a Members tab.
}Errors
401 unauthorized · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/me/avatar" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.me.deleteAvatar();GET /v1/me/following
What you follow · branchscope.me.following()
The organizations, with their bio and how many of their repositories you can read, and the repositories you can still read, newest first.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Response 200
{
orgs: ({
name: string
displayName: string
avatarUrl: string | null
bio: string
repoCount: integer
followers: integer
})[]
repos: ({
repo: string
visibility: "private" | "public"
description: string
})[]
}Errors
401 unauthorized · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/me/following" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.me.following();PUT /v1/me/profile
Change your profile · branchscope.me.updateProfile()
Name, company, bio, location, local time, links (websites and social accounts) and whether your email shows. Only the fields sent change.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Request body
{
bio?: string
location?: string
name?: string
company?: string
timezone?: string // An IANA time zone (e.g. Europe/Stockholm) to show their local time in; empty stops showing it.
links?: ({
kind: "website" | "linkedin" | "mastodon" | "bluesky" | "x" | "facebook" | "instagram"
url: string
})[] // http(s) addresses: up to 4 websites, and at most one account on each social network (LinkedIn, Mastodon, Bluesky, X, Facebook, Instagram); replaces the list.
showEmail?: boolean
}Response 200
{
name: string // Their name as they set it on their profile; empty uses their sign-in's.
company: string
bio: string // A few words about them, up to 180 characters.
location: string
timezone: string // An IANA time zone their local time is shown in; empty when they don't show it.
links: ({
kind: "website" | "linkedin" | "mastodon" | "bluesky" | "x" | "facebook" | "instagram" // A website, or which social network the account is on.
url: string
})[] // Up to 4 websites, and at most one account on each social network.
avatarUrl: string | null // Served by Branchscope; null without an avatar.
showEmail: boolean // Whether they show their email on their profile.
email: string | null // Their email, when they show it; otherwise null.
showRepositories?: boolean // An organization's: whether its page has a Repositories tab.
showMembers?: boolean // An organization's: whether its page has a Members tab.
}Errors
400 invalid_request · 401 unauthorized · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/me/profile" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'await branchscope.me.updateProfile();branchscope.audit
GET /v1/me/audit
Your audit log · branchscope.audit.listMine()
Your external tokens saved and deleted, and your personal repositories transferred or deleted. From the web app only: a session, never a token or an app.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
actionquery | string · Only this action, or those starting with it: secret for secret.set and secret.deleted. |
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
id: integer
at: string (time)
actor: string // The person (a username).
actorKind: "person" | "agent" | "app" // person: they did it themselves; agent or app: one acting for them.
agent: string | null // The agent's name, or app:<slug> for an app; null for a person.
scope: "repository" | "organization" | "user"
target: string // owner/name for a repository, the name for an organization, the username for a person's own.
action: string // Dotted, such as secret.set, mirror.created, ruleset.updated, repo.visibility_changed, collaborator.role_changed, run.approved, webhook.deleted, external_token.created.
subject: string // What it was done to: a secret's or ruleset's name, a mirror's URL, a webhook's host, a collaborator's username, a run's name.
details: Record<string, unknown> // More about it, never secret: the fields changed, old and new visibility or role.
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 401 unauthorized · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/me/audit" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.audit.listMine();GET /v1/orgs/{org}/audit
An organization's audit log · branchscope.audit.listForOrg()
Its secrets, webhooks and integrations changed, and its repositories transferred in or out or deleted; and, unless repositories=false, its repositories' own entries (each with the repository as its target). Organization owners, with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
orgpath, required | string |
actionquery | string · Only this action, or those starting with it: secret for secret.set and secret.deleted. |
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
repositoriesquery | "true" | "false" · false: only the organization's own entries. Left out or true: its repositories' entries too. |
Response 200
{
data: ({
id: integer
at: string (time)
actor: string // The person (a username).
actorKind: "person" | "agent" | "app" // person: they did it themselves; agent or app: one acting for them.
agent: string | null // The agent's name, or app:<slug> for an app; null for a person.
scope: "repository" | "organization" | "user"
target: string // owner/name for a repository, the name for an organization, the username for a person's own.
action: string // Dotted, such as secret.set, mirror.created, ruleset.updated, repo.visibility_changed, collaborator.role_changed, run.approved, webhook.deleted, external_token.created.
subject: string // What it was done to: a secret's or ruleset's name, a mirror's URL, a webhook's host, a collaborator's username, a run's name.
details: Record<string, unknown> // More about it, never secret: the fields changed, old and new visibility or role.
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/orgs/acme/audit" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.audit.listForOrg({ org: "acme" });GET /v1/repos/{owner}/{name}/audit
A repository's audit log · branchscope.audit.listForRepo()
Secrets set and deleted; mirrors added, changed, deleted and synced by hand; rulesets added, changed (Protected branches too) and deleted; environments' protection changed; webhooks added, changed (or their secret rotated) and deleted; integrations added, changed and deleted; collaborators added, removed and their role changed; the repository made public or private, archived, unarchived, transferred or deleted; runs whose secrets were approved. Its entries move with it when it's transferred, and go when it's deleted (the deletion is recorded on its owner). Repository administrators, with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
actionquery | string · Only this action, or those starting with it: secret for secret.set and secret.deleted. |
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
id: integer
at: string (time)
actor: string // The person (a username).
actorKind: "person" | "agent" | "app" // person: they did it themselves; agent or app: one acting for them.
agent: string | null // The agent's name, or app:<slug> for an app; null for a person.
scope: "repository" | "organization" | "user"
target: string // owner/name for a repository, the name for an organization, the username for a person's own.
action: string // Dotted, such as secret.set, mirror.created, ruleset.updated, repo.visibility_changed, collaborator.role_changed, run.approved, webhook.deleted, external_token.created.
subject: string // What it was done to: a secret's or ruleset's name, a mirror's URL, a webhook's host, a collaborator's username, a run's name.
details: Record<string, unknown> // More about it, never secret: the fields changed, old and new visibility or role.
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/audit" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.audit.listForRepo({ owner: "acme", name: "web" });branchscope.identities
GET /v1/me/identities
Your linked identities · branchscope.identities.list()
Your accounts at organizations' identity providers, linked to your Branchscope account, and the organizations you belong to whose provider you could link (linkable). From the web app (a session).
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Response 200
{
data: ({
org: string
provider: string
username: string // Your username at the provider.
linkedAt: string
lastSignInAt: string | null
sessionExpiresAt: string | null // When your organization session ends, if you have one.
})[]
linkable: ({
org: string
provider: string
required: boolean
})[]
}Errors
401 unauthorized · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/me/identities" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.identities.list();DELETE /v1/me/identities/{org}
Unlink an identity · branchscope.identities.unlink()
In Keycloak too. Your organization sessions there end, and if it requires SSO, your tokens, SSH keys and agents stop reaching its repositories. Refused (conflict) when it's the only way you sign in. From the web app (a session).
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 404 not_found · 409 conflict · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/me/identities/acme" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.identities.unlink({ org: "acme" });branchscope.notifications
GET /v1/me/notifications
Your notifications · branchscope.notifications.list()
What happened where you follow, newest first: pushes to a repository's default branch, changes opened, merged or closed, runs that failed, and new repositories in an organization. Never your own doing, and only from repositories you can still read.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
unreadquery | "true" | "false" · true: only those you haven't read. |
Response 200
{
data: ({
id: integer
kind: "push" | "change.opened" | "change.merged" | "change.closed" | "run.failed" | "repo.created" | "deployment.review_requested" // deployment.review_requested goes to an environment's reviewers, not to followers: subject is the environment, number the run.
repo: string | null
org: string | null // For a new repository: the organization you follow.
actor: string // Who did it; empty when nobody did (a run that failed).
subject: string // The branch pushed to, the change's or run's title, or the new repository's name.
number: integer | null // The change's or run's number.
count: integer | null // For a push: how many commits it brought (101 means more than 100).
url: string // Where it is in the web app, a path.
createdAt: string (time)
read: boolean
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 401 unauthorized · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/me/notifications" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.notifications.list();POST /v1/me/notifications/{id}/read
Mark a notification read · branchscope.notifications.read()
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
idpath, required | integer |
Response 200
{
unread: integer // How many are still unread.
}Errors
400 invalid_request · 401 unauthorized · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/me/notifications/7/read" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.notifications.read({ id: 7 });POST /v1/me/notifications/read
Mark all your notifications read · branchscope.notifications.readAll()
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Response 200
{
unread: integer
}Errors
401 unauthorized · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/me/notifications/read" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.notifications.readAll();branchscope.passkeys
GET /v1/me/passkeys
Your passkeys · branchscope.passkeys.list()
Newest first, as Keycloak holds them; changes made in Keycloak are found and audited first. canRemoveLast says whether you could sign in without any (a password or a linked identity). upstream_error when Keycloak can't be reached, never an empty list. From the web app (a session).
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Response 200
{
data: ({
id: string // Keycloak's id for the credential.
label: string // The name you gave it.
createdAt: string | null
lastUsedAt: string | null // When it last signed you in, from Keycloak's sign-in events (kept 30 days there); null if not known.
})[]
canRemoveLast: boolean
}Errors
401 unauthorized · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl "$BRANCHSCOPE_API/v1/me/passkeys" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.passkeys.list();PUT /v1/me/passkeys/{id}
Rename a passkey · branchscope.passkeys.rename()
In Keycloak too; the label is trimmed, 1-100 characters. From the web app (a session).
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
idpath, required | string |
Request body
{
label: string
}Response 200
{
id: string // Keycloak's id for the credential.
label: string // The name you gave it.
createdAt: string | null
lastUsedAt: string | null // When it last signed you in, from Keycloak's sign-in events (kept 30 days there); null if not known.
}Errors
400 invalid_request · 401 unauthorized · 404 not_found · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl -X PUT "$BRANCHSCOPE_API/v1/me/passkeys/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"label":"label"}'await branchscope.passkeys.rename({ id: 7, label: "label" });DELETE /v1/me/passkeys/{id}
Remove a passkey · branchscope.passkeys.remove()
In Keycloak too. Refused (conflict) when it's the only way you sign in: no password, no other passkey, no linked identity. Your sessions stay. From the web app (a session).
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
idpath, required | string |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 404 not_found · 409 conflict · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/me/passkeys/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.passkeys.remove({ id: 7 });branchscope.namespaces
GET /v1/namespaces/{name}
A user or organization · branchscope.namespaces.get()
Your role in it (organizations) and whether you can create repositories in it.
No credential needed; what you see depends on the one you send.
OAuth apps need the `org:read` scope.
Parameters
namepath, required | string |
Response 200
{
name: string
kind: "user" | "org"
displayName: string
myRole: "owner" | "member" | null // Your role in an organization.
canCreateRepos: boolean
profile: {
name: string // Their name as they set it on their profile; empty uses their sign-in's.
company: string
bio: string // A few words about them, up to 180 characters.
location: string
timezone: string // An IANA time zone their local time is shown in; empty when they don't show it.
links: ProfileLink[] // Up to 4 websites, and at most one account on each social network.
avatarUrl: string | null // Served by Branchscope; null without an avatar.
showEmail: boolean // Whether they show their email on their profile.
email: string | null // Their email, when they show it; otherwise null.
showRepositories?: boolean // An organization's: whether its page has a Repositories tab.
showMembers?: boolean // An organization's: whether its page has a Members tab.
} | null // Its profile; null for a person who hasn't signed in since it was made.
repoCount: integer // How many of its repositories you can read.
followers?: integer // An organization's: how many people follow it.
following?: boolean // An organization's: whether you follow it (false signed out).
orgs?: ({
org: string
avatarUrl: string | null
})[] // A person's: the organizations they belong to, each with its picture.
sso?: {
provider: string
required: boolean
met: boolean // Whether your session or credential meets it now.
role: "owner" | "member" // Your role, whether or not you meet it (myRole is null until you do).
} | null // An organization's, for its members: its identity provider (M18), whether it requires SSO, and whether you meet it. null when it has none turned on.
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/namespaces/web" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.namespaces.get({ name: "web" });GET /v1/namespaces/{name}/repos
A user's or organization's repositories that you can read · branchscope.namespaces.listRepos()
By name.
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
namepath, required | string |
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
repo: string
visibility: "private" | "public"
access: "read" | "write" | "admin" // The caller's access: the highest of ownership, organization role, collaborator role and visibility.
description: string
lastPushAt: string | null // When it was last pushed to; null before its first push.
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/namespaces/web/repos" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.namespaces.listRepos({ name: "web" });branchscope.openapi
GET /v1/openapi.json
This API's OpenAPI document · branchscope.openapi.get()
No credential needed; what you see depends on the one you send.
Response 200
application/json · The OpenAPI 3.1 document, as checked in to packages/api.Errors
429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/openapi.json" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.openapi.get();branchscope.orgs
POST /v1/orgs
Create an organization · branchscope.orgs.create()
You become its owner. Give its name as written (`displayName`, e.g. "Acme Corp"), and its namespace is made from it (`acme-corp`); or give the namespace (`name`) yourself.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
name?: string // Its namespace; made from displayName when left out.
displayName?: string // Its name as written, shown on its page.
}Response 201
{
name: string
kind: "org"
displayName: string
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/orgs" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'await branchscope.orgs.create();DELETE /v1/orgs/{org}
Delete an organization · branchscope.orgs.delete()
With all its repositories (their git data, changes, reviews, graphs, runs, checks and webhooks), its apps, its installations of other apps, its webhooks, members, runners and hosted access. It can't be undone. Owners and site administrators, signed in to the web UI.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Response 200
{
removed: true
repos: string[] // The repositories deleted with it.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/orgs/acme" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.orgs.delete({ org: "acme" });GET /v1/orgs/{org}/avatar
An organization's picture · branchscope.orgs.getAvatar()
No credential needed; what you see depends on the one you send.
Parameters
orgpath, required | string |
Response 200
image/png · The image (PNG, JPEG or WebP), served by Branchscope.Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/orgs/acme/avatar" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.orgs.getAvatar({ org: "acme" });PUT /v1/orgs/{org}/avatar
Upload an organization's picture · branchscope.orgs.setAvatar()
PNG, JPEG or WebP, up to 256 KB, sent as base64. Owners and site administrators.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Request body
{
image: string // The image's bytes, base64.
}Response 200
{
name: string // Their name as they set it on their profile; empty uses their sign-in's.
company: string
bio: string // A few words about them, up to 180 characters.
location: string
timezone: string // An IANA time zone their local time is shown in; empty when they don't show it.
links: ({
kind: "website" | "linkedin" | "mastodon" | "bluesky" | "x" | "facebook" | "instagram" // A website, or which social network the account is on.
url: string
})[] // Up to 4 websites, and at most one account on each social network.
avatarUrl: string | null // Served by Branchscope; null without an avatar.
showEmail: boolean // Whether they show their email on their profile.
email: string | null // Their email, when they show it; otherwise null.
showRepositories?: boolean // An organization's: whether its page has a Repositories tab.
showMembers?: boolean // An organization's: whether its page has a Members tab.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/orgs/acme/avatar" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"image":"image"}'await branchscope.orgs.setAvatar({ org: "acme", image: "image" });DELETE /v1/orgs/{org}/avatar
Remove an organization's picture · branchscope.orgs.deleteAvatar()
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Response 200
{
name: string // Their name as they set it on their profile; empty uses their sign-in's.
company: string
bio: string // A few words about them, up to 180 characters.
location: string
timezone: string // An IANA time zone their local time is shown in; empty when they don't show it.
links: ({
kind: "website" | "linkedin" | "mastodon" | "bluesky" | "x" | "facebook" | "instagram" // A website, or which social network the account is on.
url: string
})[] // Up to 4 websites, and at most one account on each social network.
avatarUrl: string | null // Served by Branchscope; null without an avatar.
showEmail: boolean // Whether they show their email on their profile.
email: string | null // Their email, when they show it; otherwise null.
showRepositories?: boolean // An organization's: whether its page has a Repositories tab.
showMembers?: boolean // An organization's: whether its page has a Members tab.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/orgs/acme/avatar" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.orgs.deleteAvatar({ org: "acme" });GET /v1/orgs/{org}/connections/services
Service names across an organization's repositories · branchscope.orgs.connectionServices()
Who serves each service (the repository whose branchscope.json gives it a project) and who calls it, from each repository's default branch graph. Its owners also see the problems: services two repositories claim, services called that none serves, and near-miss names as suggestions. Only repositories you can read count; the others are left out as if they didn't exist.
No credential needed; what you see depends on the one you send.
OAuth apps need the `graphs:read` scope.
Parameters
orgpath, required | string |
Response 200
{
owner: string
repositories: ({
repo: string
branch: string // The default branch it was indexed from.
commit: string // The commit its connections come from: the newest default-branch head whose graph was built.
latest: {
commit: string // A commit ID (40 or 64 hex characters).
status: "queued" | "running" | "failed"
} | null // A newer build of the default branch's head that hasn't finished, or failed; null when the head is what's indexed.
})[] // The repositories it covers: those you can read that have been indexed, by name.
services: ({
name: string
servedBy: string[] // Repositories whose branchscope.json gives it a project. More than one is a duplicate claim; none, an unserved service.
calledBy: ({
repo: string
endpoints: integer // How many of its endpoints this repository calls.
})[] // Repositories calling its endpoints.
endpoints: integer // Endpoints its servers handle.
})[] // Every service served or called, by name.
problems: {
duplicates: ({
service: string
repos: string[]
})[] // Services more than one repository claims.
unserved: ({
service: string
calledBy: ({
repo: string
endpoints: integer // How many of its endpoints this repository calls.
})[]
})[] // Services clients call that no repository serves.
suggestions: ({
service: string
suggestion: string
servedBy: string[]
})[] // An unserved name close to a served one (billing and billing-api): a suggestion, never matched automatically.
} | null // For an organization's owners, or the person whose repositories these are; null for anyone else.
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/orgs/acme/connections/services" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.orgs.connectionServices({ org: "acme" });PUT /v1/orgs/{org}/follow
Follow an organization · branchscope.orgs.follow()
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Response 200
{
following: boolean
followers: integer
}Errors
400 invalid_request · 401 unauthorized · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/orgs/acme/follow" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.orgs.follow({ org: "acme" });DELETE /v1/orgs/{org}/follow
Stop following an organization · branchscope.orgs.unfollow()
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Response 200
{
following: boolean
followers: integer
}Errors
400 invalid_request · 401 unauthorized · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/orgs/acme/follow" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.orgs.unfollow({ org: "acme" });GET /v1/orgs/{org}/graph
An organization's services and how they connect · branchscope.orgs.graph()
One box per service (or per repository whose code serves none), with HTTP and gRPC calls, messages and shared tables between them, each with the endpoints, channels or tables behind it and the functions on each side, from each repository's default branch graph. All services leaves out archived repositories and those with no connections; ?view= draws a saved view instead. ?against=<environment> draws each repository as deployed there, where it has a current successful deployment whose commit has a graph, and its default branch otherwise, saying why. The findings (endpoints nobody calls, calls nobody serves, tables more than one service writes, dependency cycles) come from every repository you can read, whatever is drawn. Only repositories you can read count; the others are left out as if they didn't exist.
No credential needed; what you see depends on the one you send.
OAuth apps need the `graphs:read` scope.
Parameters
orgpath, required | string |
viewquery | string · A saved view's slug. |
againstquery | string · An environment, e.g. production: compare with each repository's commit deployed there instead of its default branch. |
Response 200
{
schemaVersion: string // Semver (SPEC §8). The connections and owner-graph documents version on their own line from 0.5.
kind: "owner-graph"
owner: string // The organization or person whose repositories these are.
view: null | {
slug: string
name: string
} // The saved view drawn; null for All services.
kinds: GraphKind[] // The kinds of connection drawn, sorted.
against: string | null // The environment whose deployed commits were asked for as the other side (?against=); null for each repository's default branch.
environments: string[] // The environments at least one of the repositories joined has a current successful deployment to, sorted: what against can name.
repositories: PinnedRepository[] // The repositories drawn, sorted by repo: each one's indexed commit and how fresh it is.
nodes: OwnerNode[] // One per service, or per repository for code that serves none, sorted by id.
edges: OwnerEdge[] // Sorted by from, to, kind.
findings: {
unused: UnusedFinding[] // Sorted by id.
unserved: UnservedFinding[] // Sorted by id.
sharedTables: SharedTableFinding[] // Sorted by id.
cycles: CycleFinding[] // Sorted by their first node.
} // Across all the owner's repositories the reader can read; listed when they involve a repository drawn. Shown, never enforced.
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/orgs/acme/graph" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.orgs.graph({ org: "acme" });GET /v1/orgs/{org}/graph/views
The saved views of an organization's graph · branchscope.orgs.listGraphViews()
By name. Anyone who can see the graph can open them; each lists only the repositories you can read.
No credential needed; what you see depends on the one you send.
OAuth apps need the `graphs:read` scope.
Parameters
orgpath, required | string |
Response 200
{
data: ({
slug: string // Its link: /{owner}/graph?view={slug}.
name: string
repositories: string[] // Its repositories, owner/name, sorted: only those you can read.
kinds: ("http" | "messages" | "tables")[] | null // The kinds of connection it draws; null for every kind.
createdBy: string | null
createdAt: string (time)
updatedAt: string (time)
})[]
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/orgs/acme/graph/views" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.orgs.listGraphViews({ org: "acme" });POST /v1/orgs/{org}/graph/views
Save a view of an organization's graph · branchscope.orgs.createGraphView()
Its owners only, as a person (never an agent or an app). Its repositories must be the owner's. Recorded in the audit log.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Request body
{
name: string
slug?: string // From the name if left out.
repositories: string[] // The owner's repositories, owner/name.
kinds?: ("http" | "messages" | "tables")[] | null // Left out or null: every kind.
}Response 201
{
slug: string // Its link: /{owner}/graph?view={slug}.
name: string
repositories: string[] // Its repositories, owner/name, sorted: only those you can read.
kinds: ("http" | "messages" | "tables")[] | null // The kinds of connection it draws; null for every kind.
createdBy: string | null
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/orgs/acme/graph/views" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"web","repositories":[]}'await branchscope.orgs.createGraphView({ org: "acme", name: "web", repositories: [] });PUT /v1/orgs/{org}/graph/views/{slug}
Rename or change a view of an organization's graph · branchscope.orgs.updateGraphView()
Its owners only, as a person. Recorded in the audit log.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
orgpath, required | string |
slugpath, required | string |
Request body
{
name?: string
slug?: string // From the name if left out.
repositories?: string[] // The owner's repositories, owner/name.
kinds?: ("http" | "messages" | "tables")[] | null // Left out or null: every kind.
}Response 200
{
slug: string // Its link: /{owner}/graph?view={slug}.
name: string
repositories: string[] // Its repositories, owner/name, sorted: only those you can read.
kinds: ("http" | "messages" | "tables")[] | null // The kinds of connection it draws; null for every kind.
createdBy: string | null
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/orgs/acme/graph/views/slug" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'await branchscope.orgs.updateGraphView({ org: "acme", slug: "slug" });DELETE /v1/orgs/{org}/graph/views/{slug}
Delete a view of an organization's graph · branchscope.orgs.deleteGraphView()
Its owners only, as a person. Recorded in the audit log.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
orgpath, required | string |
slugpath, required | string |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/orgs/acme/graph/views/slug" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.orgs.deleteGraphView({ org: "acme", slug: "slug" });GET /v1/orgs/{org}/members
An organization's members · branchscope.orgs.listMembers()
Owners first, then by username.
No credential needed; what you see depends on the one you send.
OAuth apps need the `org:read` scope.
Parameters
orgpath, required | string |
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
username: string
name: string
role: "owner" | "member"
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/orgs/acme/members" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.orgs.listMembers({ org: "acme" });POST /v1/orgs/{org}/members
Add or update an organization member · branchscope.orgs.addMember()
Organization owners and site administrators. The person must have signed in once.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
username: string
role?: "owner" | "member"
}Response 201
{
org: string
username: string
role: "owner" | "member"
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/orgs/acme/members" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"username":"bob"}'await branchscope.orgs.addMember({ org: "acme", username: "bob" });DELETE /v1/orgs/{org}/members/{username}
Remove an organization member · branchscope.orgs.removeMember()
An organization keeps at least one owner.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
orgpath, required | string |
usernamepath, required | string |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/orgs/acme/members/bob" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.orgs.removeMember({ org: "acme", username: "bob" });PUT /v1/orgs/{org}/profile
Change an organization's profile · branchscope.orgs.updateProfile()
Its name as written, bio, location, links (websites and social accounts) and a public email. Only the fields sent change. Owners and site administrators.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Request body
{
name?: string // Its name as written; its namespace doesn't change.
bio?: string
location?: string
links?: ({
kind: "website" | "linkedin" | "mastodon" | "bluesky" | "x" | "facebook" | "instagram"
url: string
})[] // http(s) addresses: its website, and its LinkedIn page (an organization's only social account); replaces the list.
email?: string | "" // Shown on its profile; empty shows none.
showRepositories?: boolean // Whether its page has a Repositories tab.
showMembers?: boolean // Whether its page has a Members tab.
}Response 200
{
name: string // Their name as they set it on their profile; empty uses their sign-in's.
company: string
bio: string // A few words about them, up to 180 characters.
location: string
timezone: string // An IANA time zone their local time is shown in; empty when they don't show it.
links: ({
kind: "website" | "linkedin" | "mastodon" | "bluesky" | "x" | "facebook" | "instagram" // A website, or which social network the account is on.
url: string
})[] // Up to 4 websites, and at most one account on each social network.
avatarUrl: string | null // Served by Branchscope; null without an avatar.
showEmail: boolean // Whether they show their email on their profile.
email: string | null // Their email, when they show it; otherwise null.
showRepositories?: boolean // An organization's: whether its page has a Repositories tab.
showMembers?: boolean // An organization's: whether its page has a Members tab.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/orgs/acme/profile" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'await branchscope.orgs.updateProfile({ org: "acme" });branchscope.agents
GET /v1/orgs/{org}/agents
An organization's agents · branchscope.agents.listForOrg()
Its installed agent apps (M14), each with its installation, open changes, recent changes, the push guard's refusals and MCP activity. For the organization's members.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `org:read` scope.
Parameters
orgpath, required | string |
Response 200
{
data: ({
installation: Installation
openChanges: integer
changes: ({
number: integer
title: string
description: string
sourceBranch: string
targetBranch: string
status: "open" | "merged" | "closed"
author: string | null // Who opened it (null for older changes, opened before authors were recorded).
agent: string | null // The agent that opened it, when an agent credential was used.
mergedSha: string | null
mergeMethod: "merge" | "squash" | "rebase" | null // How it merged; null while it's open or closed.
override: {
checks: string[]
reason: string
by: string
} | null // An administrator merged it although these required checks hadn't passed (M15).
createdAt: string (time)
updatedAt: string (time)
repo: string
})[] // Its most recently updated changes in the organization.
refusals: PushEvent[] // Its latest pushes and merges the push guard refused.
activity: AgentEvent[] // Its latest MCP sessions and writes.
tasks: Task[] // Its open tasks: queued, working or blocked.
})[]
}Errors
400 invalid_request · 401 unauthorized · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/orgs/acme/agents" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.agents.listForOrg({ org: "acme" });GET /v1/repos/{owner}/{name}/agents
The agent apps a repository can hand work to · branchscope.agents.listForRepo()
Agent apps installed in its organization on it, paused ones included (their tasks wait).
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
Response 200
{
data: ({
slug: string
name: string
description: string
homepage: string | null
owner: string // The user or organization that registered it.
scopes: string[] // What it may ask for.
iconUrl: string | null // Its icon, served by Branchscope; null when it has none.
firstParty: boolean // Built into Branchscope (the Branchscope CLI).
agent: boolean // An agent app (M14): organizations can delegate work to it, and see it on their Agents page.
dynamic: boolean // Registered by a client itself (RFC 7591), such as an MCP client: no one at Branchscope or an organization vouches for it.
paused: boolean
})[]
}Errors
400 invalid_request · 401 unauthorized · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/agents" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.agents.listForRepo({ owner: "acme", name: "web" });branchscope.installations
GET /v1/orgs/{org}/installations
The apps installed in an organization · branchscope.installations.list()
The organization's owners (and site administrators), from the web app (a session).
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
orgpath, required | string |
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
id: integer // The app passes it as installation_id to get installation tokens.
app: AppPublic
org: string
repos: string[] | null // null: every repository of the organization, those added later too.
maxOpenChanges: integer // How many changes it may have open in the organization at once (M14).
pausedAt: string (time) | null // Paused by the organization: its tokens are refused and its sessions ended until it's resumed.
createdBy: string | null
createdAt: string (time)
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/orgs/acme/installations" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.installations.list({ org: "acme" });POST /v1/orgs/{org}/installations
Install an app in an organization · branchscope.installations.create()
The app then acts as its bot, app-<slug>, on the chosen repositories, within its scopes, and gets their events at its webhook. Only confidential apps can be installed. The organization's owners (and site administrators), from the web app (a session).
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
app: string // The app's slug.
repos?: string[] | null // null for every repository of the organization, or up to 100 of its owner/name.
}Response 201
{
id: integer // The app passes it as installation_id to get installation tokens.
app: {
slug: string
name: string
description: string
homepage: string | null
owner: string // The user or organization that registered it.
scopes: string[] // What it may ask for.
iconUrl: string | null // Its icon, served by Branchscope; null when it has none.
firstParty: boolean // Built into Branchscope (the Branchscope CLI).
agent: boolean // An agent app (M14): organizations can delegate work to it, and see it on their Agents page.
dynamic: boolean // Registered by a client itself (RFC 7591), such as an MCP client: no one at Branchscope or an organization vouches for it.
}
org: string
repos: string[] | null // null: every repository of the organization, those added later too.
maxOpenChanges: integer // How many changes it may have open in the organization at once (M14).
pausedAt: string (time) | null // Paused by the organization: its tokens are refused and its sessions ended until it's resumed.
createdBy: string | null
createdAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/orgs/acme/installations" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"app":"app"}'await branchscope.installations.create({ org: "acme", app: "app" });PUT /v1/orgs/{org}/installations/{slug}
Change an installed app's repositories and limits, or pause it · branchscope.installations.update()
The organization's owners (and site administrators), from the web app (a session).
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
orgpath, required | string |
slugpath, required | string |
Request body
{
repos?: string[] | null // null for every repository of the organization.
maxOpenChanges?: integer // How many changes it may have open at once (default 10).
paused?: boolean // Pause: its tokens are refused and its MCP sessions end, until resumed.
}Response 200
{
id: integer // The app passes it as installation_id to get installation tokens.
app: {
slug: string
name: string
description: string
homepage: string | null
owner: string // The user or organization that registered it.
scopes: string[] // What it may ask for.
iconUrl: string | null // Its icon, served by Branchscope; null when it has none.
firstParty: boolean // Built into Branchscope (the Branchscope CLI).
agent: boolean // An agent app (M14): organizations can delegate work to it, and see it on their Agents page.
dynamic: boolean // Registered by a client itself (RFC 7591), such as an MCP client: no one at Branchscope or an organization vouches for it.
}
org: string
repos: string[] | null // null: every repository of the organization, those added later too.
maxOpenChanges: integer // How many changes it may have open in the organization at once (M14).
pausedAt: string (time) | null // Paused by the organization: its tokens are refused and its sessions ended until it's resumed.
createdBy: string | null
createdAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/orgs/acme/installations/slug" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'await branchscope.installations.update({ org: "acme", slug: "slug" });DELETE /v1/orgs/{org}/installations/{slug}
Remove an app from an organization · branchscope.installations.delete()
Its installation tokens stop working at once. The organization's owners (and site administrators), from the web app (a session).
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
orgpath, required | string |
slugpath, required | string |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/orgs/acme/installations/slug" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.installations.delete({ org: "acme", slug: "slug" });branchscope.linear
GET /v1/orgs/{org}/linear
An organization's Linear connection · branchscope.linear.get()
Its workspace, teams and their workflow states, and the automations; never the API key. connection is null when none is connected. The organization's owners (and site administrators), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Response 200
{
available: boolean // Whether this server can keep a credential (its SECRETS_KEY is set).
oauth: {
available: boolean // Whether this server has a Linear app, to connect with linear.startOAuth (LINEAR_CLIENT_ID, LINEAR_CLIENT_SECRET).
}
connection: {
id: integer // Its integration's: its deliveries are at integrations.listDeliveries.
org: string
workspace: {
id: string
name: string
urlKey: string
}
actor: string // Who Branchscope acts as in Linear (the app, or the API key's member): attachments, state changes and comments show under this name.
auth: "api_key" | "oauth" // How it's connected: with the server's Linear app (oauth), or an API key.
destination: string // The API key's last 4 characters, or OAuth.
tokenExpiresAt: string (time) | null // When the OAuth access token expires (it's refreshed shortly before); null for an API key, or a token that doesn't expire.
inbound: {
mode: "app" | "webhook" | null // How Linear tells Branchscope about changes to linked issues: the server's app webhook, the connection's own webhook, or not at all (null: issues are read from Linear when shown).
url: string | null // Where Linear sends events; for an API key connection without inbound, the URL to give a webhook made in Linear by hand.
created: boolean // Whether Branchscope made the webhook in Linear itself (it's deleted on disconnect).
lastEventAt: string (time) | null // When the last event from Linear arrived.
} // Linear's webhook back to Branchscope (M33).
moveIssues: boolean // Whether issues move through the teams' mapped states as changes are opened, reviewed and merged.
postComments: boolean // Whether a finished review posts (and, re-run, updates) one comment on each linked issue.
active: boolean // Paused connections send nothing; Branchscope pauses one whose updates failed for a day.
disabledReason: string | null
teams: LinearTeam[]
createdBy: string
createdAt: string (time)
syncedAt: string (time) // When its teams and states were last read from Linear.
} | null
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/orgs/acme/linear" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.linear.get({ org: "acme" });PUT /v1/orgs/{org}/linear
Connect a Linear workspace · branchscope.linear.connect()
With a Linear API key, checked by reading the workspace and its teams, then sealed and never returned. Sent again, it replaces the key (for the same workspace; disconnect first to change workspace). The organization's owners (and site administrators), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Request body
{
apiKey: string // A Linear API key (Settings → Security & access → Personal API keys in Linear), best one of a member set aside for Branchscope.
}Response 200
{
id: integer // Its integration's: its deliveries are at integrations.listDeliveries.
org: string
workspace: {
id: string
name: string
urlKey: string
}
actor: string // Who Branchscope acts as in Linear (the app, or the API key's member): attachments, state changes and comments show under this name.
auth: "api_key" | "oauth" // How it's connected: with the server's Linear app (oauth), or an API key.
destination: string // The API key's last 4 characters, or OAuth.
tokenExpiresAt: string (time) | null // When the OAuth access token expires (it's refreshed shortly before); null for an API key, or a token that doesn't expire.
inbound: {
mode: "app" | "webhook" | null // How Linear tells Branchscope about changes to linked issues: the server's app webhook, the connection's own webhook, or not at all (null: issues are read from Linear when shown).
url: string | null // Where Linear sends events; for an API key connection without inbound, the URL to give a webhook made in Linear by hand.
created: boolean // Whether Branchscope made the webhook in Linear itself (it's deleted on disconnect).
lastEventAt: string (time) | null // When the last event from Linear arrived.
} // Linear's webhook back to Branchscope (M33).
moveIssues: boolean // Whether issues move through the teams' mapped states as changes are opened, reviewed and merged.
postComments: boolean // Whether a finished review posts (and, re-run, updates) one comment on each linked issue.
active: boolean // Paused connections send nothing; Branchscope pauses one whose updates failed for a day.
disabledReason: string | null
teams: ({
id: string
key: string // Its issue keys' prefix: ENG for ENG-123.
name: string
states: LinearState[]
started: string | null // The state an issue moves to when a change for it is opened (In Progress).
review: string | null // The state it moves to when the change's review finishes (In Review).
done: string | null // The state it moves to when the change is merged (Done).
})[]
createdBy: string
createdAt: string (time)
syncedAt: string (time) // When its teams and states were last read from Linear.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal · 502 upstream_error · 503 unavailable
Example
curl -X PUT "$BRANCHSCOPE_API/v1/orgs/acme/linear" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"apiKey":"apiKey"}'await branchscope.linear.connect({ org: "acme", apiKey: "apiKey" });DELETE /v1/orgs/{org}/linear
Disconnect Linear · branchscope.linear.disconnect()
An OAuth connection's token is revoked in Linear first; an API key can't be, so revoke it in Linear's settings. Then the credential, the mapping, the delivery and inbound logs, stored issues and changes' links to issues are deleted, even if Linear refused or couldn't be reached. The organization's owners (and site administrators), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Response 200
{
removed: true
revoked: boolean | null // Whether Linear revoked the OAuth token: false if it refused or couldn't be reached (remove the app in the workspace's settings in Linear), null for an API key, which only Linear's settings can revoke.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/orgs/acme/linear" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.linear.disconnect({ org: "acme" });GET /v1/orgs/{org}/linear/events
A Linear connection's inbound log · branchscope.linear.listEvents()
The last 7 days' events from Linear, newest first (up to 200): each one's type and action, the issue's key, and what was done. Events with a bad signature aren't kept; they're counted per hour. The organization's owners (and site administrators), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Response 200
{
data: ({
deliveryId: string // Linear's Linear-Delivery ID.
receivedAt: string (time)
type: string // Issue, Attachment …
action: string // create, update or remove.
identifier: string | null // The issue's key, when the event names one.
result: string // What Branchscope did: ENG-123 moved to Done, ignored: not linked …
})[]
rejected: ({
hour: string (time)
count: integer
})[] // Events refused for a bad signature, per hour (a connection's own webhook only).
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/orgs/acme/linear/events" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.linear.listEvents({ org: "acme" });PUT /v1/orgs/{org}/linear/inbound
Set the signing secret of a Linear webhook made by hand · branchscope.linear.setInbound()
For a connection made with an API key whose member isn't a workspace admin, so Branchscope couldn't make the webhook itself: make one in Linear (Settings → API → Webhooks) sending Issue and Attachment events to the connection's inbound.url, then give its signing secret here. It's sealed and never returned. The organization's owners (and site administrators), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Request body
{
signingSecret: string
}Response 200
{
id: integer // Its integration's: its deliveries are at integrations.listDeliveries.
org: string
workspace: {
id: string
name: string
urlKey: string
}
actor: string // Who Branchscope acts as in Linear (the app, or the API key's member): attachments, state changes and comments show under this name.
auth: "api_key" | "oauth" // How it's connected: with the server's Linear app (oauth), or an API key.
destination: string // The API key's last 4 characters, or OAuth.
tokenExpiresAt: string (time) | null // When the OAuth access token expires (it's refreshed shortly before); null for an API key, or a token that doesn't expire.
inbound: {
mode: "app" | "webhook" | null // How Linear tells Branchscope about changes to linked issues: the server's app webhook, the connection's own webhook, or not at all (null: issues are read from Linear when shown).
url: string | null // Where Linear sends events; for an API key connection without inbound, the URL to give a webhook made in Linear by hand.
created: boolean // Whether Branchscope made the webhook in Linear itself (it's deleted on disconnect).
lastEventAt: string (time) | null // When the last event from Linear arrived.
} // Linear's webhook back to Branchscope (M33).
moveIssues: boolean // Whether issues move through the teams' mapped states as changes are opened, reviewed and merged.
postComments: boolean // Whether a finished review posts (and, re-run, updates) one comment on each linked issue.
active: boolean // Paused connections send nothing; Branchscope pauses one whose updates failed for a day.
disabledReason: string | null
teams: ({
id: string
key: string // Its issue keys' prefix: ENG for ENG-123.
name: string
states: LinearState[]
started: string | null // The state an issue moves to when a change for it is opened (In Progress).
review: string | null // The state it moves to when the change's review finishes (In Review).
done: string | null // The state it moves to when the change is merged (Done).
})[]
createdBy: string
createdAt: string (time)
syncedAt: string (time) // When its teams and states were last read from Linear.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal · 503 unavailable
Example
curl -X PUT "$BRANCHSCOPE_API/v1/orgs/acme/linear/inbound" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"signingSecret":"signingSecret"}'await branchscope.linear.setInbound({ org: "acme", signingSecret: "signingSecret" });POST /v1/orgs/{org}/linear/oauth
Start connecting Linear with the server's Linear app · branchscope.linear.startOAuth()
Where to send the person: Linear's consent page, asking a workspace admin to install the app (actor=app, scopes read and write). Linear sends them back to the web app's /integrations/linear/callback, which finishes with linear.finishOAuth. The attempt is good for one use within 10 minutes, for this organization and person only. The organization's owners (and site administrators), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Response 200
{
authorizeUrl: string // Linear's authorization page, with the state and the PKCE challenge.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal · 503 unavailable
Example
curl -X POST "$BRANCHSCOPE_API/v1/orgs/acme/linear/oauth" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.linear.startOAuth({ org: "acme" });POST /v1/orgs/{org}/linear/oauth/callback
Finish connecting Linear with the server's Linear app · branchscope.linear.finishOAuth()
With the code and state Linear sent back. The code is exchanged (with the client secret and the PKCE verifier), the workspace read, and the token sealed. Connecting the workspace already connected with an API key replaces the key; another workspace is refused. The organization's owners (and site administrators), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Request body
{
code: string
state: string
}Response 200
{
id: integer // Its integration's: its deliveries are at integrations.listDeliveries.
org: string
workspace: {
id: string
name: string
urlKey: string
}
actor: string // Who Branchscope acts as in Linear (the app, or the API key's member): attachments, state changes and comments show under this name.
auth: "api_key" | "oauth" // How it's connected: with the server's Linear app (oauth), or an API key.
destination: string // The API key's last 4 characters, or OAuth.
tokenExpiresAt: string (time) | null // When the OAuth access token expires (it's refreshed shortly before); null for an API key, or a token that doesn't expire.
inbound: {
mode: "app" | "webhook" | null // How Linear tells Branchscope about changes to linked issues: the server's app webhook, the connection's own webhook, or not at all (null: issues are read from Linear when shown).
url: string | null // Where Linear sends events; for an API key connection without inbound, the URL to give a webhook made in Linear by hand.
created: boolean // Whether Branchscope made the webhook in Linear itself (it's deleted on disconnect).
lastEventAt: string (time) | null // When the last event from Linear arrived.
} // Linear's webhook back to Branchscope (M33).
moveIssues: boolean // Whether issues move through the teams' mapped states as changes are opened, reviewed and merged.
postComments: boolean // Whether a finished review posts (and, re-run, updates) one comment on each linked issue.
active: boolean // Paused connections send nothing; Branchscope pauses one whose updates failed for a day.
disabledReason: string | null
teams: ({
id: string
key: string // Its issue keys' prefix: ENG for ENG-123.
name: string
states: LinearState[]
started: string | null // The state an issue moves to when a change for it is opened (In Progress).
review: string | null // The state it moves to when the change's review finishes (In Review).
done: string | null // The state it moves to when the change is merged (Done).
})[]
createdBy: string
createdAt: string (time)
syncedAt: string (time) // When its teams and states were last read from Linear.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal · 502 upstream_error · 503 unavailable
Example
curl -X POST "$BRANCHSCOPE_API/v1/orgs/acme/linear/oauth/callback" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"code":"code","state":"state"}'await branchscope.linear.finishOAuth({ org: "acme", code: "code", state: "state" });POST /v1/orgs/{org}/linear/refresh
Read a Linear workspace's teams and states again · branchscope.linear.refresh()
After a team or a workflow state was added in Linear. Mappings onto states that still exist are kept. The organization's owners (and site administrators), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Response 200
{
id: integer // Its integration's: its deliveries are at integrations.listDeliveries.
org: string
workspace: {
id: string
name: string
urlKey: string
}
actor: string // Who Branchscope acts as in Linear (the app, or the API key's member): attachments, state changes and comments show under this name.
auth: "api_key" | "oauth" // How it's connected: with the server's Linear app (oauth), or an API key.
destination: string // The API key's last 4 characters, or OAuth.
tokenExpiresAt: string (time) | null // When the OAuth access token expires (it's refreshed shortly before); null for an API key, or a token that doesn't expire.
inbound: {
mode: "app" | "webhook" | null // How Linear tells Branchscope about changes to linked issues: the server's app webhook, the connection's own webhook, or not at all (null: issues are read from Linear when shown).
url: string | null // Where Linear sends events; for an API key connection without inbound, the URL to give a webhook made in Linear by hand.
created: boolean // Whether Branchscope made the webhook in Linear itself (it's deleted on disconnect).
lastEventAt: string (time) | null // When the last event from Linear arrived.
} // Linear's webhook back to Branchscope (M33).
moveIssues: boolean // Whether issues move through the teams' mapped states as changes are opened, reviewed and merged.
postComments: boolean // Whether a finished review posts (and, re-run, updates) one comment on each linked issue.
active: boolean // Paused connections send nothing; Branchscope pauses one whose updates failed for a day.
disabledReason: string | null
teams: ({
id: string
key: string // Its issue keys' prefix: ENG for ENG-123.
name: string
states: LinearState[]
started: string | null // The state an issue moves to when a change for it is opened (In Progress).
review: string | null // The state it moves to when the change's review finishes (In Review).
done: string | null // The state it moves to when the change is merged (Done).
})[]
createdBy: string
createdAt: string (time)
syncedAt: string (time) // When its teams and states were last read from Linear.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal · 502 upstream_error · 503 unavailable
Example
curl -X POST "$BRANCHSCOPE_API/v1/orgs/acme/linear/refresh" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.linear.refresh({ org: "acme" });PUT /v1/orgs/{org}/linear/settings
Change a Linear connection's automations · branchscope.linear.update()
Only the fields sent change. teams maps each team's steps onto its own states (null turns that step off). Turning it back on (active) clears why Branchscope paused it. The organization's owners (and site administrators), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Request body
{
moveIssues?: boolean
postComments?: boolean
active?: boolean
teams?: ({
id: string
started: string | null
review: string | null
done: string | null
})[]
}Response 200
{
id: integer // Its integration's: its deliveries are at integrations.listDeliveries.
org: string
workspace: {
id: string
name: string
urlKey: string
}
actor: string // Who Branchscope acts as in Linear (the app, or the API key's member): attachments, state changes and comments show under this name.
auth: "api_key" | "oauth" // How it's connected: with the server's Linear app (oauth), or an API key.
destination: string // The API key's last 4 characters, or OAuth.
tokenExpiresAt: string (time) | null // When the OAuth access token expires (it's refreshed shortly before); null for an API key, or a token that doesn't expire.
inbound: {
mode: "app" | "webhook" | null // How Linear tells Branchscope about changes to linked issues: the server's app webhook, the connection's own webhook, or not at all (null: issues are read from Linear when shown).
url: string | null // Where Linear sends events; for an API key connection without inbound, the URL to give a webhook made in Linear by hand.
created: boolean // Whether Branchscope made the webhook in Linear itself (it's deleted on disconnect).
lastEventAt: string (time) | null // When the last event from Linear arrived.
} // Linear's webhook back to Branchscope (M33).
moveIssues: boolean // Whether issues move through the teams' mapped states as changes are opened, reviewed and merged.
postComments: boolean // Whether a finished review posts (and, re-run, updates) one comment on each linked issue.
active: boolean // Paused connections send nothing; Branchscope pauses one whose updates failed for a day.
disabledReason: string | null
teams: ({
id: string
key: string // Its issue keys' prefix: ENG for ENG-123.
name: string
states: LinearState[]
started: string | null // The state an issue moves to when a change for it is opened (In Progress).
review: string | null // The state it moves to when the change's review finishes (In Review).
done: string | null // The state it moves to when the change is merged (Done).
})[]
createdBy: string
createdAt: string (time)
syncedAt: string (time) // When its teams and states were last read from Linear.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/orgs/acme/linear/settings" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'await branchscope.linear.update({ org: "acme" });branchscope.runners
GET /v1/orgs/{org}/runners
A namespace's runners · branchscope.runners.list()
An organization's (for its members) or your own namespace's self-hosted runners, and its access to hosted runners. `{org}` may be your username.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `org:read` scope.
Parameters
orgpath, required | string |
Response 200
{
data: ({
id: integer
name: string
labels: string[] // A job runs on a runner with its runs-on among these.
online: boolean // Heard from in the last two minutes.
job: integer | null // The job it's running, if any.
createdBy: string | null
createdAt: string (time)
lastSeenAt: string (time) | null
})[]
hosted: {
allowed: boolean // Hosted runners are in early access: a site administrator allows namespaces one by one.
maxConcurrent: integer // Hosted jobs it may run at once.
running: integer // Hosted jobs running now.
}
}Errors
400 invalid_request · 401 unauthorized · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/orgs/acme/runners" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.runners.list({ org: "acme" });DELETE /v1/orgs/{org}/runners/{id}
Remove a runner · branchscope.runners.delete()
Its credential stops working at once; a job it was running is queued again. The organization's owners (and site administrators), from the web app (a session).
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
orgpath, required | string |
idpath, required | integer |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/orgs/acme/runners/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.runners.delete({ org: "acme", id: 7 });POST /v1/orgs/{org}/runners/registration-tokens
A token to register runners with · branchscope.runners.createRegistrationToken()
The organization's owners (and site administrators), from the web app (a session).
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Response 201
{
token: string // Shown once. `bscope-runner register --token …` trades it for the runner's own credential; it works for an hour, for any number of runners.
expiresAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/orgs/acme/runners/registration-tokens" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.runners.createRegistrationToken({ org: "acme" });branchscope.secrets
GET /v1/orgs/{org}/secrets
An organization's secrets · branchscope.secrets.listForOrg()
By name, never with their values. Repository administrators (a repository's secrets) and organization owners (an organization's), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Response 200
{
data: ({
name: string // As its jobs see it: an environment variable's name.
kind: "npm" | "pypi" | "nuget" | "crates" | "rubygems" | "maven" | "docker-hub" | "container-registry" | "hetzner" | "aws" | "google-cloud" | "azure" | "fly" | "digitalocean" | "cloudflare" | "vercel" | "netlify" | "other"
level: "organization" | "repository" | "environment"
repositories: string[] | null // An organization secret's repositories (owner/name), or null for all of them; null for a repository or environment secret.
environment: string | null // An environment secret's environment; null otherwise.
createdBy: string | null // Who set it last.
createdAt: string (time)
updatedAt: string (time)
lastUsedAt: string (time) | null // The last job given it.
})[]
available: boolean // Whether this server can store secrets (its SECRETS_KEY is set). Without it, setting one is refused (unavailable) and runs get none.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/orgs/acme/secrets" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.secrets.listForOrg({ org: "acme" });PUT /v1/orgs/{org}/secrets/{secret}
Set an organization secret · branchscope.secrets.setForOrg()
Made, or replaced if it exists, for all the organization's repositories or only those listed. At most 100 per organization. Repository administrators (a repository's secrets) and organization owners (an organization's), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
orgpath, required | string |
secretpath, required | string |
Request body
{
kind?: "npm" | "pypi" | "nuget" | "crates" | "rubygems" | "maven" | "docker-hub" | "container-registry" | "hetzner" | "aws" | "google-cloud" | "azure" | "fly" | "digitalocean" | "cloudflare" | "vercel" | "netlify" | "other"
value: string // The secret itself, at most 48 KiB. Kept encrypted; it can't be read back, only replaced.
repositories?: string[] | null // null for all the organization's repositories, or up to 100 of them (owner/name).
}Response 200
{
name: string // As its jobs see it: an environment variable's name.
kind: "npm" | "pypi" | "nuget" | "crates" | "rubygems" | "maven" | "docker-hub" | "container-registry" | "hetzner" | "aws" | "google-cloud" | "azure" | "fly" | "digitalocean" | "cloudflare" | "vercel" | "netlify" | "other"
level: "organization" | "repository" | "environment"
repositories: string[] | null // An organization secret's repositories (owner/name), or null for all of them; null for a repository or environment secret.
environment: string | null // An environment secret's environment; null otherwise.
createdBy: string | null // Who set it last.
createdAt: string (time)
updatedAt: string (time)
lastUsedAt: string (time) | null // The last job given it.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal · 503 unavailable
Example
curl -X PUT "$BRANCHSCOPE_API/v1/orgs/acme/secrets/secret" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"value":"value"}'await branchscope.secrets.setForOrg({ org: "acme", secret: "secret", value: "value" });DELETE /v1/orgs/{org}/secrets/{secret}
Delete an organization secret · branchscope.secrets.deleteForOrg()
Repository administrators (a repository's secrets) and organization owners (an organization's), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
orgpath, required | string |
secretpath, required | string |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/orgs/acme/secrets/secret" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.secrets.deleteForOrg({ org: "acme", secret: "secret" });GET /v1/repos/{owner}/{name}/environments/{environment}/secrets
An environment's secrets · branchscope.secrets.listForEnvironment()
By name, never with their values. Only jobs with environment: set to it get them, once its protection passed, and they win over the repository's and organization's of the same name. Repository administrators, with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
environmentpath, required | string |
Response 200
{
data: ({
name: string // As its jobs see it: an environment variable's name.
kind: "npm" | "pypi" | "nuget" | "crates" | "rubygems" | "maven" | "docker-hub" | "container-registry" | "hetzner" | "aws" | "google-cloud" | "azure" | "fly" | "digitalocean" | "cloudflare" | "vercel" | "netlify" | "other"
level: "organization" | "repository" | "environment"
repositories: string[] | null // An organization secret's repositories (owner/name), or null for all of them; null for a repository or environment secret.
environment: string | null // An environment secret's environment; null otherwise.
createdBy: string | null // Who set it last.
createdAt: string (time)
updatedAt: string (time)
lastUsedAt: string (time) | null // The last job given it.
})[]
available: boolean // Whether this server can store secrets (its SECRETS_KEY is set). Without it, setting one is refused (unavailable) and runs get none.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/environments/environment/secrets" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.secrets.listForEnvironment({ owner: "acme", name: "web", environment: "environment" });PUT /v1/repos/{owner}/{name}/environments/{environment}/secrets/{secret}
Set an environment secret · branchscope.secrets.setForEnvironment()
Made, or replaced if it exists; the environment is made if it's new. At most 100 per environment. Repository administrators, with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
environmentpath, required | string |
secretpath, required | string |
Request body
{
kind?: "npm" | "pypi" | "nuget" | "crates" | "rubygems" | "maven" | "docker-hub" | "container-registry" | "hetzner" | "aws" | "google-cloud" | "azure" | "fly" | "digitalocean" | "cloudflare" | "vercel" | "netlify" | "other"
value: string // The secret itself, at most 48 KiB. Kept encrypted; it can't be read back, only replaced.
}Response 200
{
name: string // As its jobs see it: an environment variable's name.
kind: "npm" | "pypi" | "nuget" | "crates" | "rubygems" | "maven" | "docker-hub" | "container-registry" | "hetzner" | "aws" | "google-cloud" | "azure" | "fly" | "digitalocean" | "cloudflare" | "vercel" | "netlify" | "other"
level: "organization" | "repository" | "environment"
repositories: string[] | null // An organization secret's repositories (owner/name), or null for all of them; null for a repository or environment secret.
environment: string | null // An environment secret's environment; null otherwise.
createdBy: string | null // Who set it last.
createdAt: string (time)
updatedAt: string (time)
lastUsedAt: string (time) | null // The last job given it.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal · 503 unavailable
Example
curl -X PUT "$BRANCHSCOPE_API/v1/repos/acme/web/environments/environment/secrets/secret" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"value":"value"}'await branchscope.secrets.setForEnvironment({ owner: "acme", name: "web", environment: "environment", secret: "secret", value: "value" });DELETE /v1/repos/{owner}/{name}/environments/{environment}/secrets/{secret}
Delete an environment secret · branchscope.secrets.deleteForEnvironment()
Jobs deploying to it that name it get the repository's or organization's of the same name, if there's one, or nothing. Repository administrators, with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
environmentpath, required | string |
secretpath, required | string |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/repos/acme/web/environments/environment/secrets/secret" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.secrets.deleteForEnvironment({ owner: "acme", name: "web", environment: "environment", secret: "secret" });GET /v1/repos/{owner}/{name}/secrets
A repository's secrets · branchscope.secrets.listForRepo()
By name, never with their values; organization lists those of its organization this repository can use (its own win over them). Repository administrators (a repository's secrets) and organization owners (an organization's), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
Response 200
{
data: ({
name: string // As its jobs see it: an environment variable's name.
kind: "npm" | "pypi" | "nuget" | "crates" | "rubygems" | "maven" | "docker-hub" | "container-registry" | "hetzner" | "aws" | "google-cloud" | "azure" | "fly" | "digitalocean" | "cloudflare" | "vercel" | "netlify" | "other"
level: "organization" | "repository" | "environment"
repositories: string[] | null // An organization secret's repositories (owner/name), or null for all of them; null for a repository or environment secret.
environment: string | null // An environment secret's environment; null otherwise.
createdBy: string | null // Who set it last.
createdAt: string (time)
updatedAt: string (time)
lastUsedAt: string (time) | null // The last job given it.
})[]
organization: ({
name: string // As its jobs see it: an environment variable's name.
kind: "npm" | "pypi" | "nuget" | "crates" | "rubygems" | "maven" | "docker-hub" | "container-registry" | "hetzner" | "aws" | "google-cloud" | "azure" | "fly" | "digitalocean" | "cloudflare" | "vercel" | "netlify" | "other"
level: "organization" | "repository" | "environment"
repositories: string[] | null // An organization secret's repositories (owner/name), or null for all of them; null for a repository or environment secret.
environment: string | null // An environment secret's environment; null otherwise.
createdBy: string | null // Who set it last.
createdAt: string (time)
updatedAt: string (time)
lastUsedAt: string (time) | null // The last job given it.
})[]
available: boolean // Whether this server can store secrets (its SECRETS_KEY is set). Without it, setting one is refused (unavailable) and runs get none.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/secrets" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.secrets.listForRepo({ owner: "acme", name: "web" });PUT /v1/repos/{owner}/{name}/secrets/{secret}
Set a repository secret · branchscope.secrets.setForRepo()
Made, or replaced if it exists. At most 100 per repository. Repository administrators (a repository's secrets) and organization owners (an organization's), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
secretpath, required | string |
Request body
{
kind?: "npm" | "pypi" | "nuget" | "crates" | "rubygems" | "maven" | "docker-hub" | "container-registry" | "hetzner" | "aws" | "google-cloud" | "azure" | "fly" | "digitalocean" | "cloudflare" | "vercel" | "netlify" | "other"
value: string // The secret itself, at most 48 KiB. Kept encrypted; it can't be read back, only replaced.
}Response 200
{
name: string // As its jobs see it: an environment variable's name.
kind: "npm" | "pypi" | "nuget" | "crates" | "rubygems" | "maven" | "docker-hub" | "container-registry" | "hetzner" | "aws" | "google-cloud" | "azure" | "fly" | "digitalocean" | "cloudflare" | "vercel" | "netlify" | "other"
level: "organization" | "repository" | "environment"
repositories: string[] | null // An organization secret's repositories (owner/name), or null for all of them; null for a repository or environment secret.
environment: string | null // An environment secret's environment; null otherwise.
createdBy: string | null // Who set it last.
createdAt: string (time)
updatedAt: string (time)
lastUsedAt: string (time) | null // The last job given it.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal · 503 unavailable
Example
curl -X PUT "$BRANCHSCOPE_API/v1/repos/acme/web/secrets/secret" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"value":"value"}'await branchscope.secrets.setForRepo({ owner: "acme", name: "web", secret: "secret", value: "value" });DELETE /v1/repos/{owner}/{name}/secrets/{secret}
Delete a repository secret · branchscope.secrets.deleteForRepo()
Jobs that name it get its organization's of the same name, if there's one, or nothing. Repository administrators (a repository's secrets) and organization owners (an organization's), with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
secretpath, required | string |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/repos/acme/web/secrets/secret" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.secrets.deleteForRepo({ owner: "acme", name: "web", secret: "secret" });branchscope.sso
GET /v1/orgs/{org}/sso
An organization's SSO settings · branchscope.sso.get()
Its identity provider (never the client secret), its domains with the DNS records that verify them, and which members have linked their account to it. The organization's owners (and site administrators), from the web app (a session). While it requires SSO, changing these needs an organization session.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Response 200
{
provider: {
protocol: "oidc"
kind: "entra" | "google" | "okta" | "oidc" // Microsoft Entra ID, Google Workspace, Okta, or another OIDC provider.
displayName: string
discoveryUrl: string
issuer: string
clientId: string
hostedDomain: string | null // Google Workspace: the domain whose accounts alone get in.
enabled: boolean // On: verified domains' sign-ins go to it, and it can be required.
requireSso: boolean
sessionHours: integer // How long an organization session lasts after signing in through the provider.
jit: boolean // Just-in-time membership: someone who signs in through it with an email on a verified domain joins as a member.
testedAt: string | null // When an owner last signed in through it since its settings changed; it can't be turned on before.
testedBy: string | null
createdAt: string
updatedAt: string
} | null
domains: ({
domain: string
verified: boolean
verifiedAt: string | null
record: {
type: "TXT"
name: string
value: string
} // The DNS record that proves the organization owns the domain.
createdAt: string
})[]
members: ({
username: string
role: "owner" | "member"
linked: boolean
lastSignInAt: string | null
})[] // Who has linked their account to the provider, and when each last signed in through it.
redirectUri: string | null // The redirect URI to register with the provider.
available: boolean // false when this server can't store a client secret (SECRETS_KEY).
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/orgs/acme/sso" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.sso.get({ org: "acme" });PUT /v1/orgs/{org}/sso
Set an organization's identity provider · branchscope.sso.set()
OIDC: its discovery URL (or issuer), and the client ID and secret it gave you; Keycloak brokers the sign-ins. The secret is sealed and never returned; leave it out to keep it. Changing where the provider is or its client means testing it again (an owner signing in through it) before it can be turned on. The organization's owners (and site administrators), from the web app (a session). While it requires SSO, changing these needs an organization session.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Request body
{
kind: "entra" | "google" | "okta" | "oidc"
displayName: string
discoveryUrl: string // The provider's issuer or its …/.well-known/openid-configuration.
clientId: string
clientSecret?: string
hostedDomain?: string // Google Workspace only, and needed there: your Workspace domain.
enabled?: boolean
requireSso?: boolean
sessionHours?: integer
jit?: boolean
}Response 200
{
protocol: "oidc"
kind: "entra" | "google" | "okta" | "oidc" // Microsoft Entra ID, Google Workspace, Okta, or another OIDC provider.
displayName: string
discoveryUrl: string
issuer: string
clientId: string
hostedDomain: string | null // Google Workspace: the domain whose accounts alone get in.
enabled: boolean // On: verified domains' sign-ins go to it, and it can be required.
requireSso: boolean
sessionHours: integer // How long an organization session lasts after signing in through the provider.
jit: boolean // Just-in-time membership: someone who signs in through it with an email on a verified domain joins as a member.
testedAt: string | null // When an owner last signed in through it since its settings changed; it can't be turned on before.
testedBy: string | null
createdAt: string
updatedAt: string
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 not_ready · 429 rate_limited · 500 internal · 502 upstream_error · 503 unavailable
Example
curl -X PUT "$BRANCHSCOPE_API/v1/orgs/acme/sso" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"kind":"personal","displayName":"displayName","discoveryUrl":"discoveryUrl","clientId":"clientId"}'await branchscope.sso.set({ org: "acme", kind: "personal", displayName: "displayName", discoveryUrl: "discoveryUrl", clientId: "clientId" });DELETE /v1/orgs/{org}/sso
Remove an organization's identity provider · branchscope.sso.delete()
From Keycloak too, with everyone's link to it and their organization sessions; its domains stay. The organization's owners (and site administrators), from the web app (a session). While it requires SSO, changing these needs an organization session.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/orgs/acme/sso" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.sso.delete({ org: "acme" });POST /v1/orgs/{org}/sso/domains
Add a domain to verify · branchscope.sso.addDomain()
Returns the TXT record to add to its DNS. A domain another organization verified can't be added. The organization's owners (and site administrators), from the web app (a session). While it requires SSO, changing these needs an organization session.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
orgpath, required | string |
Request body
{
domain: string
}Response 201
{
domain: string
verified: boolean
verifiedAt: string | null
record: {
type: "TXT"
name: string
value: string
} // The DNS record that proves the organization owns the domain.
createdAt: string
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/orgs/acme/sso/domains" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"domain":"domain"}'await branchscope.sso.addDomain({ org: "acme", domain: "domain" });DELETE /v1/orgs/{org}/sso/domains/{domain}
Remove a domain · branchscope.sso.removeDomain()
The organization's owners (and site administrators), from the web app (a session). While it requires SSO, changing these needs an organization session.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
orgpath, required | string |
domainpath, required | string |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/orgs/acme/sso/domains/domain" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.sso.removeDomain({ org: "acme", domain: "domain" });POST /v1/orgs/{org}/sso/domains/{domain}/verify
Verify a domain · branchscope.sso.verifyDomain()
Looks up its TXT record; not_ready until it's there. The organization's owners (and site administrators), from the web app (a session). While it requires SSO, changing these needs an organization session.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
orgpath, required | string |
domainpath, required | string |
Response 200
{
domain: string
verified: boolean
verifiedAt: string | null
record: {
type: "TXT"
name: string
value: string
} // The DNS record that proves the organization owns the domain.
createdAt: string
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict, not_ready · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/orgs/acme/sso/domains/domain/verify" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.sso.verifyDomain({ org: "acme", domain: "domain" });branchscope.webhooks
GET /v1/orgs/{org}/webhooks
An organization's webhooks · branchscope.webhooks.listForOrg()
They get events from every repository in the organization. Organization owners.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `webhooks:admin` scope.
Parameters
orgpath, required | string |
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
id: integer
scope: "repo" | "org" | "app"
target: string // owner/name for a repository, the name for an organization, the slug for an app's webhook.
url: string
events: string[] // The events it receives.
active: boolean // Paused webhooks get nothing; Branchscope pauses one whose deliveries failed for a day.
disabledReason: string | null // Why Branchscope paused it, if it did.
createdBy: string
createdAt: string (time)
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/orgs/acme/webhooks" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.webhooks.listForOrg({ org: "acme" });POST /v1/orgs/{org}/webhooks
Add a webhook to an organization · branchscope.webhooks.createForOrg()
It gets events from every repository in the organization. Its secret is in the response, once. Repository administrators (repository webhooks) and organization owners (organization webhooks), with a session or a personal write token; OAuth apps need the webhooks:admin scope.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `webhooks:admin` scope.
Parameters
orgpath, required | string |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
url: string // Where to deliver: a public HTTPS address.
events?: ("push.checked" | "change.opened" | "change.closed" | "change.reopened" | "change.merged" | "review.completed" | "review.failed" | "graph.built" | "comment.created" | "agent.task" | "check.completed" | "run.completed" | "release.published" | "deployment.created" | "deployment.status" | "deployment.review_requested" | "agent.action")[] // Which events; all if left out.
active?: boolean
}Response 201
{
id: integer
scope: "repo" | "org" | "app"
target: string // owner/name for a repository, the name for an organization, the slug for an app's webhook.
url: string
events: string[] // The events it receives.
active: boolean // Paused webhooks get nothing; Branchscope pauses one whose deliveries failed for a day.
disabledReason: string | null // Why Branchscope paused it, if it did.
createdBy: string
createdAt: string (time)
secret: string // whsec_…: shown only now. Verify deliveries with it.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/orgs/acme/webhooks" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url":"url"}'await branchscope.webhooks.createForOrg({ org: "acme", url: "url" });GET /v1/repos/{owner}/{name}/webhooks
A repository's webhooks · branchscope.webhooks.listForRepo()
Repository administrators.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `webhooks:admin` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
id: integer
scope: "repo" | "org" | "app"
target: string // owner/name for a repository, the name for an organization, the slug for an app's webhook.
url: string
events: string[] // The events it receives.
active: boolean // Paused webhooks get nothing; Branchscope pauses one whose deliveries failed for a day.
disabledReason: string | null // Why Branchscope paused it, if it did.
createdBy: string
createdAt: string (time)
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/webhooks" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.webhooks.listForRepo({ owner: "acme", name: "web" });POST /v1/repos/{owner}/{name}/webhooks
Add a webhook to a repository · branchscope.webhooks.createForRepo()
Its secret is in the response, once. Repository administrators (repository webhooks) and organization owners (organization webhooks), with a session or a personal write token; OAuth apps need the webhooks:admin scope.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `webhooks:admin` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
url: string // Where to deliver: a public HTTPS address.
events?: ("push.checked" | "change.opened" | "change.closed" | "change.reopened" | "change.merged" | "review.completed" | "review.failed" | "graph.built" | "comment.created" | "agent.task" | "check.completed" | "run.completed" | "release.published" | "deployment.created" | "deployment.status" | "deployment.review_requested" | "agent.action")[] // Which events; all if left out.
active?: boolean
}Response 201
{
id: integer
scope: "repo" | "org" | "app"
target: string // owner/name for a repository, the name for an organization, the slug for an app's webhook.
url: string
events: string[] // The events it receives.
active: boolean // Paused webhooks get nothing; Branchscope pauses one whose deliveries failed for a day.
disabledReason: string | null // Why Branchscope paused it, if it did.
createdBy: string
createdAt: string (time)
secret: string // whsec_…: shown only now. Verify deliveries with it.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/webhooks" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url":"url"}'await branchscope.webhooks.createForRepo({ owner: "acme", name: "web", url: "url" });GET /v1/webhooks/{id}
A webhook · branchscope.webhooks.get()
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `webhooks:admin` scope.
Parameters
idpath, required | integer |
Response 200
{
id: integer
scope: "repo" | "org" | "app"
target: string // owner/name for a repository, the name for an organization, the slug for an app's webhook.
url: string
events: string[] // The events it receives.
active: boolean // Paused webhooks get nothing; Branchscope pauses one whose deliveries failed for a day.
disabledReason: string | null // Why Branchscope paused it, if it did.
createdBy: string
createdAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/webhooks/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.webhooks.get({ id: 7 });PUT /v1/webhooks/{id}
Change a webhook · branchscope.webhooks.update()
Only the fields sent change. Turning it back on (active) clears why Branchscope paused it. Repository administrators (repository webhooks) and organization owners (organization webhooks), with a session or a personal write token; OAuth apps need the webhooks:admin scope.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `webhooks:admin` scope.
Parameters
idpath, required | integer |
Request body
{
url?: string // Where to deliver: a public HTTPS address.
events?: ("push.checked" | "change.opened" | "change.closed" | "change.reopened" | "change.merged" | "review.completed" | "review.failed" | "graph.built" | "comment.created" | "agent.task" | "check.completed" | "run.completed" | "release.published" | "deployment.created" | "deployment.status" | "deployment.review_requested" | "agent.action")[] // Which events; all if left out.
active?: boolean
}Response 200
{
id: integer
scope: "repo" | "org" | "app"
target: string // owner/name for a repository, the name for an organization, the slug for an app's webhook.
url: string
events: string[] // The events it receives.
active: boolean // Paused webhooks get nothing; Branchscope pauses one whose deliveries failed for a day.
disabledReason: string | null // Why Branchscope paused it, if it did.
createdBy: string
createdAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/webhooks/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'await branchscope.webhooks.update({ id: 7 });DELETE /v1/webhooks/{id}
Delete a webhook · branchscope.webhooks.delete()
Repository administrators (repository webhooks) and organization owners (organization webhooks), with a session or a personal write token; OAuth apps need the webhooks:admin scope.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `webhooks:admin` scope.
Parameters
idpath, required | integer |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/webhooks/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.webhooks.delete({ id: 7 });GET /v1/webhooks/{id}/deliveries
A webhook's deliveries · branchscope.webhooks.listDeliveries()
Newest first: each event, its attempts and how the last one went.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `webhooks:admin` scope.
Parameters
idpath, required | integer |
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
id: integer
eventId: string // The webhook-id header: the same on every attempt and redelivery.
event: string
status: "pending" | "succeeded" | "failed"
attempts: integer
nextAttemptAt: string (time) | null
createdAt: string (time)
lastAttemptAt: string (time) | null
responseCode: integer | null
durationMs: integer | null
error: string | null // Why the last attempt failed: a timeout, a refused address, a status code.
response: string | null // The start of the receiver's last response.
payload: string | null // The body sent; kept for 7 days.
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/webhooks/7/deliveries" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.webhooks.listDeliveries({ id: 7 });POST /v1/webhooks/{id}/deliveries/{delivery}/redeliver
Send a delivery again · branchscope.webhooks.redeliver()
The same event, with the same ID and a new signature, as a new delivery. Only while its body is kept (7 days). Repository administrators (repository webhooks) and organization owners (organization webhooks), with a session or a personal write token; OAuth apps need the webhooks:admin scope.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `webhooks:admin` scope.
Parameters
idpath, required | integer |
deliverypath, required | integer |
Response 201
{
id: integer
eventId: string // The webhook-id header: the same on every attempt and redelivery.
event: string
status: "pending" | "succeeded" | "failed"
attempts: integer
nextAttemptAt: string (time) | null
createdAt: string (time)
lastAttemptAt: string (time) | null
responseCode: integer | null
durationMs: integer | null
error: string | null // Why the last attempt failed: a timeout, a refused address, a status code.
response: string | null // The start of the receiver's last response.
payload: string | null // The body sent; kept for 7 days.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 410 gone · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/webhooks/7/deliveries/delivery/redeliver" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.webhooks.redeliver({ id: 7, delivery: 1 });POST /v1/webhooks/{id}/ping
Send a webhook a test event · branchscope.webhooks.ping()
A ping event, delivered like any other: check the delivery log for how it went. Repository administrators (repository webhooks) and organization owners (organization webhooks), with a session or a personal write token; OAuth apps need the webhooks:admin scope.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `webhooks:admin` scope.
Parameters
idpath, required | integer |
Response 201
{
id: integer
eventId: string // The webhook-id header: the same on every attempt and redelivery.
event: string
status: "pending" | "succeeded" | "failed"
attempts: integer
nextAttemptAt: string (time) | null
createdAt: string (time)
lastAttemptAt: string (time) | null
responseCode: integer | null
durationMs: integer | null
error: string | null // Why the last attempt failed: a timeout, a refused address, a status code.
response: string | null // The start of the receiver's last response.
payload: string | null // The body sent; kept for 7 days.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/webhooks/7/ping" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.webhooks.ping({ id: 7 });POST /v1/webhooks/{id}/rotate-secret
Give a webhook a new secret · branchscope.webhooks.rotateSecret()
The old one stops working at once; the new one is in the response, once. Repository administrators (repository webhooks) and organization owners (organization webhooks), with a session or a personal write token; OAuth apps need the webhooks:admin scope.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `webhooks:admin` scope.
Parameters
idpath, required | integer |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Response 201
{
secret: string
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/webhooks/7/rotate-secret" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.webhooks.rotateSecret({ id: 7 });branchscope.repos
GET /v1/repo-templates
Starting files for a new repository · branchscope.repos.templates()
The .gitignore templates and licenses repos.create can start main with.
No credential needed; what you see depends on the one you send.
Response 200
{
gitignore: ("Dotnet" | "Go" | "Java" | "Kotlin" | "Node" | "Python" | "Ruby" | "Rust" | "Swift" | "Terraform" | "Unity" | "VisualStudio")[]
licenses: ({
id: "MIT" | "Apache-2.0" | "GPL-3.0-only" | "AGPL-3.0-only" | "LGPL-3.0-only" | "MPL-2.0" | "BSD-2-Clause" | "BSD-3-Clause" | "EUPL-1.2" | "Unlicense"
name: string
})[]
}Errors
429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repo-templates" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.repos.templates();GET /v1/repos
Repositories you can read · branchscope.repos.list()
By name, with your access, open changes and last push. With affiliation=mine, only yours: in your namespace, your organizations', or ones you collaborate on (not public ones, nor what site administration reaches). With affiliation=following, only those you follow.
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
affiliationquery | "all" | "mine" | "following" |
topicquery | string · Only repositories with this topic. |
ownerquery | string · Only this person's or organization's repositories. |
qquery | string · Only those with this in their name or description. |
sortquery | "name" | "pushed" · By name, or the most recently pushed first. |
Response 200
{
data: ({
repo: string // owner/name
cloneUrl: string
sshUrl: string | null // null when SSH isn't offered.
visibility: "private" | "public"
archived: boolean // Read-only: no pushes, merges, changes or runs until an administrator unarchives it.
unclaimed: boolean // Made before owners existed; only site administrators see it.
description: string
topics: string[]
access: "read" | "write" | "admin" // The caller's access: the highest of ownership, organization role, collaborator role and visibility.
lastPushAt: string (time) | null
lastPushBy: string | null // Who made its latest push; null before its first.
openChanges: integer
defaultBranch: string | null // Stored per repository: set on create (main), import and fork, by an empty repository's first push, and in its settings; null with no branches.
languages: ({
name: string
bytes: integer
})[] // What its default branch's head is written in: bytes per language by file extension, the most first; prose, data, vendored folders and binary files aren't counted. Empty before its first push.
followers: integer // How many people follow it.
following: boolean // Whether you follow it (false signed out).
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.repos.list();POST /v1/repos
Create a repository · branchscope.repos.create()
In your namespace or an organization you belong to. Its default branch protection reaches the push guard before anyone can push. With a README, .gitignore or license, main starts with one commit of them, by you, which the push guard checks like a push; if it refuses, nothing is created.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `repo:write` scope.
Parameters
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
owner: string
name: string
visibility?: "private" | "public"
description?: string
readme?: boolean // Start main with a README.md: the name and description.
gitignore?: "Dotnet" | "Go" | "Java" | "Kotlin" | "Node" | "Python" | "Ruby" | "Rust" | "Swift" | "Terraform" | "Unity" | "VisualStudio" | null // Start main with this .gitignore template (see repos.templates).
license?: "MIT" | "Apache-2.0" | "GPL-3.0-only" | "AGPL-3.0-only" | "LGPL-3.0-only" | "MPL-2.0" | "BSD-2-Clause" | "BSD-3-Clause" | "EUPL-1.2" | "Unlicense" | null // Start main with this license, by SPDX id (see repos.templates).
}Response 201
{
repo: string
cloneUrl: string
sshUrl: string | null
visibility: "private" | "public"
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 409 conflict · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"owner":"acme","name":"web"}'await branchscope.repos.create({ owner: "acme", name: "web" });GET /v1/repos/{owner}/{name}
A repository · branchscope.repos.get()
Branches, tags, clone URLs and settings.
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
Response 200
{
repo: string
cloneUrl: string
sshUrl: string | null
visibility: "private" | "public"
archived: boolean // Read-only: no pushes, merges, changes or runs until an administrator unarchives it.
unclaimed: boolean
description: string
topics: string[] // Set by its admins, to group repositories.
access: "read" | "write" | "admin" // The caller's access: the highest of ownership, organization role, collaborator role and visibility.
defaultBranch: string | null // Stored per repository (what ~DEFAULT_BRANCH targets); null with no branches.
branches: ({
name: string
sha: string // A commit ID (40 or 64 hex characters).
})[]
tags: ({
name: string
sha: string // A commit ID (40 or 64 hex characters).
})[]
settings: {
setupCommands: string[] // Run before indexing (pnpm install, dotnet restore …).
protectedBranches: string[] // Deprecated: the "Protected branches" ruleset's targets (rulesets.list has every ruleset). Branch names or patterns ("*" matches any characters but "/", "**" any characters).
requireChanges: boolean // Deprecated: whether the "Protected branches" ruleset requires changes (no direct pushes, for people too).
allowInstallScripts: boolean
requiredChecks: string[] // Deprecated: the "Protected branches" ruleset's required checks, which must pass on a change's head before it merges (M15). Requiring checks also requires changes.
mergeMethods: ("merge" | "squash" | "rebase")[] // The ways changes may merge (M22); all three unless an administrator chose fewer.
defaultMergeMethod: "merge" | "squash" | "rebase" // The one offered first, and used when a merge doesn't say.
}
followers: integer // How many people follow it.
following: boolean // Whether you follow it (false signed out).
forkedFrom: string | null // The repository it was forked from (owner/name); null when it isn't a fork, that one is gone, or you can't read it.
lastPush: {
at: string
by: string
} | null // When it was last pushed to, and by whom; null before its first push.
created: {
at: string
by: string | null
} | null // When it was made, and by whom (null when that account is gone); null for repositories from before this was recorded.
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.repos.get({ owner: "acme", name: "web" });DELETE /v1/repos/{owner}/{name}
Delete a repository · branchscope.repos.delete()
Its git data, changes, reviews, graphs, runs, checks and webhooks go at once, and can't be restored. Repository admins only, signed in to the web UI.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/repos/acme/web" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.repos.delete({ owner: "acme", name: "web" });GET /v1/repos/{owner}/{name}/analytics/languages
A repository's languages · branchscope.repos.analyticsLanguages()
What its default branch's head is written in, for the Analytics tab: bytes per language by file extension, the most first, and the commit they were counted at. Prose, data, vendored folders and binary files aren't counted. Counted now if it hasn't been yet.
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
Response 200
{
branch: string | null // The default branch; null before the first push.
commit: string | null // The commit counted; null before the first push.
countedAt: string | null
languages: ({
name: string
bytes: integer
})[]
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/analytics/languages" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.repos.analyticsLanguages({ owner: "acme", name: "web" });PUT /v1/repos/{owner}/{name}/archive
Archive a repository · branchscope.repos.archive()
Makes it read-only: the push guard rejects every push and merge, and it takes no new changes, comments, checks, tasks, runs or settings (its visibility can still change). It can still be read, forked, transferred and deleted. Repository admins only, signed in to the web UI.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
Response 200
{
repo: string
archived: boolean
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/repos/acme/web/archive" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.repos.archive({ owner: "acme", name: "web" });DELETE /v1/repos/{owner}/{name}/archive
Unarchive a repository · branchscope.repos.unarchive()
It takes pushes, merges, changes and runs again. Repository admins only, signed in to the web UI.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
Response 200
{
repo: string
archived: boolean
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/repos/acme/web/archive" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.repos.unarchive({ owner: "acme", name: "web" });GET /v1/repos/{owner}/{name}/blame
Which commit last changed each line of a file · branchscope.repos.blame()
git blame, following moves and copies (M22). Files up to 1 MB or 20,000 lines, and 10 seconds per request; past them, an invalid_request or upstream_error saying which.
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
refquery | string · A branch, tag or commit; the default branch if left out. |
pathquery, required | string · A file |
Response 200
{
sha: string // The commit blamed at.
path: string
ranges: ({
start: integer // The first line, numbered from 1.
lines: integer
commit: string // The commit that last changed these lines.
origPath: string // The file's path in that commit (blame follows moves and copies).
origStart: integer // Where the lines started there.
})[] // Runs of lines, in order, each last changed by one commit.
commits: Record<string, {
authorName: string
authorEmail: string
authorTime: string
summary: string
previous: string // Its parent, to blame what came before; empty for the commit that added the file.
previousPath: string
}> // Each commit the ranges name.
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/blame?path=path" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.repos.blame({ owner: "acme", name: "web", path: "path" });GET /v1/repos/{owner}/{name}/blob
A file's contents · branchscope.repos.getBlob()
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
revquery | string |
pathquery, required | string |
Response 200
application/octet-stream · The file's bytes.Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/blob?path=path" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.repos.getBlob({ owner: "acme", name: "web", path: "path" });GET /v1/repos/{owner}/{name}/collaborators
A repository's collaborators · branchscope.repos.listCollaborators()
By username.
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
username: string
name: string
role: "read" | "write" | "admin" // The caller's access: the highest of ownership, organization role, collaborator role and visibility.
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/collaborators" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.repos.listCollaborators({ owner: "acme", name: "web" });PUT /v1/repos/{owner}/{name}/collaborators/{username}
Add a collaborator or change their role · branchscope.repos.setCollaborator()
Repository administrators. The person must have signed in once.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
usernamepath, required | string |
Request body
{
role: "read" | "write" | "admin" // The caller's access: the highest of ownership, organization role, collaborator role and visibility.
}Response 200
{
username: string
role: "read" | "write" | "admin" // The caller's access: the highest of ownership, organization role, collaborator role and visibility.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/repos/acme/web/collaborators/bob" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"role":"write"}'await branchscope.repos.setCollaborator({ owner: "acme", name: "web", username: "bob", role: "write" });DELETE /v1/repos/{owner}/{name}/collaborators/{username}
Remove a collaborator · branchscope.repos.removeCollaborator()
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
usernamepath, required | string |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/repos/acme/web/collaborators/bob" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.repos.removeCollaborator({ owner: "acme", name: "web", username: "bob" });GET /v1/repos/{owner}/{name}/commits
History from a branch, tag or commit · branchscope.repos.listCommits()
Newest first. The pages follow the history as it was when the first page was read. With path, a file's history, following renames, or a folder's (M22).
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
revquery | string · A branch, tag or commit; the default branch if left out. |
pathquery | string · Only commits that changed this file (following renames) or anything in this folder. |
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
sha: string // A commit ID (40 or 64 hex characters).
parents: string[]
authorName: string
authorEmail: string
authorTime: string
subject: string
path?: string // In a file's history: the file's path at this commit, which follows renames (M22).
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/commits" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.repos.listCommits({ owner: "acme", name: "web" });GET /v1/repos/{owner}/{name}/commits/{sha}
One commit · branchscope.repos.getCommit()
Its whole message, its author and committer, and its parents (M22). The sha can be a branch or tag.
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
shapath, required | string |
Response 200
{
sha: string // A commit ID (40 or 64 hex characters).
parents: string[]
authorName: string
authorEmail: string
authorTime: string
subject: string
path?: string // In a file's history: the file's path at this commit, which follows renames (M22).
message: string // The whole message: the subject, and the body after a blank line.
committerName: string
committerEmail: string
commitTime: string
change: {
number: integer
title: string
mergeMethod: "merge" | "squash" | "rebase" | null // Null for merges from before this was recorded.
} | null // The change whose merge made this commit, whose diagrams it shows; null for any other commit.
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/commits/9c1d22a0f3e1b6c4d5a7e8f90a1b2c3d4e5f6a7b" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.repos.getCommit({ owner: "acme", name: "web", sha: "9c1d22a0f3e1b6c4d5a7e8f90a1b2c3d4e5f6a7b" });GET /v1/repos/{owner}/{name}/commits/{sha}/diff
What one commit changed · branchscope.repos.getCommitDiff()
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
shapath, required | string |
Response 200
text/plain · A unified diff against its first parent (against nothing, for a first commit).Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/commits/9c1d22a0f3e1b6c4d5a7e8f90a1b2c3d4e5f6a7b/diff" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.repos.getCommitDiff({ owner: "acme", name: "web", sha: "9c1d22a0f3e1b6c4d5a7e8f90a1b2c3d4e5f6a7b" });PUT /v1/repos/{owner}/{name}/follow
Follow a repository · branchscope.repos.follow()
One you can read. Settings lists what you follow.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Response 200
{
following: boolean
followers: integer
}Errors
400 invalid_request · 401 unauthorized · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/repos/acme/web/follow" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.repos.follow({ owner: "acme", name: "web" });DELETE /v1/repos/{owner}/{name}/follow
Stop following a repository · branchscope.repos.unfollow()
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
Response 200
{
following: boolean
followers: integer
}Errors
400 invalid_request · 401 unauthorized · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/repos/acme/web/follow" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.repos.unfollow({ owner: "acme", name: "web" });POST /v1/repos/{owner}/{name}/fork
Fork a repository · branchscope.repos.fork()
Copies a repository you can read into your namespace or an organization you can create repositories in, public or private (a private repository's fork is always private): every branch and tag, or (defaultBranchOnly) its default branch alone. The copy goes through the push guard as a push by you; if it refuses, nothing is created. The fork remembers where it came from (forkedFrom).
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `repo:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
Request body
{
owner: string // Where the fork goes: you, or one of your organizations.
name: string // The fork's name; usually the same as the repository's.
description?: string // The fork's description; the repository's when left out.
defaultBranchOnly?: boolean // Copy only the default branch, not every branch and tag.
visibility?: "private" | "public" // The fork's; the original's when left out. A private repository's fork can't be public.
}Response 201
{
repo: string
cloneUrl: string
sshUrl: string | null
visibility: "private" | "public"
forkedFrom: string
branches: integer
tags: integer
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden, guard_denied · 404 not_found · 409 conflict · 429 rate_limited · 500 internal · 502 upstream_error · 503 guard_unavailable
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/fork" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"owner":"acme","name":"web"}'await branchscope.repos.fork({ owner: "acme", name: "web" });PUT /v1/repos/{owner}/{name}/settings
Change a repository's settings · branchscope.repos.updateSettings()
Repository administrators. Only the fields sent change. An archived repository's settings can't change, except its visibility; a fork of a private repository can't be made public.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
Request body
{
visibility?: "private" | "public"
protectedBranches?: string[] // Deprecated (use rulesets): the "Protected branches" ruleset's targets; made again if it was deleted.
requireChanges?: boolean // Deprecated (use rulesets): whether the "Protected branches" ruleset requires changes.
setupCommands?: string[]
allowInstallScripts?: boolean
mergeMethods?: ("merge" | "squash" | "rebase")[] // At least one.
defaultMergeMethod?: "merge" | "squash" | "rebase" // One of the allowed methods.
requiredChecks?: string[] // Deprecated (use rulesets): the "Protected branches" ruleset's required checks; requiring any turns on requireChanges.
description?: string
topics?: string[] // Replaces its topics: up to 10, lowercase letters, digits and dashes.
defaultBranch?: string // An existing branch. What ~DEFAULT_BRANCH in rulesets targets, what the Code tab and a clone open on. People only: an agent can't change it. Recorded in the audit log.
}Response 200
{
setupCommands: string[] // Run before indexing (pnpm install, dotnet restore …).
protectedBranches: string[] // Deprecated: the "Protected branches" ruleset's targets (rulesets.list has every ruleset). Branch names or patterns ("*" matches any characters but "/", "**" any characters).
requireChanges: boolean // Deprecated: whether the "Protected branches" ruleset requires changes (no direct pushes, for people too).
allowInstallScripts: boolean
requiredChecks: string[] // Deprecated: the "Protected branches" ruleset's required checks, which must pass on a change's head before it merges (M15). Requiring checks also requires changes.
mergeMethods: ("merge" | "squash" | "rebase")[] // The ways changes may merge (M22); all three unless an administrator chose fewer.
defaultMergeMethod: "merge" | "squash" | "rebase" // The one offered first, and used when a merge doesn't say.
visibility: "private" | "public"
description: string
topics: string[]
defaultBranch: string | null
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/repos/acme/web/settings" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'await branchscope.repos.updateSettings({ owner: "acme", name: "web" });POST /v1/repos/{owner}/{name}/transfer
Transfer a repository · branchscope.repos.transfer()
Moves a repository, under the same name, to another owner you can create repositories in: you, or an organization you own. Its git data, changes, reviews, graphs, runs, checks, webhooks, collaborators, followers and forks come with it; the old name isn't kept as a redirect. Repository admins only, signed in to the web UI.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
Request body
{
owner: string // The new owner: a username or an organization's name.
}Response 200
{
repo: string // The repository's new owner/name.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/transfer" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"owner":"acme"}'await branchscope.repos.transfer({ owner: "acme", name: "web" });GET /v1/repos/{owner}/{name}/tree
A directory's entries · branchscope.repos.getTree()
Directories first, then files, each alphabetically.
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
revquery | string |
pathquery | string |
Response 200
{
entries: ({
name: string
type: "blob" | "tree" | "commit"
mode: string
sha: string // A commit ID (40 or 64 hex characters).
size: integer
})[]
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/tree" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.repos.getTree({ owner: "acme", name: "web" });branchscope.caches
GET /v1/repos/{owner}/{name}/actions/caches
A repository's caches · branchscope.caches.list()
Every entry its workflows saved (M17), the most recently used first, and how much of the repository's limit they use.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
Response 200
{
usedBytes: integer
limitBytes: integer // Past it, the entries used least recently are deleted.
entryLimitBytes: integer // The most one entry may be.
unusedDays: integer // An entry nobody restored for this long is deleted.
entries: ({
id: integer
key: string
branch: string // The branch (or tag) it was saved for.
workflow: string
sizeBytes: integer
createdAt: string (time)
lastUsedAt: string (time)
})[]
}Errors
400 invalid_request · 401 unauthorized · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/actions/caches" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.caches.list({ owner: "acme", name: "web" });DELETE /v1/repos/{owner}/{name}/actions/caches
Clear a repository's caches · branchscope.caches.clear()
Deletes every entry, or one branch's (M17); the next runs start without one. On the audit log. Repository administrators, with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
branchquery | string · Only this branch's entries. |
Response 200
{
deleted: integer
freedBytes: integer
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/repos/acme/web/actions/caches" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.caches.clear({ owner: "acme", name: "web" });branchscope.branches
GET /v1/repos/{owner}/{name}/branches
A repository's branches, with where each stands · branchscope.branches.list()
Each with its head commit, its last push, its head's checks, its open change, and how far it is ahead of and behind the default branch. The default branch comes on its own (default), never in data. Newest first; up to 2000 branches are looked at.
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
tabquery | "yours" | "active" | "stale" | "all" · yours: branches you've pushed to; active: pushed in the last 90 days; stale: not since. |
qquery | string · Only branches whose name contains this (any case). |
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
name: string
sha: string // A commit ID (40 or 64 hex characters).
protected: boolean // It matches one of the repository's protected-branch patterns, as the push guard matches them.
commit: {
subject: string
authorName: string
authorEmail: string
authorTime: string
commitTime: string
} // Its head commit.
updated: {
at: string
by: string | null // Who last pushed to it (a username); null when no push is recorded, as for imported branches.
agent: string | null
name: string // Who to show: the pusher, or without one the head commit's author.
avatarUrl: string | null
} // Its last recorded push, or without one its head commit's time.
checks: {
state: "passed" | "failed" | "pending" | "none"
total: integer
passed: integer
failed: integer
pending: integer
} // Its head commit's checks.
change: {
number: integer
title: string
} | null // The newest open change from this branch.
aheadBehind: {
ahead: integer
behind: integer
} | null // Commits it has that the default branch doesn't (ahead), and the other way round (behind); null for the default branch, or when counting took too long.
})[]
nextCursor: string | null // null on the last page.
default: {
name: string
sha: string // A commit ID (40 or 64 hex characters).
protected: boolean // It matches one of the repository's protected-branch patterns, as the push guard matches them.
commit: {
subject: string
authorName: string
authorEmail: string
authorTime: string
commitTime: string
} // Its head commit.
updated: {
at: string
by: string | null // Who last pushed to it (a username); null when no push is recorded, as for imported branches.
agent: string | null
name: string // Who to show: the pusher, or without one the head commit's author.
avatarUrl: string | null
} // Its last recorded push, or without one its head commit's time.
checks: {
state: "passed" | "failed" | "pending" | "none"
total: integer
passed: integer
failed: integer
pending: integer
} // Its head commit's checks.
change: {
number: integer
title: string
} | null // The newest open change from this branch.
aheadBehind: {
ahead: integer
behind: integer
} | null // Commits it has that the default branch doesn't (ahead), and the other way round (behind); null for the default branch, or when counting took too long.
} | null // The default branch, when it matches q.
counts: {
yours: integer
active: integer
stale: integer
all: integer
} // How many branches matching q each tab has: yours, active and stale without the default branch, all with it. yours is 0 signed out.
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/branches" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.branches.list({ owner: "acme", name: "web" });POST /v1/repos/{owner}/{name}/branches
Create a branch · branchscope.branches.create()
From a branch, tag or commit. People and agents with write access, in an unarchived repository. Like a push, the push guard must allow it, as you (agents can't write protected branches; a protected branch can't be deleted, so it can't be renamed); a guard that can't be reached refuses, and nothing is written. Recorded in push activity, and sent as push.checked, as a push is.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `repo:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
name: string
from: string // A branch, tag or commit.
}Response 201
{
name: string
sha: string
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden, guard_denied · 404 not_found · 409 conflict · 429 rate_limited · 500 internal · 502 upstream_error · 503 guard_unavailable
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/branches" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"web","from":"from"}'await branchscope.branches.create({ owner: "acme", name: "web", from: "from" });DELETE /v1/repos/{owner}/{name}/branches/{branch}
Delete a branch · branchscope.branches.delete()
Not the default branch. It can be restored for 14 days. People and agents with write access, in an unarchived repository. Like a push, the push guard must allow it, as you (agents can't write protected branches; a protected branch can't be deleted, so it can't be renamed); a guard that can't be reached refuses, and nothing is written. Recorded in push activity, and sent as push.checked, as a push is.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `repo:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
branchpath, required | string · URL-encoded: feature%2Fx for feature/x. |
Response 200
{
name: string
sha: string
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden, guard_denied · 404 not_found · 409 conflict · 429 rate_limited · 500 internal · 502 upstream_error · 503 guard_unavailable
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/repos/acme/web/branches/branch" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.branches.delete({ owner: "acme", name: "web", branch: "branch" });POST /v1/repos/{owner}/{name}/branches/{branch}/rename
Rename a branch · branchscope.branches.rename()
Not the default branch. The old name goes and the new one comes in one step, and open changes from or into it follow it. People and agents with write access, in an unarchived repository. Like a push, the push guard must allow it, as you (agents can't write protected branches; a protected branch can't be deleted, so it can't be renamed); a guard that can't be reached refuses, and nothing is written. Recorded in push activity, and sent as push.checked, as a push is.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `repo:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
branchpath, required | string · URL-encoded: feature%2Fx for feature/x. |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
name: string
}Response 200
{
name: string
sha: string
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden, guard_denied · 404 not_found · 409 conflict · 429 rate_limited · 500 internal · 502 upstream_error · 503 guard_unavailable
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/branches/branch/rename" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"web"}'await branchscope.branches.rename({ owner: "acme", name: "web", branch: "branch" });POST /v1/repos/{owner}/{name}/branches/{branch}/restore
Restore a deleted branch · branchscope.branches.restore()
At the commit its latest deletion left (from the web app or a git push), within 14 days, if no branch has its name since. People and agents with write access, in an unarchived repository. Like a push, the push guard must allow it, as you (agents can't write protected branches; a protected branch can't be deleted, so it can't be renamed); a guard that can't be reached refuses, and nothing is written. Recorded in push activity, and sent as push.checked, as a push is.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `repo:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
branchpath, required | string · URL-encoded: feature%2Fx for feature/x. |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Response 201
{
name: string
sha: string
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden, guard_denied · 404 not_found · 409 conflict · 429 rate_limited · 500 internal · 502 upstream_error · 503 guard_unavailable
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/branches/branch/restore" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.branches.restore({ owner: "acme", name: "web", branch: "branch" });branchscope.changes
GET /v1/repos/{owner}/{name}/changes
A repository's changes · branchscope.changes.list()
Newest first.
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
statusquery | "open" | "merged" | "closed" |
authorquery | string · Who opened it (a username). |
agentquery | string · The agent that opened it. |
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
number: integer
title: string
description: string
sourceBranch: string
targetBranch: string
status: "open" | "merged" | "closed"
author: string | null // Who opened it (null for older changes, opened before authors were recorded).
agent: string | null // The agent that opened it, when an agent credential was used.
mergedSha: string | null
mergeMethod: "merge" | "squash" | "rebase" | null // How it merged; null while it's open or closed.
override: {
checks: string[]
reason: string
by: string
} | null // An administrator merged it although these required checks hadn't passed (M15).
createdAt: string (time)
updatedAt: string (time)
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/changes" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.changes.list({ owner: "acme", name: "web" });POST /v1/repos/{owner}/{name}/changes
Open a change · branchscope.changes.create()
From one branch into another; its graph review is queued at once.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `changes:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
title: string
description?: string
source: string
target: string
}Response 201
{
number: integer
title: string
description: string
sourceBranch: string
targetBranch: string
status: "open" | "merged" | "closed"
author: string | null // Who opened it (null for older changes, opened before authors were recorded).
agent: string | null // The agent that opened it, when an agent credential was used.
mergedSha: string | null
mergeMethod: "merge" | "squash" | "rebase" | null // How it merged; null while it's open or closed.
override: {
checks: string[]
reason: string
by: string
} | null // An administrator merged it although these required checks hadn't passed (M15).
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/changes" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Add invoice export","source":"agent/export","target":"main"}'await branchscope.changes.create({ owner: "acme", name: "web", title: "Add invoice export", source: "agent/export", target: "main" });GET /v1/repos/{owner}/{name}/changes/{number}
A change · branchscope.changes.get()
With its commits, whether it merges cleanly, and its latest review.
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
numberpath, required | integer |
Response 200
{
number: integer
title: string
description: string
sourceBranch: string
targetBranch: string
status: "open" | "merged" | "closed"
author: string | null // Who opened it (null for older changes, opened before authors were recorded).
agent: string | null // The agent that opened it, when an agent credential was used.
mergedSha: string | null
mergeMethod: "merge" | "squash" | "rebase" | null // How it merged; null while it's open or closed.
override: {
checks: string[]
reason: string
by: string
} | null // An administrator merged it although these required checks hadn't passed (M15).
createdAt: string (time)
updatedAt: string (time)
access: "read" | "write" | "admin" // The caller's access: the highest of ownership, organization role, collaborator role and visibility.
baseSha: string | null // The merge base; null when a branch is gone.
headSha: string | null
commits: ({
sha: string // A commit ID (40 or 64 hex characters).
parents: string[]
authorName: string
authorEmail: string
authorTime: string
subject: string
path?: string // In a file's history: the file's path at this commit, which follows renames (M22).
})[]
mergeable: {
clean: boolean // A merge commit or a squash would merge cleanly.
conflicts: string[] // Their conflicting files.
rebase: {
clean: boolean
commit: string // The first of the change's commits that conflicts when replayed; empty if none does.
conflicts: string[]
mergeCommits: boolean // The change contains merge commits, so it can't be rebased.
} // Whether a rebase would merge cleanly: it can conflict at one commit where a merge doesn't.
} | null // Open changes only: conflicts, caught before merging, per method (M22).
mergeOptions: {
methods: ("merge" | "squash" | "rebase")[] // The ways this repository allows changes to merge.
default: "merge" | "squash" | "rebase" // A merge commit, one squashed commit, or the change's commits rebased onto the target (M22).
messages: {
merge: string
squash: string
} // The default commit messages, as GitHub's.
unavailable: {
merge?: string
squash?: string
rebase?: string
} // Methods the repository allows that the target branch's rulesets don't, each with why (a merge commit where linear history is required); default is never one of them.
} | null // Open changes only.
review: {
id: integer
baseSha: string // A commit ID (40 or 64 hex characters).
headSha: string // A commit ID (40 or 64 hex characters).
status: "queued" | "running" | "done" | "failed"
error: string
log: string
result: unknown // Counts and findings once done: the review's checks, plan and summary.
updatedAt: string (time)
} | null
conflictMap: {
id: integer
status: "queued" | "running" | "done" | "failed"
baseSha: string // The merge base.
currentSha: string // The target branch's head.
headSha: string // The change's head.
error: string
counts: {
elements: integer
textual: integer
semantic: integer
} | null // Once done: elements both sides touched, files git reports as conflicting, semantic conflicts.
updatedAt: string (time)
} | null // Both sides since the merge base (M24): null when the target hasn't moved past it, or a branch is gone. The document is at …/conflicts.
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/changes/12" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.changes.get({ owner: "acme", name: "web", number: 12 });POST /v1/repos/{owner}/{name}/changes/{number}/close
Close a change · branchscope.changes.close()
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `changes:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
numberpath, required | integer |
Response 201
{
number: integer
title: string
description: string
sourceBranch: string
targetBranch: string
status: "open" | "merged" | "closed"
author: string | null // Who opened it (null for older changes, opened before authors were recorded).
agent: string | null // The agent that opened it, when an agent credential was used.
mergedSha: string | null
mergeMethod: "merge" | "squash" | "rebase" | null // How it merged; null while it's open or closed.
override: {
checks: string[]
reason: string
by: string
} | null // An administrator merged it although these required checks hadn't passed (M15).
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/changes/12/close" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.changes.close({ owner: "acme", name: "web", number: 12 });GET /v1/repos/{owner}/{name}/changes/{number}/conflicts
A change's conflict map · branchscope.changes.getConflicts()
Both sides of the change since its merge base, once the target branch has moved past it: what the target (current) and the change each did to every element both touched, the files git reports as conflicting with their regions placed on the base elements that own them, and semantic conflicts that may merge cleanly as text (one side removes, renames or moves something the other starts using; changes its declaration, such as an endpoint's route, under a new use; or both add the same element). Derived from the three graphs and git only (SPEC §19), as a job of its own: queued on first ask, status says when it's done. With both sides as change maps, limited to the collisions. Shown, never enforced.
No credential needed; what you see depends on the one you send.
OAuth apps need the `reviews:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
numberpath, required | integer |
Response 200
{
status: "none" | "queued" | "running" | "done" | "failed" // none: the target hasn't moved past the merge base (or a branch is gone), so there's nothing to compare.
baseSha: string | null
currentSha: string | null
headSha: string | null
error: string
log: string
document: {
schemaVersion: string // Semver (SPEC §8). On the 0.5 line of documents derived from graphs, with connections and owner graphs.
kind: "conflicts"
repo?: string // The graphs' source.repo, when they name one.
base: GraphRef // The merge base's graph.
current: GraphRef // The target branch's head's graph.
change: GraphRef // The change's head's graph.
elements: CollidingElement[] // Each element both sides touched, by its base ID (or, added on both sides, the ID both gave it), sorted by id.
textual: TextualFile[] // Each file git's merge of current and change reports as conflicting, sorted by path.
semantic: SemanticConflict[] // Changes that may merge cleanly as text but don't fit together, sorted by kind, id, then by (absent first).
} | null // The conflicts document (SPEC §19) once done.
maps: {
current: ({
})[] // The target's side as change-map diagrams (view.schema.json's Diagram), limited to the collisions and what they connect to.
change: ({
})[] // The change's side, the same way.
marks: {
current: Record<string, string[]>
change: Record<string, string[]>
} // Per side, each drawn node's collisions: e<i> for document.elements[i], s<i> for document.semantic[i].
omitted: integer // Collisions past the limit the maps draw; the document lists them all.
} | null
names: {
nodes: Record<string, string> // Element IDs to readable labels.
edges: Record<string, string> // Edge kinds to verbs.
}
}Errors
400 invalid_request · 404 not_found · 409 not_ready · 410 gone · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/changes/12/conflicts" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.changes.getConflicts({ owner: "acme", name: "web", number: 12 });GET /v1/repos/{owner}/{name}/changes/{number}/connections
A change's other repositories · branchscope.changes.getConnections()
The endpoints, tables and channels its latest review touches, the functions on the other side of them in the owner's other repositories (each one's default branch, at the commit its graph was built from, with how fresh that is), and the findings: an endpoint or channel it stops serving that others still call (breaking), one it starts calling that nothing serves (missing), and a handler it modifies that others call (affected). ?against=<environment> compares with what's deployed there instead: each repository at the commit of its current successful deployment to it, or its default branch, with the reason, when it has none or that commit has no graph yet; `environments` lists the names it can take. Shown, never enforced. Only repositories you can read count; the others, and their deployments, are left out as if they didn't exist.
No credential needed; what you see depends on the one you send.
OAuth apps need the `reviews:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
numberpath, required | integer |
againstquery | string · An environment, e.g. production: compare with each repository's commit deployed there instead of its default branch. |
Response 200
{
schemaVersion: string // Semver (SPEC §8). The connections and owner-graph documents version on their own line from 0.5.
kind: "connections"
repo: string // The change's repository, owner/name.
base: GraphRef
head: GraphRef
against: string | null // The environment whose deployed commits were asked for as the other side (?against=); null for each repository's default branch.
environments: string[] // The environments at least one of the repositories joined has a current successful deployment to, sorted: what against can name.
repositories: PinnedRepository[] // The owner's other repositories it was built from, sorted by repo: each one's indexed commit and how fresh it is.
touched: TouchedId[] // Each neutral ID the change adds, removes or changes on its side, or whose handler or caller it modifies, sorted by id.
findings: {
breaking: BreakingFinding[]
missing: MissingFinding[]
affected: AffectedFinding[]
} // Shown, never enforced. Each list sorted by id.
}Errors
400 invalid_request · 404 not_found · 409 not_ready · 410 gone · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/changes/12/connections" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.changes.getConnections({ owner: "acme", name: "web", number: 12 });GET /v1/repos/{owner}/{name}/changes/{number}/diff
A change's unified diff · branchscope.changes.getDiff()
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
numberpath, required | integer |
Response 200
text/plain · The diff from the merge base to the head; empty when a branch is gone.Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/changes/12/diff" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.changes.getDiff({ owner: "acme", name: "web", number: 12 });GET /v1/repos/{owner}/{name}/changes/{number}/issues
A change's linked Linear issues · branchscope.changes.listIssues()
Those whose keys are in its branch, title, description or commits, and those linked by hand. Issues' titles, states and assignees (as Linear's events last said, or from Linear, cached for a minute) are for members of the organization; others see only the keys. No credential needed for a public repository.
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
numberpath, required | integer |
Response 200
{
connected: boolean // Whether the repository's organization has a Linear workspace connected.
workspace: string | null
issues: ({
identifier: string // ENG-123.
source: "detected" | "manual" // detected: its key is in the change's branch, title, description or commits; manual: someone linked it.
url: string | null // The issue in Linear.
title: string | null // From Linear, for members of the organization; null for anyone else, or while Linear can't be reached.
state: {
name: string
type: string
} | null
assignee: string | null
syncedAt: string (time) | null // When Branchscope last updated the issue.
error: string | null // Why the last update to the issue failed, if it did.
currentIdentifier: string | null // The issue's key now, for members: another than identifier once it moved to another team in Linear (OPS-45, was ENG-123).
deleted: boolean // Deleted in Linear: nothing more is sent to it; unlink it to remove it here.
})[]
}Errors
400 invalid_request · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/changes/12/issues" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.changes.listIssues({ owner: "acme", name: "web", number: 12 });POST /v1/repos/{owner}/{name}/changes/{number}/issues
Link a Linear issue to a change · branchscope.changes.linkIssue()
By its key, which must be one of the workspace's teams'. The issue gets the change as an attachment at once (and its review's comment, if comments are on). Write access.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `changes:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
numberpath, required | integer |
Request body
{
identifier: string
}Response 201
{
connected: boolean // Whether the repository's organization has a Linear workspace connected.
workspace: string | null
issues: ({
identifier: string // ENG-123.
source: "detected" | "manual" // detected: its key is in the change's branch, title, description or commits; manual: someone linked it.
url: string | null // The issue in Linear.
title: string | null // From Linear, for members of the organization; null for anyone else, or while Linear can't be reached.
state: {
name: string
type: string
} | null
assignee: string | null
syncedAt: string (time) | null // When Branchscope last updated the issue.
error: string | null // Why the last update to the issue failed, if it did.
currentIdentifier: string | null // The issue's key now, for members: another than identifier once it moved to another team in Linear (OPS-45, was ENG-123).
deleted: boolean // Deleted in Linear: nothing more is sent to it; unlink it to remove it here.
})[]
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 not_ready · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/changes/12/issues" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"identifier":"identifier"}'await branchscope.changes.linkIssue({ owner: "acme", name: "web", number: 12, identifier: "identifier" });DELETE /v1/repos/{owner}/{name}/changes/{number}/issues/{identifier}
Unlink a Linear issue from a change · branchscope.changes.unlinkIssue()
It stays unlinked even though its key is still in the branch or title. What was already on the issue in Linear stays. Write access.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `changes:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
numberpath, required | integer |
identifierpath, required | string |
Response 200
{
connected: boolean // Whether the repository's organization has a Linear workspace connected.
workspace: string | null
issues: ({
identifier: string // ENG-123.
source: "detected" | "manual" // detected: its key is in the change's branch, title, description or commits; manual: someone linked it.
url: string | null // The issue in Linear.
title: string | null // From Linear, for members of the organization; null for anyone else, or while Linear can't be reached.
state: {
name: string
type: string
} | null
assignee: string | null
syncedAt: string (time) | null // When Branchscope last updated the issue.
error: string | null // Why the last update to the issue failed, if it did.
currentIdentifier: string | null // The issue's key now, for members: another than identifier once it moved to another team in Linear (OPS-45, was ENG-123).
deleted: boolean // Deleted in Linear: nothing more is sent to it; unlink it to remove it here.
})[]
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/repos/acme/web/changes/12/issues/identifier" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.changes.unlinkIssue({ owner: "acme", name: "web", number: 12, identifier: "identifier" });POST /v1/repos/{owner}/{name}/changes/{number}/merge
Merge a change · branchscope.changes.merge()
As a merge commit, a squash or a rebase (M22). The merge passes the push guard like any push: agents can't merge into protected branches, and a guard that can't be reached refuses. A merge that would conflict is refused (conflict, with the files, and the commit for a rebase) and nothing is written. Into a protected branch, the repository's required checks must have passed on the change's head; a repository administrator may merge anyway by giving overrideReason, which is recorded on the change (agents never can).
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `changes:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
numberpath, required | integer |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
overrideReason?: string // Administrators only: merge although required checks haven't passed, saying why.
method?: "merge" | "squash" | "rebase" // The repository's default if left out; it must be one the repository allows.
message?: string // The new commit's message (merge commit or squash); the default (mergeOptions.messages) if left out. Rebased commits keep theirs.
}Response 201
{
number: integer
title: string
description: string
sourceBranch: string
targetBranch: string
status: "open" | "merged" | "closed"
author: string | null // Who opened it (null for older changes, opened before authors were recorded).
agent: string | null // The agent that opened it, when an agent credential was used.
mergedSha: string | null
mergeMethod: "merge" | "squash" | "rebase" | null // How it merged; null while it's open or closed.
override: {
checks: string[]
reason: string
by: string
} | null // An administrator merged it although these required checks hadn't passed (M15).
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden, guard_denied · 404 not_found · 409 conflict · 429 rate_limited · 500 internal · 502 upstream_error · 503 guard_unavailable
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/changes/12/merge" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'await branchscope.changes.merge({ owner: "acme", name: "web", number: 12 });POST /v1/repos/{owner}/{name}/changes/{number}/reopen
Reopen a closed change · branchscope.changes.reopen()
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `changes:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
numberpath, required | integer |
Response 201
{
number: integer
title: string
description: string
sourceBranch: string
targetBranch: string
status: "open" | "merged" | "closed"
author: string | null // Who opened it (null for older changes, opened before authors were recorded).
agent: string | null // The agent that opened it, when an agent credential was used.
mergedSha: string | null
mergeMethod: "merge" | "squash" | "rebase" | null // How it merged; null while it's open or closed.
override: {
checks: string[]
reason: string
by: string
} | null // An administrator merged it although these required checks hadn't passed (M15).
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/changes/12/reopen" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.changes.reopen({ owner: "acme", name: "web", number: 12 });POST /v1/repos/{owner}/{name}/changes/{number}/review
Run a change's review again · branchscope.changes.review()
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `changes:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
numberpath, required | integer |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Response 201
{
id: integer
baseSha: string // A commit ID (40 or 64 hex characters).
headSha: string // A commit ID (40 or 64 hex characters).
status: "queued" | "running" | "done" | "failed"
error: string
log: string
result: unknown // Counts and findings once done: the review's checks, plan and summary.
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/changes/12/review" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.changes.review({ owner: "acme", name: "web", number: 12 });POST /v1/repos/{owner}/{name}/changes/{number}/status
Say what an agent is doing on a change · branchscope.changes.postStatus()
Agents only: working, pushed (a fix), blocked (needs a person) or done, with a line of text. The latest shows at the top of the change.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `changes:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
numberpath, required | integer |
Request body
{
status: "working" | "pushed" | "blocked" | "done"
text: string
}Response 201
{
id: integer
parentId: integer | null // The comment this replies to; replies are one level deep.
kind: "comment" | "status"
status: "working" | "pushed" | "blocked" | "done" | null // For a status update: what the agent says it's doing.
body: string // Markdown; for a status update, its one line of text.
author: string
agent: string | null // The agent that wrote it for its author, if one did.
createdAt: string (time)
editedAt: string (time) | null
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/changes/12/status" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status":"working","text":"text"}'await branchscope.changes.postStatus({ owner: "acme", name: "web", number: 12, status: "working", text: "text" });GET /v1/repos/{owner}/{name}/changes/{number}/views
A change's diagrams and checks · branchscope.changes.getViews()
The change map and API view of its latest review, its checks, renames, and readable names for the elements they mention.
No credential needed; what you see depends on the one you send.
OAuth apps need the `reviews:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
numberpath, required | integer |
Response 200
{
changeMap: ViewDocument
api: ViewDocument
checks: ChecksDocument
renamed: ({
from: string
to: string
})[] // Renamed and moved elements: base ID to head ID.
names: {
nodes: Record<string, string> // Element IDs to readable labels.
edges: Record<string, string> // Edge kinds to verbs.
}
}Errors
400 invalid_request · 404 not_found · 409 not_ready · 410 gone · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/changes/12/views" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.changes.getViews({ owner: "acme", name: "web", number: 12 });branchscope.checks
GET /v1/repos/{owner}/{name}/changes/{number}/checks
A change's checks, against the required ones · branchscope.checks.listForChange()
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
numberpath, required | integer |
Response 200
{
sha: string | null // The change's head commit; null when its branch is gone.
checks: ({
id: integer
sha: string // A commit ID (40 or 64 hex characters).
name: string // Unique per commit: reporting the same name again updates the check.
source: "app" | "person" | "workflow" | "review" // review: branchscope/review, written by Branchscope from the change's review results (M28).
app: string | null // The app that reports it (its slug), for app checks.
status: "queued" | "running" | "passed" | "failed" | "cancelled" | "timed_out"
summary: string // A line saying what happened.
detailsUrl: string | null
reporter: string | null // Who reported it last.
startedAt: string (time) | null
completedAt: string (time) | null
createdAt: string (time)
updatedAt: string (time)
})[]
required: string[]
state: "passed" | "pending" | "failed" | "none" // passed: every required check passed; failed: one failed, was cancelled or timed out; pending: one isn't done or hasn't been reported; none: nothing is required.
missing: string[] // Required checks not reported yet.
failing: string[] // Required checks that failed, were cancelled or timed out.
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/changes/12/checks" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.checks.listForChange({ owner: "acme", name: "web", number: 12 });GET /v1/repos/{owner}/{name}/commits/{sha}/checks
A commit's checks · branchscope.checks.listForCommit()
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
shapath, required | string |
Response 200
{
data: ({
id: integer
sha: string // A commit ID (40 or 64 hex characters).
name: string // Unique per commit: reporting the same name again updates the check.
source: "app" | "person" | "workflow" | "review" // review: branchscope/review, written by Branchscope from the change's review results (M28).
app: string | null // The app that reports it (its slug), for app checks.
status: "queued" | "running" | "passed" | "failed" | "cancelled" | "timed_out"
summary: string // A line saying what happened.
detailsUrl: string | null
reporter: string | null // Who reported it last.
startedAt: string (time) | null
completedAt: string (time) | null
createdAt: string (time)
updatedAt: string (time)
})[]
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/commits/9c1d22a0f3e1b6c4d5a7e8f90a1b2c3d4e5f6a7b/checks" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.checks.listForCommit({ owner: "acme", name: "web", sha: "9c1d22a0f3e1b6c4d5a7e8f90a1b2c3d4e5f6a7b" });POST /v1/repos/{owner}/{name}/commits/{sha}/checks
Report a check on a commit · branchscope.checks.report()
For CI and other apps (the checks:write scope) and people with write access. A check is identified by its name: reporting the same name again updates it. When it reaches passed, failed, cancelled or timed_out, check.completed is sent. Names starting with branchscope/ are Branchscope's own (branchscope/review, M28) and refused.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `checks:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
shapath, required | string |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
name: string
status: "queued" | "running" | "passed" | "failed" | "cancelled" | "timed_out"
summary?: string
detailsUrl?: string | null
}Response 201
{
id: integer
sha: string // A commit ID (40 or 64 hex characters).
name: string // Unique per commit: reporting the same name again updates the check.
source: "app" | "person" | "workflow" | "review" // review: branchscope/review, written by Branchscope from the change's review results (M28).
app: string | null // The app that reports it (its slug), for app checks.
status: "queued" | "running" | "passed" | "failed" | "cancelled" | "timed_out"
summary: string // A line saying what happened.
detailsUrl: string | null
reporter: string | null // Who reported it last.
startedAt: string (time) | null
completedAt: string (time) | null
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/commits/9c1d22a0f3e1b6c4d5a7e8f90a1b2c3d4e5f6a7b/checks" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"web","status":"queued"}'await branchscope.checks.report({ owner: "acme", name: "web", sha: "9c1d22a0f3e1b6c4d5a7e8f90a1b2c3d4e5f6a7b", status: "queued" });branchscope.comments
POST /v1/repos/{owner}/{name}/changes/{number}/comments
Comment on a change, or reply to a comment · branchscope.comments.create()
Anyone who can read the change. Replies are one level deep: reply to a comment, not to a reply.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `changes:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
numberpath, required | integer |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
body: string // Markdown.
parentId?: integer | null // The comment this replies to.
}Response 201
{
id: integer
parentId: integer | null // The comment this replies to; replies are one level deep.
kind: "comment" | "status"
status: "working" | "pushed" | "blocked" | "done" | null // For a status update: what the agent says it's doing.
body: string // Markdown; for a status update, its one line of text.
author: string
agent: string | null // The agent that wrote it for its author, if one did.
createdAt: string (time)
editedAt: string (time) | null
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/changes/12/comments" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"body":"body"}'await branchscope.comments.create({ owner: "acme", name: "web", number: 12, body: "body" });PUT /v1/repos/{owner}/{name}/changes/{number}/comments/{id}
Edit your comment · branchscope.comments.update()
Only its author (and, for an agent's, the same agent).
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `changes:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
numberpath, required | integer |
idpath, required | integer |
Request body
{
body: string
}Response 200
{
id: integer
parentId: integer | null // The comment this replies to; replies are one level deep.
kind: "comment" | "status"
status: "working" | "pushed" | "blocked" | "done" | null // For a status update: what the agent says it's doing.
body: string // Markdown; for a status update, its one line of text.
author: string
agent: string | null // The agent that wrote it for its author, if one did.
createdAt: string (time)
editedAt: string (time) | null
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/repos/acme/web/changes/12/comments/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"body":"body"}'await branchscope.comments.update({ owner: "acme", name: "web", number: 12, id: 7, body: "body" });DELETE /v1/repos/{owner}/{name}/changes/{number}/comments/{id}
Delete a comment · branchscope.comments.delete()
Its author (a person may also delete what their agents wrote), or the repository's administrators. Its replies go with it.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `changes:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
numberpath, required | integer |
idpath, required | integer |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/repos/acme/web/changes/12/comments/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.comments.delete({ owner: "acme", name: "web", number: 12, id: 7 });branchscope.releases
GET /v1/repos/{owner}/{name}/compare/{range}/views
The change map between two commits · branchscope.releases.compareViews()
From their compare review, which releases.generateNotes queues (a new release's preview): range is base...head, full SHAs, as generateNotes gives them (baseSha, sha). Nothing is queued here.
No credential needed; what you see depends on the one you send.
OAuth apps need the `reviews:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
rangepath, required | string |
Response 200
{
changeMap: ViewDocument
api: ViewDocument
checks: ChecksDocument
renamed: ({
from: string
to: string
})[] // Renamed and moved elements: base ID to head ID.
names: {
nodes: Record<string, string> // Element IDs to readable labels.
edges: Record<string, string> // Edge kinds to verbs.
}
}Errors
400 invalid_request · 404 not_found · 409 not_ready · 410 gone · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/compare/range/views" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.releases.compareViews({ owner: "acme", name: "web", range: "range" });GET /v1/repos/{owner}/{name}/releases
A repository's releases · branchscope.releases.list()
Newest first: drafts, then by when they were published. Drafts only for people and agents with write access; to anyone else a draft doesn't exist (404).
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
tag: string
sha: string // The commit the tag names; for a draft whose tag isn't made yet, the commit it will be made at.
title: string
notes: string // Markdown.
draft: boolean // Only people with write access see drafts.
prerelease: boolean
latest: boolean // The newest published release that isn't a pre-release.
tagExists: boolean // False for a draft whose tag is made when it's published.
previousTag: string | null // The release its notes and change map compare against; null for a first release.
author: string | null
agent: string | null // The agent that drafted it, when an agent credential was used.
review: Review | null // The compare review between the previous release and this one, behind its change map; null without a previous release.
createdAt: string (time)
updatedAt: string (time)
publishedAt: string (time) | null
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/releases" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.releases.list({ owner: "acme", name: "web" });POST /v1/repos/{owner}/{name}/releases
Make a release · branchscope.releases.create()
Of an existing tag, or of a new one at target (a branch or commit). People and agents with write access, in an unarchived repository. Agents (and OAuth apps) only draft: publishing, and changing or deleting a published release, is for people. A new tag is made when the release is published, through the push guard as a push of the tag by the person publishing; a guard that can't be reached refuses, and the release stays a draft.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `repo:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
tag: string
target?: string // For a new tag: the branch or commit to make it at.
title: string
notes?: string
previousTag?: string | null // The release to compare with; the latest one if left out, none if null.
draft?: boolean
prerelease?: boolean
}Response 201
{
tag: string
sha: string // The commit the tag names; for a draft whose tag isn't made yet, the commit it will be made at.
title: string
notes: string // Markdown.
draft: boolean // Only people with write access see drafts.
prerelease: boolean
latest: boolean // The newest published release that isn't a pre-release.
tagExists: boolean // False for a draft whose tag is made when it's published.
previousTag: string | null // The release its notes and change map compare against; null for a first release.
author: string | null
agent: string | null // The agent that drafted it, when an agent credential was used.
review: {
id: integer
baseSha: string // A commit ID (40 or 64 hex characters).
headSha: string // A commit ID (40 or 64 hex characters).
status: "queued" | "running" | "done" | "failed"
error: string
log: string
result: unknown // Counts and findings once done: the review's checks, plan and summary.
updatedAt: string (time)
} | null // The compare review between the previous release and this one, behind its change map; null without a previous release.
createdAt: string (time)
updatedAt: string (time)
publishedAt: string (time) | null
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden, guard_denied · 404 not_found · 409 conflict · 429 rate_limited · 500 internal · 502 upstream_error · 503 guard_unavailable
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/releases" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"tag":"tag","title":"Add invoice export"}'await branchscope.releases.create({ owner: "acme", name: "web", tag: "tag", title: "Add invoice export" });GET /v1/repos/{owner}/{name}/releases/{tag}
A release · branchscope.releases.get()
Drafts only for people and agents with write access; to anyone else a draft doesn't exist (404).
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
tagpath, required | string · URL-encoded: v1%2Frc for v1/rc. |
Response 200
{
tag: string
sha: string // The commit the tag names; for a draft whose tag isn't made yet, the commit it will be made at.
title: string
notes: string // Markdown.
draft: boolean // Only people with write access see drafts.
prerelease: boolean
latest: boolean // The newest published release that isn't a pre-release.
tagExists: boolean // False for a draft whose tag is made when it's published.
previousTag: string | null // The release its notes and change map compare against; null for a first release.
author: string | null
agent: string | null // The agent that drafted it, when an agent credential was used.
review: {
id: integer
baseSha: string // A commit ID (40 or 64 hex characters).
headSha: string // A commit ID (40 or 64 hex characters).
status: "queued" | "running" | "done" | "failed"
error: string
log: string
result: unknown // Counts and findings once done: the review's checks, plan and summary.
updatedAt: string (time)
} | null // The compare review between the previous release and this one, behind its change map; null without a previous release.
createdAt: string (time)
updatedAt: string (time)
publishedAt: string (time) | null
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/releases/tag" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.releases.get({ owner: "acme", name: "web", tag: "tag" });PUT /v1/repos/{owner}/{name}/releases/{tag}
Change or publish a release · branchscope.releases.update()
Its title, notes, previous release and pre-release; draft false publishes it. People and agents with write access, in an unarchived repository. Agents (and OAuth apps) only draft: publishing, and changing or deleting a published release, is for people. A new tag is made when the release is published, through the push guard as a push of the tag by the person publishing; a guard that can't be reached refuses, and the release stays a draft.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `repo:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
tagpath, required | string · URL-encoded: v1%2Frc for v1/rc. |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
title?: string
notes?: string
previousTag?: string | null // The release to compare with; the latest one if left out, none if null.
draft?: boolean
prerelease?: boolean
}Response 200
{
tag: string
sha: string // The commit the tag names; for a draft whose tag isn't made yet, the commit it will be made at.
title: string
notes: string // Markdown.
draft: boolean // Only people with write access see drafts.
prerelease: boolean
latest: boolean // The newest published release that isn't a pre-release.
tagExists: boolean // False for a draft whose tag is made when it's published.
previousTag: string | null // The release its notes and change map compare against; null for a first release.
author: string | null
agent: string | null // The agent that drafted it, when an agent credential was used.
review: {
id: integer
baseSha: string // A commit ID (40 or 64 hex characters).
headSha: string // A commit ID (40 or 64 hex characters).
status: "queued" | "running" | "done" | "failed"
error: string
log: string
result: unknown // Counts and findings once done: the review's checks, plan and summary.
updatedAt: string (time)
} | null // The compare review between the previous release and this one, behind its change map; null without a previous release.
createdAt: string (time)
updatedAt: string (time)
publishedAt: string (time) | null
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden, guard_denied · 404 not_found · 409 conflict · 429 rate_limited · 500 internal · 502 upstream_error · 503 guard_unavailable
Example
curl -X PUT "$BRANCHSCOPE_API/v1/repos/acme/web/releases/tag" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'await branchscope.releases.update({ owner: "acme", name: "web", tag: "tag" });DELETE /v1/repos/{owner}/{name}/releases/{tag}
Delete a release · branchscope.releases.delete()
The tag stays. People and agents with write access, in an unarchived repository. Agents (and OAuth apps) only draft: publishing, and changing or deleting a published release, is for people. A new tag is made when the release is published, through the push guard as a push of the tag by the person publishing; a guard that can't be reached refuses, and the release stays a draft.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `repo:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
tagpath, required | string · URL-encoded: v1%2Frc for v1/rc. |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/repos/acme/web/releases/tag" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.releases.delete({ owner: "acme", name: "web", tag: "tag" });GET /v1/repos/{owner}/{name}/releases/{tag}/views
A release's change map · branchscope.releases.getViews()
What the code graph says changed since the previous release, as a change's diagrams show it: derived from the code. Drafts only for people and agents with write access; to anyone else a draft doesn't exist (404).
No credential needed; what you see depends on the one you send.
OAuth apps need the `reviews:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
tagpath, required | string · URL-encoded: v1%2Frc for v1/rc. |
Response 200
{
changeMap: ViewDocument
api: ViewDocument
checks: ChecksDocument
renamed: ({
from: string
to: string
})[] // Renamed and moved elements: base ID to head ID.
names: {
nodes: Record<string, string> // Element IDs to readable labels.
edges: Record<string, string> // Edge kinds to verbs.
}
}Errors
400 invalid_request · 404 not_found · 409 not_ready · 410 gone · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/releases/tag/views" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.releases.getViews({ owner: "acme", name: "web", tag: "tag" });POST /v1/repos/{owner}/{name}/releases/generate-notes
Offer a new release's notes · branchscope.releases.generateNotes()
The changes merged since the previous release, and notes made from them; queues the change map's review. Nothing is saved.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `repo:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
Request body
{
tag: string
target?: string // For a new tag: the branch or commit to make it at.
previousTag?: string | null // The release to compare with; the latest one if left out, none if null.
}Response 200
{
tag: string
sha: string // A commit ID (40 or 64 hex characters).
previousTag: string | null
baseSha: string | null // The merge base of the previous release's commit and this one: the change map's base.
changes: ({
number: integer
title: string
author: string | null
agent: string | null
mergedSha: string // A commit ID (40 or 64 hex characters).
})[] // Changes merged into the commits since the previous release, oldest first.
commits: integer // Commits since the previous release (up to 1000).
truncated: boolean // There were more than 1000 commits: older ones' changes are left out.
notes: string // Markdown notes made from the changes, in English, to edit before saving.
review: {
id: integer
baseSha: string // A commit ID (40 or 64 hex characters).
headSha: string // A commit ID (40 or 64 hex characters).
status: "queued" | "running" | "done" | "failed"
error: string
log: string
result: unknown // Counts and findings once done: the review's checks, plan and summary.
updatedAt: string (time)
} | null // The compare review, queued if it wasn't already; null without a previous release.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/releases/generate-notes" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"tag":"tag"}'await branchscope.releases.generateNotes({ owner: "acme", name: "web", tag: "tag" });branchscope.deployments
GET /v1/repos/{owner}/{name}/deployments
A repository's deployments, newest first · branchscope.deployments.list()
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
environmentquery | string · Only this environment's. |
shaquery | string · Only this commit's. |
tagquery | string · Only those deployed from this tag. |
Response 200
{
data: ({
id: integer
environment: string
sha: string // A commit ID (40 or 64 hex characters).
ref: string // The branch or tag it was deployed from, or the commit when it was recorded from one.
tag: string | null // The tag, when it was deployed from one: a run started from a tag or a release (or deployed again on one), or a tag recorded.
status: "waiting" | "queued" | "in_progress" | "succeeded" | "failed" | "inactive" // waiting: its job waits for the environment's reviewers or wait timer (M29; set by Branchscope only); queued, in_progress, succeeded or failed; inactive: it succeeded, and a newer deployment to the environment succeeded since (or it was marked so).
description: string // Its latest status's description.
url: string | null // Where it can be seen running, if it was given.
creator: string | null // Who made it: the person who recorded it, or who started its run.
agent: string | null // The agent or app (app:<slug>) that recorded it, if one did.
run: {
id: integer
workflow: string
job: integer | null
jobName: string | null
} | null // The workflow run and job it came from; null when recorded through the API.
createdAt: string (time)
updatedAt: string (time)
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/deployments" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.deployments.list({ owner: "acme", name: "web" });POST /v1/repos/{owner}/{name}/deployments
Record a deployment · branchscope.deployments.create()
Of a branch, tag or commit to an environment (made if it's new), as a deploy outside Branchscope's actions reports it. Recording one that succeeded marks the environment's previous one inactive; deployment.created is sent. People with write access (a write token), and apps granted deployments:write, in an unarchived repository. An environment that requires a tag refuses (400, with the reason) a deployment that isn't from a matching tag protected by an active tag ruleset.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `deployments:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
environment: string
ref: string // A branch, a tag (v1.4.0 or refs/tags/v1.4.0) or a full commit SHA; a name that is both a branch and a tag is the branch.
status?: "queued" | "in_progress" | "succeeded" | "failed" // succeeded if left out: it's running.
url?: string | null
description?: string
}Response 201
{
id: integer
environment: string
sha: string // A commit ID (40 or 64 hex characters).
ref: string // The branch or tag it was deployed from, or the commit when it was recorded from one.
tag: string | null // The tag, when it was deployed from one: a run started from a tag or a release (or deployed again on one), or a tag recorded.
status: "waiting" | "queued" | "in_progress" | "succeeded" | "failed" | "inactive" // waiting: its job waits for the environment's reviewers or wait timer (M29; set by Branchscope only); queued, in_progress, succeeded or failed; inactive: it succeeded, and a newer deployment to the environment succeeded since (or it was marked so).
description: string // Its latest status's description.
url: string | null // Where it can be seen running, if it was given.
creator: string | null // Who made it: the person who recorded it, or who started its run.
agent: string | null // The agent or app (app:<slug>) that recorded it, if one did.
run: {
id: integer
workflow: string
job: integer | null
jobName: string | null
} | null // The workflow run and job it came from; null when recorded through the API.
createdAt: string (time)
updatedAt: string (time)
statuses: ({
status: "waiting" | "queued" | "in_progress" | "succeeded" | "failed" | "inactive" // waiting: its job waits for the environment's reviewers or wait timer (M29; set by Branchscope only); queued, in_progress, succeeded or failed; inactive: it succeeded, and a newer deployment to the environment succeeded since (or it was marked so).
description: string
url: string | null
creator: string | null // Who set it: a person's username, or null when Branchscope did (a job moving, a newer deployment).
agent: string | null // The agent or app (app:<slug>) that set it, if one did.
createdAt: string (time)
})[] // Its history, oldest first.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/deployments" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"environment":"environment","ref":"main"}'await branchscope.deployments.create({ owner: "acme", name: "web", environment: "environment", ref: "main" });GET /v1/repos/{owner}/{name}/deployments/{id}
A deployment and its history · branchscope.deployments.get()
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
idpath, required | integer |
Response 200
{
id: integer
environment: string
sha: string // A commit ID (40 or 64 hex characters).
ref: string // The branch or tag it was deployed from, or the commit when it was recorded from one.
tag: string | null // The tag, when it was deployed from one: a run started from a tag or a release (or deployed again on one), or a tag recorded.
status: "waiting" | "queued" | "in_progress" | "succeeded" | "failed" | "inactive" // waiting: its job waits for the environment's reviewers or wait timer (M29; set by Branchscope only); queued, in_progress, succeeded or failed; inactive: it succeeded, and a newer deployment to the environment succeeded since (or it was marked so).
description: string // Its latest status's description.
url: string | null // Where it can be seen running, if it was given.
creator: string | null // Who made it: the person who recorded it, or who started its run.
agent: string | null // The agent or app (app:<slug>) that recorded it, if one did.
run: {
id: integer
workflow: string
job: integer | null
jobName: string | null
} | null // The workflow run and job it came from; null when recorded through the API.
createdAt: string (time)
updatedAt: string (time)
statuses: ({
status: "waiting" | "queued" | "in_progress" | "succeeded" | "failed" | "inactive" // waiting: its job waits for the environment's reviewers or wait timer (M29; set by Branchscope only); queued, in_progress, succeeded or failed; inactive: it succeeded, and a newer deployment to the environment succeeded since (or it was marked so).
description: string
url: string | null
creator: string | null // Who set it: a person's username, or null when Branchscope did (a job moving, a newer deployment).
agent: string | null // The agent or app (app:<slug>) that set it, if one did.
createdAt: string (time)
})[] // Its history, oldest first.
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/deployments/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.deployments.get({ owner: "acme", name: "web", id: 7 });POST /v1/repos/{owner}/{name}/deployments/{id}/redeploy
Deploy again · branchscope.deployments.redeploy()
Runs the deployment's workflow again, by hand, on its tag (or branch) at its commit: a rollback, for an earlier deployment. Its environments' protection applies as to any run; a tag that names another commit now is refused. Only for deployments from a workflow job. People with write access, and apps granted deployments:write.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `deployments:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
idpath, required | integer |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Response 201
{
id: integer
workflow: string // Its file, e.g. .branchscope/workflows/ci.yml.
name: string
sha: string // A commit ID (40 or 64 hex characters).
ref: string // The branch it ran for, or the tag for tag and release runs.
trigger: "push" | "change" | "schedule" | "manual" | "tag" | "release"
change: integer | null
status: "queued" | "running" | "passed" | "failed" | "cancelled"
actor: string | null // Who pushed, opened the change or started it; null for scheduled runs.
agent: string | null // The agent or app that set it off for its actor, or whose change or run it's for; null for a person's own run.
secrets: "given" | "withheld" | "approved" // given: its jobs get the secrets they name and identity tokens. withheld (see withheldBecause): jobs that name secrets or ask for identity tokens wait until an administrator approves the run, as a person. approved: an administrator did (secretsApprovedBy).
withheldBecause: "agent" | "access" | null // Why its secrets are withheld (or were, until approved): agent, set off by an agent or app, or for an agent's change, and not at the head of a branch or tag agents can't update; access, set off by someone without write access to the repository. null when given.
secretsApprovedBy: string | null
createdAt: string (time)
startedAt: string (time) | null
completedAt: string (time) | null
jobs: ({
id: integer
name: string // The job's name, with its matrix values, e.g. test (node: 24).
status: "waiting" | "queued" | "running" | "passed" | "failed" | "cancelled" | "timed_out" // waiting: for the jobs it needs, for its environment's reviewers or wait timer (see review), or (in a run whose secrets are withheld) for an administrator to approve the secrets it names.
runsOn: string
size: "2cpu" | "4cpu" | "8cpu" | null // On hosted runners: 2cpu (2 vCPU, 4 GB), 4cpu or 8cpu. null on self-hosted runners.
needs: string[]
check: string // The name of its check on the commit.
runner: string | null // The self-hosted runner that ran it, or "Hosted (EU)" for Branchscope's own; null until it starts.
exitCode: integer | null
environment: string | null // The environment it deploys to (environment: in the workflow), or null.
secrets: string[] // The secrets it names (secrets: in the workflow), never their values.
secretsGiven: ({
name: string
level: "organization" | "repository" | "environment"
})[] // The secrets it was given when it started, by name and where each came from (an environment's wins over its repository's, a repository's over its organization's); never their values.
idToken: boolean // It may ask for OIDC identity tokens (permissions: id-token: write).
idTokens: ({
audience: string
issuedAt: string (time)
expiresAt: string (time)
})[] // The identity tokens it was issued, by audience; never the tokens.
review: DeploymentReview | null // Its environment's reviewers and wait timer (M29), once they held it; null when they never did.
secretsApprovedBy: string | null // The administrator whose approval of its deployment, as a reviewer, also approved its withheld secrets (M29).
cache: JobCache | null // Its cache (M17): null when the workflow names none.
summary: string // Why Branchscope ended it, when it did: refused before its first step (an environment's protection, hosted runners), or cancelled. Empty otherwise.
startedAt: string (time) | null
completedAt: string (time) | null
})[]
artifacts: ({
id: integer
name: string
job: string // The job that kept it, with its matrix values.
jobId: integer
sizeBytes: integer
createdAt: string (time)
expiresAt: string (time) // When it's deleted: 30 days after it was kept unless its retention-days says otherwise, at most 90.
})[] // What its jobs kept (M17) and hasn't expired yet.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/deployments/7/redeploy" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.deployments.redeploy({ owner: "acme", name: "web", id: 7 });POST /v1/repos/{owner}/{name}/deployments/{id}/review
Approve or reject a deployment waiting for review · branchscope.deployments.review()
As runs.review, for this deployment's workflow job only. A listed reviewer of the environment, as it is now, who can read the repository, with a session or a personal write token: never an agent or app. Not whoever set off the run when the environment prevents self-review (403, with the reason). Approved: the job's wait timer starts, and it runs once that's over; a repository administrator's approval also approves that job's withheld secrets. Rejected: the job fails with the reviewer's name and comment, its deployment fails, and the jobs that need it are cancelled. The first decision counts (409 after).
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
idpath, required | integer |
Request body
{
decision: "approve" | "reject"
comment?: string // Shown on the run and the audit log; a rejection's is the job's reason.
}Response 200
{
id: integer
environment: string
sha: string // A commit ID (40 or 64 hex characters).
ref: string // The branch or tag it was deployed from, or the commit when it was recorded from one.
tag: string | null // The tag, when it was deployed from one: a run started from a tag or a release (or deployed again on one), or a tag recorded.
status: "waiting" | "queued" | "in_progress" | "succeeded" | "failed" | "inactive" // waiting: its job waits for the environment's reviewers or wait timer (M29; set by Branchscope only); queued, in_progress, succeeded or failed; inactive: it succeeded, and a newer deployment to the environment succeeded since (or it was marked so).
description: string // Its latest status's description.
url: string | null // Where it can be seen running, if it was given.
creator: string | null // Who made it: the person who recorded it, or who started its run.
agent: string | null // The agent or app (app:<slug>) that recorded it, if one did.
run: {
id: integer
workflow: string
job: integer | null
jobName: string | null
} | null // The workflow run and job it came from; null when recorded through the API.
createdAt: string (time)
updatedAt: string (time)
statuses: ({
status: "waiting" | "queued" | "in_progress" | "succeeded" | "failed" | "inactive" // waiting: its job waits for the environment's reviewers or wait timer (M29; set by Branchscope only); queued, in_progress, succeeded or failed; inactive: it succeeded, and a newer deployment to the environment succeeded since (or it was marked so).
description: string
url: string | null
creator: string | null // Who set it: a person's username, or null when Branchscope did (a job moving, a newer deployment).
agent: string | null // The agent or app (app:<slug>) that set it, if one did.
createdAt: string (time)
})[] // Its history, oldest first.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/deployments/7/review" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"decision":"approve"}'await branchscope.deployments.review({ owner: "acme", name: "web", id: 7, decision: "approve" });POST /v1/repos/{owner}/{name}/deployments/{id}/statuses
Set a deployment's status · branchscope.deployments.createStatus()
Added to its history; deployment.status is sent. A deployment from a workflow job follows its job, and can't be set (409). People with write access (a write token), and apps granted deployments:write, in an unarchived repository. An environment that requires a tag refuses (400, with the reason) a deployment that isn't from a matching tag protected by an active tag ruleset.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `deployments:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
idpath, required | integer |
Request body
{
status: "queued" | "in_progress" | "succeeded" | "failed" | "inactive"
url?: string | null // Left as it was if left out.
description?: string
}Response 201
{
id: integer
environment: string
sha: string // A commit ID (40 or 64 hex characters).
ref: string // The branch or tag it was deployed from, or the commit when it was recorded from one.
tag: string | null // The tag, when it was deployed from one: a run started from a tag or a release (or deployed again on one), or a tag recorded.
status: "waiting" | "queued" | "in_progress" | "succeeded" | "failed" | "inactive" // waiting: its job waits for the environment's reviewers or wait timer (M29; set by Branchscope only); queued, in_progress, succeeded or failed; inactive: it succeeded, and a newer deployment to the environment succeeded since (or it was marked so).
description: string // Its latest status's description.
url: string | null // Where it can be seen running, if it was given.
creator: string | null // Who made it: the person who recorded it, or who started its run.
agent: string | null // The agent or app (app:<slug>) that recorded it, if one did.
run: {
id: integer
workflow: string
job: integer | null
jobName: string | null
} | null // The workflow run and job it came from; null when recorded through the API.
createdAt: string (time)
updatedAt: string (time)
statuses: ({
status: "waiting" | "queued" | "in_progress" | "succeeded" | "failed" | "inactive" // waiting: its job waits for the environment's reviewers or wait timer (M29; set by Branchscope only); queued, in_progress, succeeded or failed; inactive: it succeeded, and a newer deployment to the environment succeeded since (or it was marked so).
description: string
url: string | null
creator: string | null // Who set it: a person's username, or null when Branchscope did (a job moving, a newer deployment).
agent: string | null // The agent or app (app:<slug>) that set it, if one did.
createdAt: string (time)
})[] // Its history, oldest first.
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/deployments/7/statuses" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status":"queued"}'await branchscope.deployments.createStatus({ owner: "acme", name: "web", id: 7, status: "queued" });branchscope.environments
GET /v1/repos/{owner}/{name}/environments
A repository's environments · branchscope.environments.list()
By name, each with what's running now and its newest deployment. An environment is made when it's first deployed to, or when an administrator sets its protection.
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
Response 200
{
data: ({
name: string
requiredTag: string | null // A pattern ("*" matches any characters but "/", "**" any characters, "/" too): deployments must come from a tag matching it and protected by an active tag ruleset that stops it being moved. null: any branch, tag or commit.
reviewers: string[] // Required reviewers (M29), by username: a job deploying here waits until one of them approves it. People only; empty for none.
preventSelfReview: boolean // Whoever set off a run can't approve its deployment here (the person an agent acted for, for an agent's run).
waitTimerMinutes: integer // How long a job deploying here waits after it's approved (or, with no reviewers, once it's ready); 0 for no wait.
current: Deployment | null // What's running now: its latest successful deployment.
latest: Deployment | null // Its newest deployment, whatever its status.
createdAt: string (time)
updatedAt: string (time)
})[]
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/environments" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.environments.list({ owner: "acme", name: "web" });GET /v1/repos/{owner}/{name}/environments/{environment}
An environment · branchscope.environments.get()
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
environmentpath, required | string |
Response 200
{
name: string
requiredTag: string | null // A pattern ("*" matches any characters but "/", "**" any characters, "/" too): deployments must come from a tag matching it and protected by an active tag ruleset that stops it being moved. null: any branch, tag or commit.
reviewers: string[] // Required reviewers (M29), by username: a job deploying here waits until one of them approves it. People only; empty for none.
preventSelfReview: boolean // Whoever set off a run can't approve its deployment here (the person an agent acted for, for an agent's run).
waitTimerMinutes: integer // How long a job deploying here waits after it's approved (or, with no reviewers, once it's ready); 0 for no wait.
current: {
id: integer
environment: string
sha: string // A commit ID (40 or 64 hex characters).
ref: string // The branch or tag it was deployed from, or the commit when it was recorded from one.
tag: string | null // The tag, when it was deployed from one: a run started from a tag or a release (or deployed again on one), or a tag recorded.
status: "waiting" | "queued" | "in_progress" | "succeeded" | "failed" | "inactive" // waiting: its job waits for the environment's reviewers or wait timer (M29; set by Branchscope only); queued, in_progress, succeeded or failed; inactive: it succeeded, and a newer deployment to the environment succeeded since (or it was marked so).
description: string // Its latest status's description.
url: string | null // Where it can be seen running, if it was given.
creator: string | null // Who made it: the person who recorded it, or who started its run.
agent: string | null // The agent or app (app:<slug>) that recorded it, if one did.
run: {
id: integer
workflow: string
job: integer | null
jobName: string | null
} | null // The workflow run and job it came from; null when recorded through the API.
createdAt: string (time)
updatedAt: string (time)
} | null // What's running now: its latest successful deployment.
latest: {
id: integer
environment: string
sha: string // A commit ID (40 or 64 hex characters).
ref: string // The branch or tag it was deployed from, or the commit when it was recorded from one.
tag: string | null // The tag, when it was deployed from one: a run started from a tag or a release (or deployed again on one), or a tag recorded.
status: "waiting" | "queued" | "in_progress" | "succeeded" | "failed" | "inactive" // waiting: its job waits for the environment's reviewers or wait timer (M29; set by Branchscope only); queued, in_progress, succeeded or failed; inactive: it succeeded, and a newer deployment to the environment succeeded since (or it was marked so).
description: string // Its latest status's description.
url: string | null // Where it can be seen running, if it was given.
creator: string | null // Who made it: the person who recorded it, or who started its run.
agent: string | null // The agent or app (app:<slug>) that recorded it, if one did.
run: {
id: integer
workflow: string
job: integer | null
jobName: string | null
} | null // The workflow run and job it came from; null when recorded through the API.
createdAt: string (time)
updatedAt: string (time)
} | null // Its newest deployment, whatever its status.
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/environments/environment" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.environments.get({ owner: "acme", name: "web", environment: "environment" });PUT /v1/repos/{owner}/{name}/environments/{environment}
Protect an environment, making it if it's new · branchscope.environments.update()
requiredTag: deployments must come from a tag matching the pattern ("*" matches any characters but "/", "**" any characters, "/" too) and protected by an active tag ruleset that stops it being moved (restrict updates or block force pushes); null lets any branch, tag or commit deploy. reviewers, preventSelfReview and waitTimerMinutes (M29), each left as it is if left out: a job deploying here waits, before its first step and before it gets the environment's secrets or an identity token, until one of its reviewers approves it and its wait timer ran out. Every change is on the audit log. Repository administrators; never agents or apps, so an agent can't loosen its own limits.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
environmentpath, required | string |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
requiredTag: string | null
reviewers?: string[] // Required reviewers, by username: at most 10 people, each able to read the repository; any one of them approving is enough. [] for none.
preventSelfReview?: boolean // Whoever set off a run can't approve its deployment. On for a new environment.
waitTimerMinutes?: integer // Minutes a job waits after approval (or, with no reviewers, once it's ready), up to 43200 (30 days); 0 for none.
}Response 200
{
name: string
requiredTag: string | null // A pattern ("*" matches any characters but "/", "**" any characters, "/" too): deployments must come from a tag matching it and protected by an active tag ruleset that stops it being moved. null: any branch, tag or commit.
reviewers: string[] // Required reviewers (M29), by username: a job deploying here waits until one of them approves it. People only; empty for none.
preventSelfReview: boolean // Whoever set off a run can't approve its deployment here (the person an agent acted for, for an agent's run).
waitTimerMinutes: integer // How long a job deploying here waits after it's approved (or, with no reviewers, once it's ready); 0 for no wait.
current: {
id: integer
environment: string
sha: string // A commit ID (40 or 64 hex characters).
ref: string // The branch or tag it was deployed from, or the commit when it was recorded from one.
tag: string | null // The tag, when it was deployed from one: a run started from a tag or a release (or deployed again on one), or a tag recorded.
status: "waiting" | "queued" | "in_progress" | "succeeded" | "failed" | "inactive" // waiting: its job waits for the environment's reviewers or wait timer (M29; set by Branchscope only); queued, in_progress, succeeded or failed; inactive: it succeeded, and a newer deployment to the environment succeeded since (or it was marked so).
description: string // Its latest status's description.
url: string | null // Where it can be seen running, if it was given.
creator: string | null // Who made it: the person who recorded it, or who started its run.
agent: string | null // The agent or app (app:<slug>) that recorded it, if one did.
run: {
id: integer
workflow: string
job: integer | null
jobName: string | null
} | null // The workflow run and job it came from; null when recorded through the API.
createdAt: string (time)
updatedAt: string (time)
} | null // What's running now: its latest successful deployment.
latest: {
id: integer
environment: string
sha: string // A commit ID (40 or 64 hex characters).
ref: string // The branch or tag it was deployed from, or the commit when it was recorded from one.
tag: string | null // The tag, when it was deployed from one: a run started from a tag or a release (or deployed again on one), or a tag recorded.
status: "waiting" | "queued" | "in_progress" | "succeeded" | "failed" | "inactive" // waiting: its job waits for the environment's reviewers or wait timer (M29; set by Branchscope only); queued, in_progress, succeeded or failed; inactive: it succeeded, and a newer deployment to the environment succeeded since (or it was marked so).
description: string // Its latest status's description.
url: string | null // Where it can be seen running, if it was given.
creator: string | null // Who made it: the person who recorded it, or who started its run.
agent: string | null // The agent or app (app:<slug>) that recorded it, if one did.
run: {
id: integer
workflow: string
job: integer | null
jobName: string | null
} | null // The workflow run and job it came from; null when recorded through the API.
createdAt: string (time)
updatedAt: string (time)
} | null // Its newest deployment, whatever its status.
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/repos/acme/web/environments/environment" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"requiredTag":"requiredTag"}'await branchscope.environments.update({ owner: "acme", name: "web", environment: "environment", requiredTag: "requiredTag" });branchscope.graphs
GET /v1/repos/{owner}/{name}/graph
The graph build for a branch or commit · branchscope.graphs.forRef()
No credential needed; what you see depends on the one you send.
OAuth apps need the `graphs:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
refquery, required | string |
Response 200
{
ref: string
commit: string
build: {
id: integer
commit: string // A commit ID (40 or 64 hex characters).
ref: string // The branch it was built for, or empty.
status: "queued" | "running" | "done" | "failed"
toolchainKey: string
nodes: integer | null
edges: integer | null
error: string
log: string
createdAt: string (time)
updatedAt: string (time)
} | null
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/graph?ref=main" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.graphs.forRef({ owner: "acme", name: "web", ref: "main" });GET /v1/repos/{owner}/{name}/graphs
A repository's graph builds · branchscope.graphs.list()
Newest first.
No credential needed; what you see depends on the one you send.
OAuth apps need the `graphs:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
id: integer
commit: string // A commit ID (40 or 64 hex characters).
ref: string // The branch it was built for, or empty.
status: "queued" | "running" | "done" | "failed"
toolchainKey: string
nodes: integer | null
edges: integer | null
error: string
log: string
createdAt: string (time)
updatedAt: string (time)
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/graphs" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.graphs.list({ owner: "acme", name: "web" });POST /v1/repos/{owner}/{name}/graphs
Build the graph of a branch or commit · branchscope.graphs.build()
A graph already stored for that commit is returned, not rebuilt.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `repo:write` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
Request body
{
ref: string // A branch or a commit.
}Response 201
{
id: integer
commit: string // A commit ID (40 or 64 hex characters).
ref: string // The branch it was built for, or empty.
status: "queued" | "running" | "done" | "failed"
toolchainKey: string
nodes: integer | null
edges: integer | null
error: string
log: string
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/graphs" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"ref":"main"}'await branchscope.graphs.build({ owner: "acme", name: "web", ref: "main" });GET /v1/repos/{owner}/{name}/graphs/{sha}
A commit's graph · branchscope.graphs.download()
The canonical graph JSON, byte for byte as stored.
No credential needed; what you see depends on the one you send.
OAuth apps need the `graphs:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
shapath, required | string |
Response 200
GraphDocumentErrors
400 invalid_request · 404 not_found · 410 gone · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/graphs/9c1d22a0f3e1b6c4d5a7e8f90a1b2c3d4e5f6a7b" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.graphs.download({ owner: "acme", name: "web", sha: "9c1d22a0f3e1b6c4d5a7e8f90a1b2c3d4e5f6a7b" });branchscope.mirrors
GET /v1/repos/{owner}/{name}/mirrors
A repository's push mirrors · branchscope.mirrors.list()
With how each last synced. deployKeys says whether this server can make deploy keys (its SECRETS_KEY is set); without it only HTTPS mirrors, with an external token, can be added. Repository administrators, with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
Response 200
{
data: ({
id: integer
url: string // Where it pushes: https://host/path, or ssh://user@host/path or user@host:path.
credential: "deploy-key" | "token" // deploy-key: over SSH, with a key Branchscope made for this mirror (add publicKey on the other host, with write access). token: over HTTPS, with an administrator's external token.
publicKey: string | null // The deploy key's public half (an OpenSSH authorized_keys line); null for a token.
externalToken: {
id: integer | null
name: string | null
owner: string | null
} | null // The external token it signs in with, and whose it is; id null once the token was deleted. null for a deploy key.
refPatterns: string[] // The branches and tags it sends, as ref patterns: "*" matches any characters but "/", "**" any characters (refs/heads/release/*, refs/heads/release/**); empty for all of them.
mirrorDeletions: boolean // Deleting a branch or tag here deletes it there (and Sync now removes the mirror's refs this repository doesn't have). Off by default.
allowForce: boolean // A force push here is a force push there. Off by default: the mirror refuses history rewrites.
enabled: boolean
status: "never" | "queued" | "syncing" | "ok" | "rejected" | "failed" // never: not synced yet. queued: a sync is due (or a retry, after attempts failures). syncing: now. ok, rejected (the mirror refused some refs), failed: how the last sync went.
lastSyncAt: string (time) | null
lastError: string | null // Why the last sync failed or what the mirror refused; never with credentials.
lastSent: ({
ref: string
status: "created" | "updated" | "forced" | "deleted" | "up to date" | "rejected" | "skipped"
summary: string
})[] // What the last sync sent, ref by ref (skipped: a deletion, not mirrored).
attempts: integer // Failed attempts in a row; it retries with backoff, then waits for the next push or Sync now.
nextAttemptAt: string (time) | null
createdBy: string | null
createdAt: string (time)
updatedAt: string (time)
})[]
deployKeys: boolean
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/mirrors" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.mirrors.list({ owner: "acme", name: "web" });POST /v1/repos/{owner}/{name}/mirrors
Add a push mirror · branchscope.mirrors.create()
After every push Branchscope accepts, the same branches and tags go to the mirror (what the push guard refuses never does). The first sync is the next push, or Sync now. Deletions and force pushes reach it only when turned on. At most 10 per repository. Repository administrators, with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
Request body
{
url: string // https://host/path (with externalToken), or ssh://user@host/path or user@host:path (with a deploy key). Only public hosts.
externalToken?: integer | null // One of your external tokens, for an https:// URL: its provider must fit the host. Left out (or null) for an SSH URL: Branchscope makes a deploy key.
refPatterns?: string[] // Empty for all branches and tags. "*" matches any characters but "/", "**" any characters, "/" too.
mirrorDeletions?: boolean
allowForce?: boolean
enabled?: boolean
}Response 201
{
id: integer
url: string // Where it pushes: https://host/path, or ssh://user@host/path or user@host:path.
credential: "deploy-key" | "token" // deploy-key: over SSH, with a key Branchscope made for this mirror (add publicKey on the other host, with write access). token: over HTTPS, with an administrator's external token.
publicKey: string | null // The deploy key's public half (an OpenSSH authorized_keys line); null for a token.
externalToken: {
id: integer | null
name: string | null
owner: string | null
} | null // The external token it signs in with, and whose it is; id null once the token was deleted. null for a deploy key.
refPatterns: string[] // The branches and tags it sends, as ref patterns: "*" matches any characters but "/", "**" any characters (refs/heads/release/*, refs/heads/release/**); empty for all of them.
mirrorDeletions: boolean // Deleting a branch or tag here deletes it there (and Sync now removes the mirror's refs this repository doesn't have). Off by default.
allowForce: boolean // A force push here is a force push there. Off by default: the mirror refuses history rewrites.
enabled: boolean
status: "never" | "queued" | "syncing" | "ok" | "rejected" | "failed" // never: not synced yet. queued: a sync is due (or a retry, after attempts failures). syncing: now. ok, rejected (the mirror refused some refs), failed: how the last sync went.
lastSyncAt: string (time) | null
lastError: string | null // Why the last sync failed or what the mirror refused; never with credentials.
lastSent: ({
ref: string
status: "created" | "updated" | "forced" | "deleted" | "up to date" | "rejected" | "skipped"
summary: string
})[] // What the last sync sent, ref by ref (skipped: a deletion, not mirrored).
attempts: integer // Failed attempts in a row; it retries with backoff, then waits for the next push or Sync now.
nextAttemptAt: string (time) | null
createdBy: string | null
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal · 503 unavailable
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/mirrors" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url":"url"}'await branchscope.mirrors.create({ owner: "acme", name: "web", url: "url" });GET /v1/repos/{owner}/{name}/mirrors/{id}
A push mirror · branchscope.mirrors.get()
Repository administrators, with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
idpath, required | integer |
Response 200
{
id: integer
url: string // Where it pushes: https://host/path, or ssh://user@host/path or user@host:path.
credential: "deploy-key" | "token" // deploy-key: over SSH, with a key Branchscope made for this mirror (add publicKey on the other host, with write access). token: over HTTPS, with an administrator's external token.
publicKey: string | null // The deploy key's public half (an OpenSSH authorized_keys line); null for a token.
externalToken: {
id: integer | null
name: string | null
owner: string | null
} | null // The external token it signs in with, and whose it is; id null once the token was deleted. null for a deploy key.
refPatterns: string[] // The branches and tags it sends, as ref patterns: "*" matches any characters but "/", "**" any characters (refs/heads/release/*, refs/heads/release/**); empty for all of them.
mirrorDeletions: boolean // Deleting a branch or tag here deletes it there (and Sync now removes the mirror's refs this repository doesn't have). Off by default.
allowForce: boolean // A force push here is a force push there. Off by default: the mirror refuses history rewrites.
enabled: boolean
status: "never" | "queued" | "syncing" | "ok" | "rejected" | "failed" // never: not synced yet. queued: a sync is due (or a retry, after attempts failures). syncing: now. ok, rejected (the mirror refused some refs), failed: how the last sync went.
lastSyncAt: string (time) | null
lastError: string | null // Why the last sync failed or what the mirror refused; never with credentials.
lastSent: ({
ref: string
status: "created" | "updated" | "forced" | "deleted" | "up to date" | "rejected" | "skipped"
summary: string
})[] // What the last sync sent, ref by ref (skipped: a deletion, not mirrored).
attempts: integer // Failed attempts in a row; it retries with backoff, then waits for the next push or Sync now.
nextAttemptAt: string (time) | null
createdBy: string | null
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/mirrors/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.mirrors.get({ owner: "acme", name: "web", id: 7 });PUT /v1/repos/{owner}/{name}/mirrors/{id}
Change a push mirror · branchscope.mirrors.update()
Only the fields sent change. A new URL (or ref patterns, or turning it back on) syncs everything on the next push or Sync now. Moving a token mirror to another URL takes your own token: another administrator's isn't sent to a new host. Repository administrators, with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
idpath, required | integer |
Request body
{
url?: string // https://host/path (with externalToken), or ssh://user@host/path or user@host:path (with a deploy key). Only public hosts.
externalToken?: integer | null // One of your external tokens, for an https:// URL: its provider must fit the host. Left out (or null) for an SSH URL: Branchscope makes a deploy key.
refPatterns?: string[] // Empty for all branches and tags. "*" matches any characters but "/", "**" any characters, "/" too.
mirrorDeletions?: boolean
allowForce?: boolean
enabled?: boolean
}Response 200
{
id: integer
url: string // Where it pushes: https://host/path, or ssh://user@host/path or user@host:path.
credential: "deploy-key" | "token" // deploy-key: over SSH, with a key Branchscope made for this mirror (add publicKey on the other host, with write access). token: over HTTPS, with an administrator's external token.
publicKey: string | null // The deploy key's public half (an OpenSSH authorized_keys line); null for a token.
externalToken: {
id: integer | null
name: string | null
owner: string | null
} | null // The external token it signs in with, and whose it is; id null once the token was deleted. null for a deploy key.
refPatterns: string[] // The branches and tags it sends, as ref patterns: "*" matches any characters but "/", "**" any characters (refs/heads/release/*, refs/heads/release/**); empty for all of them.
mirrorDeletions: boolean // Deleting a branch or tag here deletes it there (and Sync now removes the mirror's refs this repository doesn't have). Off by default.
allowForce: boolean // A force push here is a force push there. Off by default: the mirror refuses history rewrites.
enabled: boolean
status: "never" | "queued" | "syncing" | "ok" | "rejected" | "failed" // never: not synced yet. queued: a sync is due (or a retry, after attempts failures). syncing: now. ok, rejected (the mirror refused some refs), failed: how the last sync went.
lastSyncAt: string (time) | null
lastError: string | null // Why the last sync failed or what the mirror refused; never with credentials.
lastSent: ({
ref: string
status: "created" | "updated" | "forced" | "deleted" | "up to date" | "rejected" | "skipped"
summary: string
})[] // What the last sync sent, ref by ref (skipped: a deletion, not mirrored).
attempts: integer // Failed attempts in a row; it retries with backoff, then waits for the next push or Sync now.
nextAttemptAt: string (time) | null
createdBy: string | null
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/repos/acme/web/mirrors/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'await branchscope.mirrors.update({ owner: "acme", name: "web", id: 7 });DELETE /v1/repos/{owner}/{name}/mirrors/{id}
Delete a push mirror · branchscope.mirrors.delete()
Nothing more is sent; the mirror keeps what it has. Repository administrators, with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
idpath, required | integer |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/repos/acme/web/mirrors/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.mirrors.delete({ owner: "acme", name: "web", id: 7 });POST /v1/repos/{owner}/{name}/mirrors/{id}/sync
Sync a push mirror now · branchscope.mirrors.sync()
Sends every branch and tag it mirrors, soon (status queued, then syncing); a failure retries with backoff. Repository administrators, with a session or a personal write token; never agents or apps.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
idpath, required | integer |
Response 200
{
id: integer
url: string // Where it pushes: https://host/path, or ssh://user@host/path or user@host:path.
credential: "deploy-key" | "token" // deploy-key: over SSH, with a key Branchscope made for this mirror (add publicKey on the other host, with write access). token: over HTTPS, with an administrator's external token.
publicKey: string | null // The deploy key's public half (an OpenSSH authorized_keys line); null for a token.
externalToken: {
id: integer | null
name: string | null
owner: string | null
} | null // The external token it signs in with, and whose it is; id null once the token was deleted. null for a deploy key.
refPatterns: string[] // The branches and tags it sends, as ref patterns: "*" matches any characters but "/", "**" any characters (refs/heads/release/*, refs/heads/release/**); empty for all of them.
mirrorDeletions: boolean // Deleting a branch or tag here deletes it there (and Sync now removes the mirror's refs this repository doesn't have). Off by default.
allowForce: boolean // A force push here is a force push there. Off by default: the mirror refuses history rewrites.
enabled: boolean
status: "never" | "queued" | "syncing" | "ok" | "rejected" | "failed" // never: not synced yet. queued: a sync is due (or a retry, after attempts failures). syncing: now. ok, rejected (the mirror refused some refs), failed: how the last sync went.
lastSyncAt: string (time) | null
lastError: string | null // Why the last sync failed or what the mirror refused; never with credentials.
lastSent: ({
ref: string
status: "created" | "updated" | "forced" | "deleted" | "up to date" | "rejected" | "skipped"
summary: string
})[] // What the last sync sent, ref by ref (skipped: a deletion, not mirrored).
attempts: integer // Failed attempts in a row; it retries with backoff, then waits for the next push or Sync now.
nextAttemptAt: string (time) | null
createdBy: string | null
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/mirrors/7/sync" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.mirrors.sync({ owner: "acme", name: "web", id: 7 });branchscope.rulesets
GET /v1/repos/{owner}/{name}/rulesets
A repository's branch and tag rulesets · branchscope.rulesets.list()
Oldest first; kind lists one kind only. Until its rulesets are first changed, a repository has one, "Protected branches", made from its older settings (main by default). Repository administrators; never agents, so an agent can't loosen its own limits.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
kindquery | "branch" | "tag" · What the ruleset targets: branches (refs/heads/*) or tags (refs/tags/*). |
Response 200
{
data: ({
name: string
enforcement: "active" | "evaluate" | "disabled" // Active rulesets are enforced; disabled ones aren't. Evaluate: never refuses, but records what it would have refused in push activity and the ruleset's insights.
target: {
kind: "branch" | "tag" // What the ruleset targets: branches (refs/heads/*) or tags (refs/tags/*).
include: string[]
exclude: string[]
}
rules: {
restrictCreations: boolean // Only those who bypass may create targeted branches or tags.
restrictUpdates: boolean // Only those who bypass may update targeted branches (by push or by merge), or move targeted tags.
restrictDeletions: boolean // Only those who bypass may delete them.
blockForcePushes: boolean // Nobody who doesn't bypass may rewrite their history (for a tag: move it with a force push).
requireChange: boolean // Branches: they change only by merging a change (Branchscope's pull request), no direct pushes. Always false for tags.
blockAgents: boolean // Agents can't create, update or delete them, by push or by merge; a person merges their changes (or, for tags, tags or publishes a release).
requiredChecks: string[] | null // Branches: checks that must pass on a change's head before it merges; needs requireChange. Tags: checks that must have passed on a tag's commit, so it's made by publishing a release, which checks them. Null when the rule is off.
requiredDeployments: string[] | null // Environments whose latest deployment of the commit must have succeeded. Branches: of a change's head, before it merges; needs requireChange, so a direct push is refused. Tags: of the tag's commit, so it's made by publishing a release, which checks them; a push of it is refused. Null (the default) when the rule is off.
requireLinearHistory: boolean // Branches: no merge commits (a commit with more than one parent) may land, by push or by merge; changes merge by squash or rebase. Always false for tags.
requireSignedCommits: boolean // Every commit a push or merge adds, and an annotated tag itself, must carry a valid SSH signature by a key registered to a Branchscope user (or Branchscope's own, for the merges it makes). GPG signatures aren't verified yet.
}
bypass: ({
type: "role"
role: "admin" | "write" // Repository admin, or anyone with write access (admins too).
} | {
type: "user"
username: string
} | {
type: "agent"
agent: string // An agent app, app:<slug>.
})[]
id: integer
createdAt: string (time)
updatedAt: string (time)
})[]
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/rulesets" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.rulesets.list({ owner: "acme", name: "web" });POST /v1/repos/{owner}/{name}/rulesets
Add a branch or tag ruleset · branchscope.rulesets.create()
Up to 50 per repository, each name once. A tag ruleset can't require a change, nor target ~DEFAULT_BRANCH. Repository administrators; never agents, so an agent can't loosen its own limits. An archived repository's rulesets can't change. The push guard hears of the change at once.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
name: string
enforcement: "active" | "evaluate" | "disabled" // Active rulesets are enforced; disabled ones aren't. Evaluate: never refuses, but records what it would have refused in push activity and the ruleset's insights.
target: {
kind?: "branch" | "tag" // Branches when a ruleset is made without it; a ruleset's kind doesn't change.
include: string[]
exclude: string[]
} // A branch or tag is targeted when it matches an include and no exclude. In a pattern, "*" matches any characters but "/", "**" any characters, "/" too; ~DEFAULT_BRANCH is the default branch, ~ALL every branch or tag.
rules: {
restrictCreations: boolean // Only those who bypass may create targeted branches or tags.
restrictUpdates: boolean // Only those who bypass may update targeted branches (by push or by merge), or move targeted tags.
restrictDeletions: boolean // Only those who bypass may delete them.
blockForcePushes: boolean // Nobody who doesn't bypass may rewrite their history (for a tag: move it with a force push).
requireChange: boolean // Branches: they change only by merging a change (Branchscope's pull request), no direct pushes. Always false for tags.
blockAgents: boolean // Agents can't create, update or delete them, by push or by merge; a person merges their changes (or, for tags, tags or publishes a release).
requiredChecks: string[] | null // Branches: checks that must pass on a change's head before it merges; needs requireChange. Tags: checks that must have passed on a tag's commit, so it's made by publishing a release, which checks them. Null when the rule is off.
requiredDeployments?: string[] | null // Environments whose latest deployment of the commit must have succeeded. Branches: of a change's head, before it merges; needs requireChange, so a direct push is refused. Tags: of the tag's commit, so it's made by publishing a release, which checks them; a push of it is refused. Null (the default) when the rule is off.
requireLinearHistory?: boolean // Branches: no merge commits (a commit with more than one parent) may land, by push or by merge; changes merge by squash or rebase. Always false for tags.
requireSignedCommits?: boolean // Every commit a push or merge adds, and an annotated tag itself, must carry a valid SSH signature by a key registered to a Branchscope user (or Branchscope's own, for the merges it makes). GPG signatures aren't verified yet.
}
bypass: ({
type: "role"
role: "admin" | "write" // Repository admin, or anyone with write access (admins too).
} | {
type: "user"
username: string
} | {
type: "agent"
agent: string // An agent app, app:<slug>.
})[]
}Response 201
{
name: string
enforcement: "active" | "evaluate" | "disabled" // Active rulesets are enforced; disabled ones aren't. Evaluate: never refuses, but records what it would have refused in push activity and the ruleset's insights.
target: {
kind: "branch" | "tag" // What the ruleset targets: branches (refs/heads/*) or tags (refs/tags/*).
include: string[]
exclude: string[]
}
rules: {
restrictCreations: boolean // Only those who bypass may create targeted branches or tags.
restrictUpdates: boolean // Only those who bypass may update targeted branches (by push or by merge), or move targeted tags.
restrictDeletions: boolean // Only those who bypass may delete them.
blockForcePushes: boolean // Nobody who doesn't bypass may rewrite their history (for a tag: move it with a force push).
requireChange: boolean // Branches: they change only by merging a change (Branchscope's pull request), no direct pushes. Always false for tags.
blockAgents: boolean // Agents can't create, update or delete them, by push or by merge; a person merges their changes (or, for tags, tags or publishes a release).
requiredChecks: string[] | null // Branches: checks that must pass on a change's head before it merges; needs requireChange. Tags: checks that must have passed on a tag's commit, so it's made by publishing a release, which checks them. Null when the rule is off.
requiredDeployments: string[] | null // Environments whose latest deployment of the commit must have succeeded. Branches: of a change's head, before it merges; needs requireChange, so a direct push is refused. Tags: of the tag's commit, so it's made by publishing a release, which checks them; a push of it is refused. Null (the default) when the rule is off.
requireLinearHistory: boolean // Branches: no merge commits (a commit with more than one parent) may land, by push or by merge; changes merge by squash or rebase. Always false for tags.
requireSignedCommits: boolean // Every commit a push or merge adds, and an annotated tag itself, must carry a valid SSH signature by a key registered to a Branchscope user (or Branchscope's own, for the merges it makes). GPG signatures aren't verified yet.
}
bypass: ({
type: "role"
role: "admin" | "write" // Repository admin, or anyone with write access (admins too).
} | {
type: "user"
username: string
} | {
type: "agent"
agent: string // An agent app, app:<slug>.
})[]
id: integer
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/rulesets" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"web","enforcement":"active","target":"main","rules":{"restrictCreations":true,"restrictUpdates":true,"restrictDeletions":true,"blockForcePushes":true,"requireChange":true,"blockAgents":true,"requiredChecks":[]},"bypass":[]}'await branchscope.rulesets.create({ owner: "acme", name: "web", enforcement: "active", target: "main", rules: {restrictCreations: true, restrictUpdates: true, restrictDeletions: true, blockForcePushes: true, requireChange: true, blockAgents: true, requiredChecks: []}, bypass: [] });GET /v1/repos/{owner}/{name}/rulesets/{id}
A branch or tag ruleset · branchscope.rulesets.get()
Repository administrators; never agents, so an agent can't loosen its own limits.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
idpath, required | integer |
Response 200
{
name: string
enforcement: "active" | "evaluate" | "disabled" // Active rulesets are enforced; disabled ones aren't. Evaluate: never refuses, but records what it would have refused in push activity and the ruleset's insights.
target: {
kind: "branch" | "tag" // What the ruleset targets: branches (refs/heads/*) or tags (refs/tags/*).
include: string[]
exclude: string[]
}
rules: {
restrictCreations: boolean // Only those who bypass may create targeted branches or tags.
restrictUpdates: boolean // Only those who bypass may update targeted branches (by push or by merge), or move targeted tags.
restrictDeletions: boolean // Only those who bypass may delete them.
blockForcePushes: boolean // Nobody who doesn't bypass may rewrite their history (for a tag: move it with a force push).
requireChange: boolean // Branches: they change only by merging a change (Branchscope's pull request), no direct pushes. Always false for tags.
blockAgents: boolean // Agents can't create, update or delete them, by push or by merge; a person merges their changes (or, for tags, tags or publishes a release).
requiredChecks: string[] | null // Branches: checks that must pass on a change's head before it merges; needs requireChange. Tags: checks that must have passed on a tag's commit, so it's made by publishing a release, which checks them. Null when the rule is off.
requiredDeployments: string[] | null // Environments whose latest deployment of the commit must have succeeded. Branches: of a change's head, before it merges; needs requireChange, so a direct push is refused. Tags: of the tag's commit, so it's made by publishing a release, which checks them; a push of it is refused. Null (the default) when the rule is off.
requireLinearHistory: boolean // Branches: no merge commits (a commit with more than one parent) may land, by push or by merge; changes merge by squash or rebase. Always false for tags.
requireSignedCommits: boolean // Every commit a push or merge adds, and an annotated tag itself, must carry a valid SSH signature by a key registered to a Branchscope user (or Branchscope's own, for the merges it makes). GPG signatures aren't verified yet.
}
bypass: ({
type: "role"
role: "admin" | "write" // Repository admin, or anyone with write access (admins too).
} | {
type: "user"
username: string
} | {
type: "agent"
agent: string // An agent app, app:<slug>.
})[]
id: integer
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/rulesets/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.rulesets.get({ owner: "acme", name: "web", id: 7 });PUT /v1/repos/{owner}/{name}/rulesets/{id}
Change a branch or tag ruleset · branchscope.rulesets.update()
Replaces it; its kind stays. Repository administrators; never agents, so an agent can't loosen its own limits. An archived repository's rulesets can't change. The push guard hears of the change at once.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
idpath, required | integer |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
name: string
enforcement: "active" | "evaluate" | "disabled" // Active rulesets are enforced; disabled ones aren't. Evaluate: never refuses, but records what it would have refused in push activity and the ruleset's insights.
target: {
kind?: "branch" | "tag" // Branches when a ruleset is made without it; a ruleset's kind doesn't change.
include: string[]
exclude: string[]
} // A branch or tag is targeted when it matches an include and no exclude. In a pattern, "*" matches any characters but "/", "**" any characters, "/" too; ~DEFAULT_BRANCH is the default branch, ~ALL every branch or tag.
rules: {
restrictCreations: boolean // Only those who bypass may create targeted branches or tags.
restrictUpdates: boolean // Only those who bypass may update targeted branches (by push or by merge), or move targeted tags.
restrictDeletions: boolean // Only those who bypass may delete them.
blockForcePushes: boolean // Nobody who doesn't bypass may rewrite their history (for a tag: move it with a force push).
requireChange: boolean // Branches: they change only by merging a change (Branchscope's pull request), no direct pushes. Always false for tags.
blockAgents: boolean // Agents can't create, update or delete them, by push or by merge; a person merges their changes (or, for tags, tags or publishes a release).
requiredChecks: string[] | null // Branches: checks that must pass on a change's head before it merges; needs requireChange. Tags: checks that must have passed on a tag's commit, so it's made by publishing a release, which checks them. Null when the rule is off.
requiredDeployments?: string[] | null // Environments whose latest deployment of the commit must have succeeded. Branches: of a change's head, before it merges; needs requireChange, so a direct push is refused. Tags: of the tag's commit, so it's made by publishing a release, which checks them; a push of it is refused. Null (the default) when the rule is off.
requireLinearHistory?: boolean // Branches: no merge commits (a commit with more than one parent) may land, by push or by merge; changes merge by squash or rebase. Always false for tags.
requireSignedCommits?: boolean // Every commit a push or merge adds, and an annotated tag itself, must carry a valid SSH signature by a key registered to a Branchscope user (or Branchscope's own, for the merges it makes). GPG signatures aren't verified yet.
}
bypass: ({
type: "role"
role: "admin" | "write" // Repository admin, or anyone with write access (admins too).
} | {
type: "user"
username: string
} | {
type: "agent"
agent: string // An agent app, app:<slug>.
})[]
}Response 200
{
name: string
enforcement: "active" | "evaluate" | "disabled" // Active rulesets are enforced; disabled ones aren't. Evaluate: never refuses, but records what it would have refused in push activity and the ruleset's insights.
target: {
kind: "branch" | "tag" // What the ruleset targets: branches (refs/heads/*) or tags (refs/tags/*).
include: string[]
exclude: string[]
}
rules: {
restrictCreations: boolean // Only those who bypass may create targeted branches or tags.
restrictUpdates: boolean // Only those who bypass may update targeted branches (by push or by merge), or move targeted tags.
restrictDeletions: boolean // Only those who bypass may delete them.
blockForcePushes: boolean // Nobody who doesn't bypass may rewrite their history (for a tag: move it with a force push).
requireChange: boolean // Branches: they change only by merging a change (Branchscope's pull request), no direct pushes. Always false for tags.
blockAgents: boolean // Agents can't create, update or delete them, by push or by merge; a person merges their changes (or, for tags, tags or publishes a release).
requiredChecks: string[] | null // Branches: checks that must pass on a change's head before it merges; needs requireChange. Tags: checks that must have passed on a tag's commit, so it's made by publishing a release, which checks them. Null when the rule is off.
requiredDeployments: string[] | null // Environments whose latest deployment of the commit must have succeeded. Branches: of a change's head, before it merges; needs requireChange, so a direct push is refused. Tags: of the tag's commit, so it's made by publishing a release, which checks them; a push of it is refused. Null (the default) when the rule is off.
requireLinearHistory: boolean // Branches: no merge commits (a commit with more than one parent) may land, by push or by merge; changes merge by squash or rebase. Always false for tags.
requireSignedCommits: boolean // Every commit a push or merge adds, and an annotated tag itself, must carry a valid SSH signature by a key registered to a Branchscope user (or Branchscope's own, for the merges it makes). GPG signatures aren't verified yet.
}
bypass: ({
type: "role"
role: "admin" | "write" // Repository admin, or anyone with write access (admins too).
} | {
type: "user"
username: string
} | {
type: "agent"
agent: string // An agent app, app:<slug>.
})[]
id: integer
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/repos/acme/web/rulesets/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"web","enforcement":"active","target":"main","rules":{"restrictCreations":true,"restrictUpdates":true,"restrictDeletions":true,"blockForcePushes":true,"requireChange":true,"blockAgents":true,"requiredChecks":[]},"bypass":[]}'await branchscope.rulesets.update({ owner: "acme", name: "web", id: 7, enforcement: "active", target: "main", rules: {restrictCreations: true, restrictUpdates: true, restrictDeletions: true, blockForcePushes: true, requireChange: true, blockAgents: true, requiredChecks: []}, bypass: [] });DELETE /v1/repos/{owner}/{name}/rulesets/{id}
Delete a branch or tag ruleset · branchscope.rulesets.delete()
Repository administrators; never agents, so an agent can't loosen its own limits. An archived repository's rulesets can't change. The push guard hears of the change at once.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
idpath, required | integer |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/repos/acme/web/rulesets/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.rulesets.delete({ owner: "acme", name: "web", id: 7 });GET /v1/repos/{owner}/{name}/rulesets/{id}/insights
What a ruleset would have refused · branchscope.rulesets.insights()
Its most recent evaluations (up to 50), newest first: each push or merge it would have refused while in evaluate mode, and why. Repository administrators; never agents, so an agent can't loosen its own limits.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
idpath, required | integer |
Response 200
{
data: ({
ruleset: string // The ruleset's name, when it was evaluated.
rulesetId: integer | null // The ruleset's id, which stays when it's renamed; null for an entry from a push guard before ruleset ids, which only named it.
ref: string // The full ref name, e.g. refs/heads/main.
message: string // The refusal, as the ruleset would have made it had it been active.
unknown: boolean // The rule needed to know something the git service didn't say (whether there were merge commits, or signatures); an active ruleset refuses then.
eventId: integer // The push activity entry.
at: string (time)
source: "push" | "merge"
pusher: string
agent: string
})[]
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/rulesets/7/insights" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.rulesets.insights({ owner: "acme", name: "web", id: 7 });POST /v1/repos/{owner}/{name}/rulesets/release-tags
Add the "Release tags" tag ruleset · branchscope.rulesets.addReleaseTags()
The tag ruleset a new repository starts with, for one made before it did: v* tags can't be moved or deleted, nor written by agents. Conflict if a ruleset is already named "Release tags". Repository administrators; never agents, so an agent can't loosen its own limits. An archived repository's rulesets can't change. The push guard hears of the change at once.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Response 201
{
name: string
enforcement: "active" | "evaluate" | "disabled" // Active rulesets are enforced; disabled ones aren't. Evaluate: never refuses, but records what it would have refused in push activity and the ruleset's insights.
target: {
kind: "branch" | "tag" // What the ruleset targets: branches (refs/heads/*) or tags (refs/tags/*).
include: string[]
exclude: string[]
}
rules: {
restrictCreations: boolean // Only those who bypass may create targeted branches or tags.
restrictUpdates: boolean // Only those who bypass may update targeted branches (by push or by merge), or move targeted tags.
restrictDeletions: boolean // Only those who bypass may delete them.
blockForcePushes: boolean // Nobody who doesn't bypass may rewrite their history (for a tag: move it with a force push).
requireChange: boolean // Branches: they change only by merging a change (Branchscope's pull request), no direct pushes. Always false for tags.
blockAgents: boolean // Agents can't create, update or delete them, by push or by merge; a person merges their changes (or, for tags, tags or publishes a release).
requiredChecks: string[] | null // Branches: checks that must pass on a change's head before it merges; needs requireChange. Tags: checks that must have passed on a tag's commit, so it's made by publishing a release, which checks them. Null when the rule is off.
requiredDeployments: string[] | null // Environments whose latest deployment of the commit must have succeeded. Branches: of a change's head, before it merges; needs requireChange, so a direct push is refused. Tags: of the tag's commit, so it's made by publishing a release, which checks them; a push of it is refused. Null (the default) when the rule is off.
requireLinearHistory: boolean // Branches: no merge commits (a commit with more than one parent) may land, by push or by merge; changes merge by squash or rebase. Always false for tags.
requireSignedCommits: boolean // Every commit a push or merge adds, and an annotated tag itself, must carry a valid SSH signature by a key registered to a Branchscope user (or Branchscope's own, for the merges it makes). GPG signatures aren't verified yet.
}
bypass: ({
type: "role"
role: "admin" | "write" // Repository admin, or anyone with write access (admins too).
} | {
type: "user"
username: string
} | {
type: "agent"
agent: string // An agent app, app:<slug>.
})[]
id: integer
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/rulesets/release-tags" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.rulesets.addReleaseTags({ owner: "acme", name: "web" });branchscope.tasks
GET /v1/repos/{owner}/{name}/tasks
A repository's tasks for agents · branchscope.tasks.listForRepo()
Newest first.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
statusquery | "open" | "all" · open: queued, working or blocked. |
changequery | integer · Only the tasks on this change. |
Response 200
{
data: ({
id: integer
repo: string
agent: AppPublic // The agent app it's for, installed in the repository's organization.
change: integer | null // The change it's about, or the change the agent opened for it.
base: string | null // For a free-standing task: the branch to start from.
instructions: string
status: "queued" | "working" | "blocked" | "done" | "failed" | "cancelled"
statusText: string // The agent's latest word on it.
createdBy: string | null
createdAt: string (time)
updatedAt: string (time)
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 401 unauthorized · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/tasks" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.tasks.listForRepo({ owner: "acme", name: "web" });POST /v1/repos/{owner}/{name}/tasks
Hand a task to an agent app · branchscope.tasks.create()
On a change (change: its number), or free-standing from a base branch. People with write access; agents can't hand out work. The agent gets it at its webhook (agent.task) and in its MCP sessions.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
ownerpath, required | string |
namepath, required | string |
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
agent: string // The agent app's slug.
instructions: string
change?: integer | null
base?: string | null // For a free-standing task; the default branch if left out.
}Response 201
{
id: integer
repo: string
agent: {
slug: string
name: string
description: string
homepage: string | null
owner: string // The user or organization that registered it.
scopes: string[] // What it may ask for.
iconUrl: string | null // Its icon, served by Branchscope; null when it has none.
firstParty: boolean // Built into Branchscope (the Branchscope CLI).
agent: boolean // An agent app (M14): organizations can delegate work to it, and see it on their Agents page.
dynamic: boolean // Registered by a client itself (RFC 7591), such as an MCP client: no one at Branchscope or an organization vouches for it.
} // The agent app it's for, installed in the repository's organization.
change: integer | null // The change it's about, or the change the agent opened for it.
base: string | null // For a free-standing task: the branch to start from.
instructions: string
status: "queued" | "working" | "blocked" | "done" | "failed" | "cancelled"
statusText: string // The agent's latest word on it.
createdBy: string | null
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/repos/acme/web/tasks" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"agent":"agent","instructions":"instructions"}'await branchscope.tasks.create({ owner: "acme", name: "web", agent: "agent", instructions: "instructions" });GET /v1/tasks
An agent's tasks · branchscope.tasks.listMine()
For an installed agent app: its open tasks (queued, working, blocked). For a person: the tasks they handed out.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `repo:read` scope.
Parameters
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
id: integer
repo: string
agent: AppPublic // The agent app it's for, installed in the repository's organization.
change: integer | null // The change it's about, or the change the agent opened for it.
base: string | null // For a free-standing task: the branch to start from.
instructions: string
status: "queued" | "working" | "blocked" | "done" | "failed" | "cancelled"
statusText: string // The agent's latest word on it.
createdBy: string | null
createdAt: string (time)
updatedAt: string (time)
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 401 unauthorized · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/tasks" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.tasks.listMine();GET /v1/tasks/{id}
A task · branchscope.tasks.get()
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `repo:read` scope.
Parameters
idpath, required | integer |
Response 200
{
id: integer
repo: string
agent: {
slug: string
name: string
description: string
homepage: string | null
owner: string // The user or organization that registered it.
scopes: string[] // What it may ask for.
iconUrl: string | null // Its icon, served by Branchscope; null when it has none.
firstParty: boolean // Built into Branchscope (the Branchscope CLI).
agent: boolean // An agent app (M14): organizations can delegate work to it, and see it on their Agents page.
dynamic: boolean // Registered by a client itself (RFC 7591), such as an MCP client: no one at Branchscope or an organization vouches for it.
} // The agent app it's for, installed in the repository's organization.
change: integer | null // The change it's about, or the change the agent opened for it.
base: string | null // For a free-standing task: the branch to start from.
instructions: string
status: "queued" | "working" | "blocked" | "done" | "failed" | "cancelled"
statusText: string // The agent's latest word on it.
createdBy: string | null
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/tasks/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.tasks.get({ id: 7 });POST /v1/tasks/{id}/cancel
Cancel a task · branchscope.tasks.cancel()
People with write access to its repository. The agent hears of it as it heard of the task.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
idpath, required | integer |
Response 201
{
id: integer
repo: string
agent: {
slug: string
name: string
description: string
homepage: string | null
owner: string // The user or organization that registered it.
scopes: string[] // What it may ask for.
iconUrl: string | null // Its icon, served by Branchscope; null when it has none.
firstParty: boolean // Built into Branchscope (the Branchscope CLI).
agent: boolean // An agent app (M14): organizations can delegate work to it, and see it on their Agents page.
dynamic: boolean // Registered by a client itself (RFC 7591), such as an MCP client: no one at Branchscope or an organization vouches for it.
} // The agent app it's for, installed in the repository's organization.
change: integer | null // The change it's about, or the change the agent opened for it.
base: string | null // For a free-standing task: the branch to start from.
instructions: string
status: "queued" | "working" | "blocked" | "done" | "failed" | "cancelled"
statusText: string // The agent's latest word on it.
createdBy: string | null
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/tasks/7/cancel" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.tasks.cancel({ id: 7 });POST /v1/tasks/{id}/status
Report on a task, as its agent · branchscope.tasks.updateStatus()
working, blocked (say what you need), done or failed, with a line of text. change links the change you opened for a free-standing task; on a change, the status shows there too.
Needs a credential: a personal or agent token, or a web session.
OAuth apps need the `changes:write` scope.
Parameters
idpath, required | integer |
Request body
{
status: "working" | "blocked" | "done" | "failed"
text?: string
change?: integer | null
}Response 201
{
id: integer
repo: string
agent: {
slug: string
name: string
description: string
homepage: string | null
owner: string // The user or organization that registered it.
scopes: string[] // What it may ask for.
iconUrl: string | null // Its icon, served by Branchscope; null when it has none.
firstParty: boolean // Built into Branchscope (the Branchscope CLI).
agent: boolean // An agent app (M14): organizations can delegate work to it, and see it on their Agents page.
dynamic: boolean // Registered by a client itself (RFC 7591), such as an MCP client: no one at Branchscope or an organization vouches for it.
} // The agent app it's for, installed in the repository's organization.
change: integer | null // The change it's about, or the change the agent opened for it.
base: string | null // For a free-standing task: the branch to start from.
instructions: string
status: "queued" | "working" | "blocked" | "done" | "failed" | "cancelled"
statusText: string // The agent's latest word on it.
createdBy: string | null
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/tasks/7/status" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status":"working"}'await branchscope.tasks.updateStatus({ id: 7, status: "working" });branchscope.views
GET /v1/repos/{owner}/{name}/views/{view}
A branch's or commit's architecture or personal-data flow · branchscope.views.get()
No credential needed; what you see depends on the one you send.
OAuth apps need the `graphs:read` scope.
Parameters
ownerpath, required | string |
namepath, required | string |
viewpath, required | "architecture" | "data-flow" |
refquery, required | string |
projectquery | string · Drill into one project. |
Response 200
ViewDocumentErrors
400 invalid_request · 404 not_found · 409 not_ready · 410 gone · 429 rate_limited · 500 internal · 502 upstream_error
Example
curl "$BRANCHSCOPE_API/v1/repos/acme/web/views/architecture?ref=main" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.views.get({ owner: "acme", name: "web", view: "architecture", ref: "main" });branchscope.ssh
GET /v1/ssh
How to reach git over SSH · branchscope.ssh.info()
The URL and the host keys to verify.
No credential needed; what you see depends on the one you send.
Response 200
{
enabled: boolean
url: string | null
hostKeys: ({
publicKey: string
fingerprint: string
})[]
}Errors
429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/ssh" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.ssh.info();branchscope.sshKeys
GET /v1/ssh-keys
Your SSH keys · branchscope.sshKeys.list()
Newest first.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
id: integer
title: string
type: string
fingerprint: string
scope: "read" | "write"
repos: string[] | null // null: all the owner's repositories.
createdAt: string (time)
lastUsedAt: string (time) | null
expiresAt: string (time) | null
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 401 unauthorized · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/ssh-keys" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.sshKeys.list();POST /v1/ssh-keys
Add an SSH key · branchscope.sshKeys.add()
Only from the web app, as for tokens. Each key belongs to one account.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
key: string // The public key, e.g. the contents of ~/.ssh/id_ed25519.pub.
title: string
scope?: "read" | "write"
repos?: string[] | null // null for all your repositories, or up to 100 owner/name.
expiresInDays?: integer | null
}Response 201
{
id: integer
title: string
type: string
fingerprint: string
scope: "read" | "write"
repos: string[] | null // null: all the owner's repositories.
createdAt: string (time)
lastUsedAt: string (time) | null
expiresAt: string (time) | null
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/ssh-keys" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"key":"key","title":"Add invoice export"}'await branchscope.sshKeys.add({ key: "key", title: "Add invoice export" });DELETE /v1/ssh-keys/{id}
Remove an SSH key · branchscope.sshKeys.remove()
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
idpath, required | integer |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/ssh-keys/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.sshKeys.remove({ id: 7 });branchscope.tokens
GET /v1/tokens
Your personal tokens · branchscope.tokens.list()
Newest first.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
cursorquery | string · From the previous page's nextCursor. |
limitquery | integer · Items per page, 1-100. |
Response 200
{
data: ({
id: integer
name: string
prefix: string // The token's first characters, to recognise it.
scope: "read" | "write"
repos: string[] | null // null: all the owner's repositories.
createdAt: string (time)
lastUsedAt: string (time) | null
expiresAt: string (time) | null
})[]
nextCursor: string | null // null on the last page.
}Errors
400 invalid_request · 401 unauthorized · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/tokens" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.tokens.list();POST /v1/tokens
Create a personal token · branchscope.tokens.create()
Only from the web app: a token can't make another token. The token itself is returned once.
Only from a signed-in person in the web app: a token can't do this.
OAuth apps can't call this.
Parameters
Idempotency-Keyheader | string · Any unique string. A retry with the same key gets the first response back instead of acting twice. |
Request body
{
name: string
scope?: "read" | "write"
repos?: string[] | null // null for all your repositories, or up to 100 owner/name.
expiresInDays?: integer | null
}Response 201
{
token: string // Shown once.
info: {
id: integer
name: string
prefix: string // The token's first characters, to recognise it.
scope: "read" | "write"
repos: string[] | null // null: all the owner's repositories.
createdAt: string (time)
lastUsedAt: string (time) | null
expiresAt: string (time) | null
}
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/tokens" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"web"}'await branchscope.tokens.create({ name: "web" });DELETE /v1/tokens/{id}
Revoke a token · branchscope.tokens.revoke()
Not with a read-only token.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
idpath, required | integer |
Response 200
{
revoked: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/tokens/7" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.tokens.revoke({ id: 7 });branchscope.users
GET /v1/users/{username}/avatar
A person's avatar · branchscope.users.getAvatar()
No credential needed; what you see depends on the one you send.
Parameters
usernamepath, required | string |
Response 200
image/png · The image (PNG, JPEG or WebP), served by Branchscope.Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/users/bob/avatar" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.users.getAvatar({ username: "bob" });GET /v1/users/{username}/connections/services
Service names across a person's own repositories · branchscope.users.connectionServices()
As for an organization, over the person's own repositories; the person sees the problems.
No credential needed; what you see depends on the one you send.
OAuth apps need the `graphs:read` scope.
Parameters
usernamepath, required | string |
Response 200
{
owner: string
repositories: ({
repo: string
branch: string // The default branch it was indexed from.
commit: string // The commit its connections come from: the newest default-branch head whose graph was built.
latest: {
commit: string // A commit ID (40 or 64 hex characters).
status: "queued" | "running" | "failed"
} | null // A newer build of the default branch's head that hasn't finished, or failed; null when the head is what's indexed.
})[] // The repositories it covers: those you can read that have been indexed, by name.
services: ({
name: string
servedBy: string[] // Repositories whose branchscope.json gives it a project. More than one is a duplicate claim; none, an unserved service.
calledBy: ({
repo: string
endpoints: integer // How many of its endpoints this repository calls.
})[] // Repositories calling its endpoints.
endpoints: integer // Endpoints its servers handle.
})[] // Every service served or called, by name.
problems: {
duplicates: ({
service: string
repos: string[]
})[] // Services more than one repository claims.
unserved: ({
service: string
calledBy: ({
repo: string
endpoints: integer // How many of its endpoints this repository calls.
})[]
})[] // Services clients call that no repository serves.
suggestions: ({
service: string
suggestion: string
servedBy: string[]
})[] // An unserved name close to a served one (billing and billing-api): a suggestion, never matched automatically.
} | null // For an organization's owners, or the person whose repositories these are; null for anyone else.
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/users/bob/connections/services" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.users.connectionServices({ username: "bob" });GET /v1/users/{username}/graph
A person's services and how they connect · branchscope.users.graph()
As for an organization, over the person's own repositories.
No credential needed; what you see depends on the one you send.
OAuth apps need the `graphs:read` scope.
Parameters
usernamepath, required | string |
viewquery | string · A saved view's slug. |
againstquery | string · An environment, e.g. production: compare with each repository's commit deployed there instead of its default branch. |
Response 200
{
schemaVersion: string // Semver (SPEC §8). The connections and owner-graph documents version on their own line from 0.5.
kind: "owner-graph"
owner: string // The organization or person whose repositories these are.
view: null | {
slug: string
name: string
} // The saved view drawn; null for All services.
kinds: GraphKind[] // The kinds of connection drawn, sorted.
against: string | null // The environment whose deployed commits were asked for as the other side (?against=); null for each repository's default branch.
environments: string[] // The environments at least one of the repositories joined has a current successful deployment to, sorted: what against can name.
repositories: PinnedRepository[] // The repositories drawn, sorted by repo: each one's indexed commit and how fresh it is.
nodes: OwnerNode[] // One per service, or per repository for code that serves none, sorted by id.
edges: OwnerEdge[] // Sorted by from, to, kind.
findings: {
unused: UnusedFinding[] // Sorted by id.
unserved: UnservedFinding[] // Sorted by id.
sharedTables: SharedTableFinding[] // Sorted by id.
cycles: CycleFinding[] // Sorted by their first node.
} // Across all the owner's repositories the reader can read; listed when they involve a repository drawn. Shown, never enforced.
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/users/bob/graph" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.users.graph({ username: "bob" });GET /v1/users/{username}/graph/views
The saved views of a person's graph · branchscope.users.listGraphViews()
By name. Anyone who can see the graph can open them; each lists only the repositories you can read.
No credential needed; what you see depends on the one you send.
OAuth apps need the `graphs:read` scope.
Parameters
usernamepath, required | string |
Response 200
{
data: ({
slug: string // Its link: /{owner}/graph?view={slug}.
name: string
repositories: string[] // Its repositories, owner/name, sorted: only those you can read.
kinds: ("http" | "messages" | "tables")[] | null // The kinds of connection it draws; null for every kind.
createdBy: string | null
createdAt: string (time)
updatedAt: string (time)
})[]
}Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/users/bob/graph/views" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.users.listGraphViews({ username: "bob" });POST /v1/users/{username}/graph/views
Save a view of a person's graph · branchscope.users.createGraphView()
The person only, as a person (never an agent or an app). Its repositories must be the owner's. Recorded in the audit log.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
usernamepath, required | string |
Request body
{
name: string
slug?: string // From the name if left out.
repositories: string[] // The owner's repositories, owner/name.
kinds?: ("http" | "messages" | "tables")[] | null // Left out or null: every kind.
}Response 201
{
slug: string // Its link: /{owner}/graph?view={slug}.
name: string
repositories: string[] // Its repositories, owner/name, sorted: only those you can read.
kinds: ("http" | "messages" | "tables")[] | null // The kinds of connection it draws; null for every kind.
createdBy: string | null
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X POST "$BRANCHSCOPE_API/v1/users/bob/graph/views" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"web","repositories":[]}'await branchscope.users.createGraphView({ username: "bob", name: "web", repositories: [] });PUT /v1/users/{username}/graph/views/{slug}
Rename or change a view of a person's graph · branchscope.users.updateGraphView()
The person only, as a person. Recorded in the audit log.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
usernamepath, required | string |
slugpath, required | string |
Request body
{
name?: string
slug?: string // From the name if left out.
repositories?: string[] // The owner's repositories, owner/name.
kinds?: ("http" | "messages" | "tables")[] | null // Left out or null: every kind.
}Response 200
{
slug: string // Its link: /{owner}/graph?view={slug}.
name: string
repositories: string[] // Its repositories, owner/name, sorted: only those you can read.
kinds: ("http" | "messages" | "tables")[] | null // The kinds of connection it draws; null for every kind.
createdBy: string | null
createdAt: string (time)
updatedAt: string (time)
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 409 conflict · 429 rate_limited · 500 internal
Example
curl -X PUT "$BRANCHSCOPE_API/v1/users/bob/graph/views/slug" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'await branchscope.users.updateGraphView({ username: "bob", slug: "slug" });DELETE /v1/users/{username}/graph/views/{slug}
Delete a view of a person's graph · branchscope.users.deleteGraphView()
The person only, as a person. Recorded in the audit log.
Needs a credential: a personal or agent token, or a web session.
OAuth apps can't call this.
Parameters
usernamepath, required | string |
slugpath, required | string |
Response 200
{
removed: true
}Errors
400 invalid_request · 401 unauthorized · 403 forbidden · 404 not_found · 429 rate_limited · 500 internal
Example
curl -X DELETE "$BRANCHSCOPE_API/v1/users/bob/graph/views/slug" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.users.deleteGraphView({ username: "bob", slug: "slug" });branchscope.workflows
GET /v1/workflow-schema.json
The JSON Schema of the workflow format · branchscope.workflows.schema()
For editors and agents writing .branchscope/workflows/*.yml.
No credential needed; what you see depends on the one you send.
Response 200
application/json · A JSON Schema (draft 2020-12).Errors
429 rate_limited · 500 internal
Example
curl "$BRANCHSCOPE_API/v1/workflow-schema.json" \
-H "Authorization: Bearer $BRANCHSCOPE_TOKEN"await branchscope.workflows.schema();
GET
/v1/repos/{owner}/{name}/changes/{number}/commentsA change's comments and status updates ·
branchscope.comments.list()Oldest first; a reply names the comment it answers (parentId).
No credential needed; what you see depends on the one you send.
OAuth apps need the `repo:read` scope.
Parameters
ownerpath, required
stringnamepath, required
stringnumberpath, required
integercursorquery
string· From the previous page's nextCursor.limitquery
integer· Items per page, 1-100.Response 200
Errors
400 invalid_request · 404 not_found · 429 rate_limited · 500 internal
Example