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

audience
query
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

owner
path, required
string
name
path, required
string
cursor
query
string · From the previous page's nextCursor.
limit
query
integer · Items per page, 1-100.
change
query
integer · Only this change's runs.
trigger
query
"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

owner
path, required
string
name
path, required
string
Idempotency-Key
header
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

owner
path, required
string
name
path, required
string
id
path, 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

owner
path, required
string
name
path, required
string
id
path, 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

owner
path, required
string
name
path, required
string
id
path, required
integer
artifact
path, 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

owner
path, required
string
name
path, required
string
id
path, 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

owner
path, required
string
name
path, required
string
id
path, required
integer
job
path, required
integer
after
query
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

owner
path, required
string
name
path, required
string
id
path, required
integer
Idempotency-Key
header
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

owner
path, required
string
name
path, required
string
id
path, 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

owner
path, required
string
name
path, required
string
id
path, 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

repo
query
string · owner/name
actor
query
string · The person (a username) who pushed, merged or used the agent.
agent
query
string · The agent's name.
since
query
string (time) · At or after this time (ISO 8601).
until
query
string (time) · Before this time.
affiliation
query
"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).
ref
query
string · Only pushes and merges that moved this branch (its name), or this full ref (refs/tags/v1).
cursor
query
string · From the previous page's nextCursor.
limit
query
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

repo
query
string · owner/name
actor
query
string · The person (a username) who pushed, merged or used the agent.
agent
query
string · The agent's name.
since
query
string (time) · At or after this time (ISO 8601).
until
query
string (time) · Before this time.
affiliation
query
"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).
cursor
query
string · From the previous page's nextCursor.
limit
query
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

namespace
path, 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

namespace
path, 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

id
path, 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

id
path, 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

cursor
query
string · From the previous page's nextCursor.
limit
query
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-Key
header
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

slug
path, 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

slug
path, 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

slug
path, 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

slug
path, 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

slug
path, 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

slug
path, 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

slug
path, required
string
Idempotency-Key
header
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

slug
path, 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

GET /v1/authorizations

The apps you've authorized to act for you · branchscope.authorizations.list()

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: ({
    app: AppPublic
    agent: string | null  // For the Branchscope CLI: the agent this authorization signs in.
    scopes: string[]
    repos: string[] | null  // null: all their repositories.
    createdAt: string (time)
    lastUsedAt: string (time) | null
  })[]
  nextCursor: null
}

Errors

401 unauthorized · 429 rate_limited · 500 internal

Example

curl "$BRANCHSCOPE_API/v1/authorizations" \
  -H "Authorization: Bearer $BRANCHSCOPE_TOKEN"
await branchscope.authorizations.list();

DELETE /v1/authorizations/{slug}

Revoke an app's authorization · branchscope.authorizations.revoke()

All its tokens for you stop working at once. For the Branchscope CLI, name the agent.

Only from a signed-in person in the web app: a token can't do this.

OAuth apps can't call this.

Parameters

slug
path, required
string
agent
query
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/authorizations/slug" \
  -H "Authorization: Bearer $BRANCHSCOPE_TOKEN"
await branchscope.authorizations.revoke({ slug: "slug" });

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

cursor
query
string · From the previous page's nextCursor.
limit
query
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

id
path, 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

id
path, required
integer
cursor
query
string · From the previous page's nextCursor.
limit
query
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

id
path, 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

id
path, required
integer
message
path, 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

id
path, required
integer
message
path, 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

id
path, required
integer
message
path, 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

id
path, 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

id
path, 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

username
path, 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

username
path, 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

q
query
string · A username or name, or part of one. Without it, no one.
limit
query
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

cursor
query
string · From the previous page's nextCursor.
limit
query
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-Key
header
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

id
path, 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

id
path, 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

id
path, 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

id
path, 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-Key
header
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

id
path, 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

owner
path, required
string
name
path, 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

provider
path, 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

provider
path, 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

org
path, 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

org
path, required
string
app
path, required
string
provider
path, 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

org
path, required
string
app
path, required
string
provider
path, 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

