Skip to content
KeylayerDocs
Docs menu
PricingGet started

Use GitHub with a scoped key and a request cap

Set Octokit's baseUrl to https://relay.keylayer.dev and its auth to a Keylayer key: the key gets request caps, and 36 routes that mint credentials or grant access are blocked. Keylayer forwards each call to api.github.com with your real token, a GitHub key can't have a spend cap, and a request can run for up to 120 seconds.

Set up

Add a fine-grained personal access token as a credential, limited to the repositories and permissions the key needs and with an expiry. Then mint a key as in the quickstart and put it in an environment variable:

Shell
export KEYLAYER_KEY=kl_k_YOUR_KEY
Node.js (octokit)
import { Octokit } from "octokit";

const octokit = new Octokit({
  baseUrl: "https://relay.keylayer.dev",
  auth: process.env.KEYLAYER_KEY,
});

const { data: issues } = await octokit.rest.issues.listForRepo({
  owner: "octocat",
  repo: "Hello-World",
  state: "open",
  per_page: 5,
});
console.log(issues.map((issue) => issue.title));
Python (requests)
import os

import requests

response = requests.get(
    "https://relay.keylayer.dev/repos/octocat/Hello-World/issues",
    params={"state": "open", "per_page": 5},
    headers={
        "Authorization": f"Bearer {os.environ['KEYLAYER_KEY']}",
        "Accept": "application/vnd.github+json",
    },
    timeout=30,
)
response.raise_for_status()
print([issue["title"] for issue in response.json()])
curl
curl "https://relay.keylayer.dev/repos/octocat/Hello-World/issues?state=open&per_page=5" \
  -H "Authorization: Bearer $KEYLAYER_KEY" \
  -H "Accept: application/vnd.github+json"

The relay takes the key with the Bearer or the token scheme, so Octokit's default works. Keylayer rewrites api.github.com links in the Link header to the relay, so pagination stays on the relay, and octokit.graphql posts to /graphql on the relay.

Release assets go to uploads.github.com, which the relay serves under /-/uploads. Octokit's uploadReleaseAsset brings its own base URL, so pass the relay's on the call:

Node.js (octokit), release asset
import { readFile } from "node:fs/promises";

import { Octokit } from "octokit";

const octokit = new Octokit({
  baseUrl: "https://relay.keylayer.dev",
  auth: process.env.KEYLAYER_KEY,
});

const { data: asset } = await octokit.rest.repos.uploadReleaseAsset({
  baseUrl: "https://relay.keylayer.dev/-/uploads",
  owner: "octocat",
  repo: "Hello-World",
  release_id: 1,
  name: "build.zip",
  data: await readFile("build.zip"),
  headers: { "content-type": "application/zip" },
});
console.log(asset.state);

Keylayer doesn't rewrite URLs inside response bodies, so the upload_url in a release's JSON still points at uploads.github.com. Build the /-/uploads URL yourself instead.

Request caps on GitHub

GitHub calls have no price, so a GitHub key can't have a spend cap and minting one with a spend cap is refused. Limit it with Requests per hour, day, week or month, Requests a minute and Requests at once; see request caps and rate.

A personal access token shares GitHub's limit of 5,000 REST requests per hour with every other token and app acting as you. A request cap well under that keeps one agent from using up the hour for your other tools. GitHub also allows no more than 100 concurrent requests and 80 content-generating requests a minute, so Requests at once and Requests a minute set below those keep an agent clear of GitHub's secondary limits.

Blocked routes

Keylayer refuses 36 REST routes with 403 path_not_allowed, because they mint credentials or grant access that outlives the key:

Group Routes
SSH, GPG and signing keys /user/keys, /user/gpg_keys, /user/ssh_signing_keys and deploy keys at /repos/{owner}/{repo}/keys
Self-hosted runners registration-token, remove-token and generate-jitconfig for repositories, organizations and enterprises
Secrets Actions secrets for repositories, environments and organizations; Dependabot and Codespaces secrets
Tokens Organization personal access tokens and token requests, app installation access tokens, /copilot_internal and Actions OIDC settings
Access Repository collaborators, organization invitations and memberships, team memberships and team repository grants
Webhooks Repository and organization hooks

The list is a backstop, not every admin route: transferring or deleting a repository, making a private one public, or assigning an organization role at /orgs/{org}/organization-roles goes through when the token allows it, so the token's permissions are the real limit. The list covers REST paths only. GraphQL goes to /graphql, which isn't blocked, so Keylayer forwards a mutation such as updateTeamsRepository. If the key doesn't need GraphQL, set its Only paths to the REST routes it uses; see methods and paths. Escape hatches lift the REST block for one key; see admin routes and escape hatches.

Errors

A Keylayer refusal has a keylayer-error header and a GitHub-shaped body:

403 path_not_allowed
{"message": "Keylayer path_not_allowed: this key does not allow this path.", "status": "403"}

Octokit throws RequestError, which keeps the response headers:

Node.js (octokit)
import { Octokit, RequestError } from "octokit";

const octokit = new Octokit({
  baseUrl: "https://relay.keylayer.dev",
  auth: process.env.KEYLAYER_KEY,
});

try {
  await octokit.rest.repos.get({ owner: "octocat", repo: "Hello-World" });
} catch (error) {
  if (!(error instanceof RequestError)) {
    throw error;
  }
  const code = error.response?.headers["keylayer-error"];
  if (!code) {
    throw error;
  }
  console.log(`Keylayer refused the request: ${error.status} ${code}`);
}

Octokit's retry plugin retries a 429 up to 3 times, after 1, 4 and 9 seconds, without reading retry-after, so a refusal from a used-up request cap takes about 14 seconds to reach your code. The error reference lists every code.

What doesn't work

  • The gh CLI. With GH_HOST set to the relay, gh calls /api/v3/ paths, which GitHub answers with 404. Use Octokit, curl or another client with a base URL setting.
  • Git over HTTPS. Clone and push go to github.com, and Keylayer relays only api.github.com and uploads.github.com.
  • Spend caps, as above.

Next steps

Updated

Create your first scoped key

Add a provider key once, then mint Keylayer keys with their own limits for each app, agent or teammate.

Get started