Documentation

Alpha

Provenance

See which person, agent, app or deploy key made each push and Change Request in a repository.

The Upstream API is in alpha. Endpoints, fields and limits can change before it's stable. Send feedback to support@coline.app.

Provenance tells you who made a change. For every push, Upstream records the credential behind it: a person, an agent working for a person, an app's bot, a deploy key, or Upstream itself carrying out a merge. For Change Requests it records the author, the person an agent worked for, the Code session that produced the work and who made each version.

Commit author fields are whatever the committer typed. Provenance comes from the credential Upstream checked when it took the push.

Access

Every endpoint needs the provenance:read scope. Personal access tokens, sign-in tokens, CLI sessions and installation tokens all work. An app needs the Provenance permission, or "provenance": "read" in its manifest.

The token must also be able to read the repository, and an installation token must cover it. Requests without a token get 401, even for public repositories. A token without the scope gets 403 TOKEN_SCOPE_REQUIRED, and one that can't read the repository gets 404.

Endpoints

MethodPathReturns
GET/repos/{owner}/{repo}/pushesFinished pushes, newest first
GET/repos/{owner}/{repo}/pushes/{push_id}One push
GET/repos/{owner}/{repo}/commits/{sha}/provenanceThe pushes that moved a ref to this commit, and the Change Requests whose version head or merge commit it is
GET/repos/{owner}/{repo}/changes/{number}/provenanceA Change Request's author, session and versions
GET/repos/{owner}/{repo}/sessionsAgent Code sessions that opened Change Requests in the repository
GET/repos/{owner}/{repo}/sessions/{session_id}One session with its runs

All six work in both dialects. Under /v1 they use camelCase and UUIDs. Under /api/v3 they use snake_case and numeric IDs, call Change Requests pull_requests, and the Change Request path is /repos/{owner}/{repo}/pulls/{number}/provenance.

Filter pushes with ref, actor_kind and sha. ref takes a branch or tag name like main, or a full name like refs/tags/v1.2.0. sha finds pushes that moved a ref to that commit. Lists page like the rest of the API.

Who pushed

curl -H "Authorization: Bearer $UPSTREAM_TOKEN" \
  "https://api.upstream.codes/v1/repos/acme/api/pushes?ref=main&actor_kind=app"
{
  "data": [
    {
      "id": "0b6c2f9e-…",
      "status": "succeeded",
      "actor": {
        "kind": "app",
        "account": {
          "id": "…",
          "login": "acme-ci-bot",
          "name": "Acme CI",
          "avatarUrl": "…",
          "htmlUrl": "https://upstream.codes/apps/acme-ci",
          "type": "Bot"
        },
        "onBehalfOf": null,
        "app": { "id": "8a1f2c3d-…", "slug": "acme-ci", "name": "Acme CI", "htmlUrl": "https://upstream.codes/apps/acme-ci" },
        "deployKey": null
      },
      "refs": [
        { "ref": "refs/heads/main", "before": "9fceb02…", "after": "e5bd391…", "action": "update", "landed": true }
      ],
      "refCount": 1,
      "refsTruncated": false,
      "createdAt": "2026-11-09T16:42:10.000Z",
      "completedAt": "2026-11-09T16:42:11.000Z"
    }
  ],
  "page": { "nextCursor": null }
}

actor.kind is one of these:

KindWho actedLook at
userA person, with their own credentialsaccount
agentAn agent account, such as a Coline Code machine or Kairoaccount for the agent, onBehalfOf for the person it worked for
appAn app's bot, with an installation tokenaccount for the bot, app for the app
deployA deploy keydeployKey.name
serverUpstream, carrying out a mergeonBehalfOf for the person who merged
externalSomeone outside Upstream, such as the GitHub author of an imported Change Request. Only on Change Requests.account

People who hide their profile show as null.

A push's status is succeeded, failed, rejected or pending. The list shows only finished pushes, succeeded and failed. Each ref has landed, which says whether the ref ended where the push asked, or null while the push is unfinished. A push lists up to 100 refs. refCount has the total and refsTruncated says when some are missing.

Who made a commit

curl -H "Authorization: Bearer $UPSTREAM_TOKEN" \
  https://api.upstream.codes/v1/repos/acme/api/commits/e5bd3914e2e596debea16f433f57875b5b90bcd6/provenance

The answer has sha, pushes and changes. pushes holds up to 50 pushes that moved a ref to exactly this commit and landed. changes holds up to 20 Change Requests with a version whose head is this commit, or that merged as this commit.

With Octokit, in the GitHub-compatible dialect:

const { data } = await octokit.request("GET /repos/{owner}/{repo}/commits/{sha}/provenance", {
  owner: "acme",
  repo: "api",
  sha: "e5bd3914e2e596debea16f433f57875b5b90bcd6",
});
for (const push of data.pushes) console.log(push.actor.kind, push.actor.account?.login);
for (const pull of data.pull_requests) console.log(pull.number, pull.author.kind);

Who made a Change Request

GET /repos/{owner}/{repo}/changes/{number}/provenance returns:

FieldHolds
authorThe actor who opened it
workOwnerThe person responsible for the work
mergedBy, mergedCommitSha, mergedAtWho merged it, and the result
sessionThe Code session that produced it, or null
versionsThe last 100 versions, each with headSha, baseSha, createdBy and summary

Agent sessions

GET /repos/{owner}/{repo}/sessions lists the Coline Code sessions that opened Change Requests in the repository. Each has its title, objective, status, source, agent, requestedBy, model, branch and the Change Request numbers it produced. GET /repos/{owner}/{repo}/sessions/{session_id} adds runs, each with its kind, status, model and times.

This is metadata only. Transcripts, prompts beyond the title and objective, and machine details are never included. Sessions from a fork's workspace stay private to that workspace.

In webhooks

Push webhooks carry the push's provenance, shaped like GET /pushes/{push_id}. It's upstream_provenance in the GitHub format and provenance in the Upstream format. Repository and organization webhooks always get it. App installations get it when they hold provenance:read.

app.webhooks.on("push", ({ payload }) => {
  const actor = payload.upstream_provenance?.actor;
  if (actor?.kind === "agent") {
    console.log(`${actor.account?.login} pushed for ${actor.on_behalf_of?.login}`);
  }
});