owner
path, required
string
name
path, 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

owner
path, required
string
name
path, required
string
app
path, required
string
provider
path, 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

owner
path, required
string
name
path, required
string
app
path, required
string
provider
path, 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

id
path, 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

id
path, 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

id
path, 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

id
path, required
integer
cursor
query
string · From the previous page's nextCursor.
limit
query
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

id
path, required
integer
delivery
path, 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

id
path, 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

org
path, 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

org
path, 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

owner
path, required
string
name
path, 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

owner
path, required
string
name
path, 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

action
query
string · Only this action, or those starting with it: secret for secret.set and secret.deleted.
cursor
query
string · From the previous page's nextCursor.
limit
query
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

org
path, required
string
action
query
string · Only this action, or those starting with it: secret for secret.set and secret.deleted.
cursor
query
string · From the previous page's nextCursor.
limit
query
integer · Items per page, 1-100.
repositories
query
"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

owner
path, required
string
name
path, required
string
action
query
string · Only this action, or those starting with it: secret for secret.set and secret.deleted.
cursor
query
string · From the previous page's nextCursor.
limit
query
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();

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

cursor
query
string · From the previous page's nextCursor.
limit
query
integer · Items per page, 1-100.
unread
query
"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

id
path, 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

id
path, 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

id
path, 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

name
path, 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

name
path, required
string
cursor
query
string · From the previous page's nextCursor.
limit
query
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-Key
header
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

org
path, 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

org
path, 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

org
path, 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

org
path, 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

org
path, 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

org
path, required
string
Idempotency-Key
header
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

org
path, 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

org
path, required
string
view
query
string · A saved view's slug.
against
query
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

org
path, 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

org
path, 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

org
path, required
string
slug
path, 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

org
path, required
string
slug
path, 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

org
path, required
string
cursor
query
string · From the previous page's nextCursor.
limit
query
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

org
path, required
string
Idempotency-Key
header
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

org
path, required
string
username
path, 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

org
path, 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

org
path, 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

owner
path, required
string
name
path, 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

org
path, required
string
cursor
query
string · From the previous page's nextCursor.
limit
query
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

org
path, required
string
Idempotency-Key
header
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

org
path, required
string
slug
path, 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

org
path, required
string
slug
path, 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

org
path, 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

org
path, 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

org
path, 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

org
path, 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

org
path, 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

org
path, 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

org
path, 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

org
path, 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

org
path, 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

org
path, 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

org
path, required
string
id
path, 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

org
path, 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

org
path, 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

org
path, required
string
secret
path, 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

org
path, required
string
secret
path, 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

owner
path, required
string
name
path, required
string
environment
path, 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

owner
path, required
string
name
path, required
string
environment
path, required
string
secret
path, 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

owner
path, required
string
name
path, required
string
environment
path, required
string
secret
path, 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

owner
path, required
string
name
path, 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

owner
path, required
string
name
path, required
string
secret
path, 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

owner
path, required
string
name
path, required
string
secret
path, 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

org
path, 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

org
path, 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

org
path, 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

org
path, 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

org
path, required
string
domain
path, 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

org
path, required
string
domain
path, 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

org
path, required
string
cursor
query
string · From the previous page's nextCursor.
limit
query
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

org
path, required
string
Idempotency-Key
header
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

owner
path, required
string
name
path, required
string
cursor
query
string · From the previous page's nextCursor.
limit
query
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

owner
path, required
string
name
path, required
string
Idempotency-Key
header
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

id
path, 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

id
path, 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

id
path, 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

id
path, required
integer
cursor
query
string · From the previous page's nextCursor.
limit
query
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

id
path, required
integer
delivery
path, 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

id
path, 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

id
path, required
integer
Idempotency-Key
header
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

cursor
query
string · From the previous page's nextCursor.
limit
query
integer · Items per page, 1-100.
affiliation
query
"all" | "mine" | "following"
topic
query
string · Only repositories with this topic.
owner
query
string · Only this person's or organization's repositories.
q
query
string · Only those with this in their name or description.
sort
query
"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-Key
header
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

