Documentation
AlphaREST API
Call Upstream from scripts, CI and other services with a personal access token.
The Upstream API is in alpha. Endpoints, fields and limits can change before it's stable. Send feedback to support@coline.app.
The Upstream REST API covers repositories, code, Change Requests, checks, Issues, releases, Actions, notifications and organizations.
It is served in two dialects from the same endpoints:
| Dialect | Base URL | Use it for |
|---|---|---|
| Native | https://api.upstream.codes/v1 | New integrations. camelCase JSON with cursor pagination. |
| GitHub-compatible | https://api.upstream.codes/api/v3 | Existing GitHub tooling. Point Octokit or similar clients at it. See GitHub compatibility. |
Your first request
Create a token in Tokens settings, then:
curl -H "Authorization: Bearer $UPSTREAM_TOKEN" \
https://api.upstream.codes/v1/user
Public data needs no token:
curl https://api.upstream.codes/v1/repos/{owner}/{repository}
Responses
Successful native responses wrap the result in data:
{ "data": { "fullName": "acme/api", "defaultBranch": "main" } }
Errors use a matching HTTP status and a stable code:
{
"error": {
"code": "TOKEN_SCOPE_REQUIRED",
"message": "This token requires issues:write.",
"docs": "https://upstream.codes/docs/api"
}
}
Branch on code and the status, not on message. Repositories and other objects you can't see return 404, never 403, so their existence doesn't leak.
Pagination
List endpoints return up to perPage items (default 30, maximum 100) and a cursor for the next page:
{ "data": [ ... ], "page": { "nextCursor": "b2Zmc2V0OjMw" } }
Pass it back as ?cursor=. The same link is in the Link header with rel="next". A null cursor means there are no more pages.
Rate limits
| Caller | Requests per hour | Search requests per minute |
|---|---|---|
| Personal access token | 15,000 | 120 |
| No token, per IP address | 1,000 | 20 |
All of a person's tokens share one allowance. Every response reports where you stand:
X-RateLimit-Limit: 15000
X-RateLimit-Remaining: 14872
X-RateLimit-Reset: 1791570000
X-RateLimit-Used: 128
X-RateLimit-Resource: core
X-RateLimit-Reset is a Unix timestamp in seconds. These requests don't count toward the limit:
- Conditional requests that return
304 Not Modified. Send theETagfrom a previous response asIf-None-Match. GET /rate_limit.- Files and trees requested by full commit SHA from public repositories, which are served from the edge.
When the allowance runs out, the API returns 429 with Retry-After (the GitHub-compatible dialect returns 403, as GitHub does). Creating comments, Issues and other content also has short per-minute caps to stop spam.
Conditional requests
Every GET response carries an ETag. Poll with it and an unchanged resource costs nothing:
curl -H "Authorization: Bearer $UPSTREAM_TOKEN" \
-H 'If-None-Match: W/"2b1f…"' \
https://api.upstream.codes/v1/repos/acme/api/changes
Batch reads
POST /v1/batch runs up to 50 GET requests in one call and counts as one request for every 10 it contains:
curl -X POST -H "Authorization: Bearer $UPSTREAM_TOKEN" \
-H "Content-Type: application/json" \
-d '{"requests":[{"method":"GET","path":"/repos/acme/api"},{"method":"GET","path":"/repos/acme/api/changes?perPage=5"}]}' \
https://api.upstream.codes/v1/batch
Each result has its own status, headers and body. One failure doesn't fail the batch. A batch returns at most 20 MB in total.
TypeScript SDK
@colineapp/upstream-sdk is a typed client generated from the native OpenAPI document. It unwraps data, follows cursors, retries rate-limited requests after Retry-After, and can cache responses so repeat reads are free 304s.
The SDK isn't on npm yet. Until it is, generate a client from the native OpenAPI document, or point Octokit at the GitHub-compatible API. Once published, it works like this:
import { createUpstreamClient } from "@colineapp/upstream-sdk";
const upstream = createUpstreamClient({ token: process.env.UPSTREAM_TOKEN });
for await (const change of upstream.paginate("/repos/{owner}/{repo}/changes", {
params: { owner: "acme", repo: "api" },
})) {
console.log(change.number, change.title);
}
Events
Every repository has an ordered feed of what happened in it. Read it from a cursor and you never miss an event, even after downtime:
curl -H "Authorization: Bearer $UPSTREAM_TOKEN" \
"https://api.upstream.codes/v1/repos/acme/api/events?after=$CURSOR"
Store page.nextCursor and pass it back as after. When you're caught up the cursor stays the same, so poll with If-None-Match and an unchanged feed costs nothing. Organizations have a feed at /v1/orgs/{org}/events. Webhooks deliver the same events as they happen; add them in repository or organization settings, or through /repos/{owner}/{repo}/hooks.
Alpha
The API is in alpha. Endpoints, fields and limits can change before it's stable. GraphQL isn't available yet, so gh commands other than gh api don't work. See GitHub compatibility for other differences.
Send bugs, missing endpoints and anything that doesn't match these docs to support@coline.app.