Documentation
AlphaProvenance
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
| Method | Path | Returns |
|---|---|---|
GET | /repos/{owner}/{repo}/pushes | Finished pushes, newest first |
GET | /repos/{owner}/{repo}/pushes/{push_id} | One push |
GET | /repos/{owner}/{repo}/commits/{sha}/provenance | The 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}/provenance | A Change Request's author, session and versions |
GET | /repos/{owner}/{repo}/sessions | Agent 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:
| Kind | Who acted | Look at |
|---|---|---|
user | A person, with their own credentials | account |
agent | An agent account, such as a Coline Code machine or Kairo | account for the agent, onBehalfOf for the person it worked for |
app | An app's bot, with an installation token | account for the bot, app for the app |
deploy | A deploy key | deployKey.name |
server | Upstream, carrying out a merge | onBehalfOf for the person who merged |
external | Someone 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:
| Field | Holds |
|---|---|
author | The actor who opened it |
workOwner | The person responsible for the work |
mergedBy, mergedCommitSha, mergedAt | Who merged it, and the result |
session | The Code session that produced it, or null |
versions | The 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}`);
}
});