Documentation

Alpha

REST 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:

DialectBase URLUse it for
Nativehttps://api.upstream.codes/v1New integrations. camelCase JSON with cursor pagination.
GitHub-compatiblehttps://api.upstream.codes/api/v3Existing 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

CallerRequests per hourSearch requests per minute
Personal access token15,000120
No token, per IP address1,00020

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 the ETag from a previous response as If-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.

Reference