owner
path, required
string
name
path, 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

owner
path, required
string
name
path, 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

owner
path, required
string
name
path, 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

owner
path, required
string
name
path, 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

owner
path, required
string
name
path, 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

owner
path, required
string
name
path, required
string
ref
query
string · A branch, tag or commit; the default branch if left out.
path
query, 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

owner
path, required
string
name
path, required
string
rev
query
string
path
query, 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

owner
path, required
string
name
path, required
string
cursor
query
string · From the previous page's nextCursor.
limit
query
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

owner
path, required
string
name
path, required
string
username
path, 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

owner
path, required
string
name
path, required
string
username
path, 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

owner
path, required
string
name
path, required
string
rev
query
string · A branch, tag or commit; the default branch if left out.
path
query
string · Only commits that changed this file (following renames) or anything in this folder.
cursor
query
string · From the previous page's nextCursor.
limit
query
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

owner
path, required
string
name
path, required
string
sha
path, 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

owner
path, required
string
name
path, required
string
sha
path, 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

owner
path, required
string
name
path, required
string
Idempotency-Key
header
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

owner
path, required
string
name
path, 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

owner
path, required
string
name
path, 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

owner
path, required
string
name
path, 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

owner
path, required
string
name
path, 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

owner
path, required
string
name
path, required
string
rev
query
string
path
query
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

owner
path, required
string
name
path, 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

owner
path, required
string
name
path, required
string
branch
query
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

owner
path, required
string
name
path, required
string
tab
query
"yours" | "active" | "stale" | "all" · yours: branches you've pushed to; active: pushed in the last 90 days; stale: not since.
q
query
string · Only branches whose name contains this (any case).
cursor
query
string · From the previous page's nextCursor.
limit
query
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

owner
path, required
string
name
path, required
string
Idempotency-Key
header
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

owner
path, required
string
name
path, required
string
branch
path, 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

owner
path, required
string
name
path, required
string
branch
path, required
string · URL-encoded: feature%2Fx for feature/x.
Idempotency-Key
header
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

owner
path, required
string
name
path, required
string
branch
path, required
string · URL-encoded: feature%2Fx for feature/x.
Idempotency-Key
header
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

owner
path, required
string
name
path, required
string
status
query
"open" | "merged" | "closed"
author
query
string · Who opened it (a username).
agent
query
string · The agent that opened it.
cursor
query
string · From the previous page's nextCursor.
limit
query
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

owner
path, required
string
name
path, required
string
Idempotency-Key
header
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

owner
path, required
string
name
path, required
string
number
path, 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

owner
path, required
string
name
path, required
string
number
path, 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

owner
path, required
string
name
path, required
string
number
path, 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

owner
path, required
string
name
path, required
string
number
path, required
integer
against
query
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

owner
path, required
string
name
path, required
string
number
path, 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

owner
path, required
string
name
path, required
string
number
path, 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

owner
path, required
string
name
path, required
string
number
path, 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

owner
path, required
string
name
path, required
string
number
path, required
integer
identifier
path, 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

owner
path, required
string
name
path, required
string
number
path, required
integer
Idempotency-Key
header
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

owner
path, required
string
name
path, required
string
number
path, 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

owner
path, required
string
name
path, required
string
number
path, required
integer
Idempotency-Key
header
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

owner
path, required
string
name
path, required
string
number
path, 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

owner
path, required
string
name
path, required
string
number
path, 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

owner
path, required
string
name
path, required
string
number
path, 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

owner
path, required
string
name
path, required
string
sha
path, 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

owner
path, required
string
name
path, required
string
sha
path, required
string
Idempotency-Key
header
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

GET /v1/repos/{owner}/{name}/changes/{number}/comments

A 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

owner
path, required
string
name
path, required
string
number
path, required
integer
cursor
query
string · From the previous page's nextCursor.
limit
query
integer · Items per page, 1-100.

Response 200

{
  data: ({
    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
  })[]
  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/12/comments" \
  -H "Authorization: Bearer $BRANCHSCOPE_TOKEN"
await branchscope.comments.list({ owner: "acme", name: "web", number: 12 });

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

owner
path, required
string
name
path, required
string
number
path, required
integer
Idempotency-Key
header
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

owner
path, required
string
name
path, required
string
number
path, required
integer
id
path, 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

owner
path, required
string
name
path, required
string
number
path, required
integer
id
path, 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

owner
path, required
string
name
path, required
string
range
path, 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

owner
path, required
string
name
path, required
string
cursor
query
string · From the previous page's nextCursor.
limit
query
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

owner
path, required
string
name
path, required
string
Idempotency-Key
header
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

owner
path, required
string
name
path, required
string
tag
path, 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

owner
path, required
string
name
path, required
string
tag
path, required
string · URL-encoded: v1%2Frc for v1/rc.
Idempotency-Key
header
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

owner
path, required
string
name
path, required
string
tag
path, 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

owner
path, required
string
name
path, required
string
tag
path, 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

owner
path, required
string
name
path, 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

owner
path, required
string
name
path, required
string
cursor
query
string · From the previous page's nextCursor.
limit
query
integer · Items per page, 1-100.
environment
query
string · Only this environment's.
sha
query
string · Only this commit's.
tag
query
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

owner
path, required
string
name
path, required
string
Idempotency-Key
header
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

owner
path, required
string
name
path, required
string
id
path, 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

owner
path, required
string
name
path, required
string
id
path, required
integer
Idempotency-Key
header
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

owner
path, required
string
name
path, required
string
id
path, 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

owner
path, required
string
name
path, required
string
id
path, 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

owner
path, required
string
name
path, 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

owner
path, required
string
name
path, required
string
environment
path, 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

owner
path, required
string
name
path, required
string
environment
path, required
string
Idempotency-Key
header
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

owner
path, required
string
name
path, required
string
ref
query, 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

owner
path, required
string
name
path, required
string
cursor
query
string · From the previous page's nextCursor.
limit
query
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

owner
path, required
string
name
path, 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

owner
path, required
string
name
path, required
string
sha
path, required
string

Response 200

GraphDocument

Errors

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

owner
path, required
string
name
path, 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

owner
path, required
string
name
path, 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

owner
path, required
string
name
path, required
string
id
path, 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

owner
path, required
string
name
path, required
string
id
path, 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

owner
path, required
string
name
path, required
string
id
path, 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

owner
path, required
string
name
path, required
string
id
path, 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

owner
path, required
string
name
path, required
string
kind
query
"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

owner
path, required
string
name
path, required
string
Idempotency-Key
header
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

owner
path, required
string
name
path, required
string
id
path, 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

owner
path, required
string
name
path, required
string
id
path, required
integer
Idempotency-Key
header
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

owner
path, required
string
name
path, required
string
id
path, 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

owner
path, required
string
name
path, required
string
id
path, 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

owner
path, required
string
name
path, required
string
Idempotency-Key
header
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

owner
path, required
string
name
path, required
string
cursor
query
string · From the previous page's nextCursor.
limit
query
integer · Items per page, 1-100.
status
query
"open" | "all" · open: queued, working or blocked.
change
query
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

owner
path, required
string
name
path, required
string
Idempotency-Key
header
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

cursor
query
string · From the previous page's nextCursor.
limit
query
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

id
path, 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

id
path, 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

id
path, 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

owner
path, required
string
name
path, required
string
view
path, required
"architecture" | "data-flow"
ref
query, required
string
project
query
string · Drill into one project.

Response 200

ViewDocument

Errors

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

cursor
query
string · From the previous page's nextCursor.
limit
query
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-Key
header
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

id
path, 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

cursor
query
string · From the previous page's nextCursor.
limit
query
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-Key
header
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

id
path, 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

username
path, 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

username
path, 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

username
path, required
string
view
query
string · A saved view's slug.
against
query
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

username
path, 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

username
path, 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

username
path, required
string
slug
path, 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

username
path, required
string
slug
path, 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();