Skip to main content
Every HTTP request and every Git request carries a JSON Web Token (JWT). Your private key signs the token with ES256, ES384, ES512, or RS256. Code Storage checks the signature with the public key that you registered. The private key never leaves your systems.

Create a key

A key is a key pair that you register with your organization. You keep the private key and use it to sign tokens. Code Storage stores only the public key. Open the Keys page in your organization dashboard and select Create key. New keys are unrestricted by default. To create a restricted key, enable Limit scope for this key. Deselect the scopes that the key does not need. If you leave every scope selected, the dashboard saves an unrestricted key. Your browser creates an ECDSA P-256 key pair and registers the public key. Copy the private key and your organization name.
Copy the private key now. We cannot show it again. Anyone with the private key can sign tokens for your organization. Never commit it.

Organization name

The organization name is the token’s iss claim, which Code Storage uses to find your registered public keys. It is usually also the subdomain of the API host api.your-org.code.storage and the Git host your-org.code.storage. The Settings page in the dashboard shows the exact API base URL and Git host for your organization.

Store the private key

Store the private key in PKCS8 PEM format on your server or in a secret manager. The SDK clients and the examples on this page accept this format. Set PIERRE_PRIVATE_KEY to the PEM contents in your local environment or secret manager. The examples read the key from this variable. Set PIERRE_ORG to your organization name:

Token claims

Clients send the token with requests. They do not send the private key. The token header sets alg to the signing algorithm. It can also set kid to the ID of the registered key. When kid matches a registered key, Code Storage checks the signature with that key only. Otherwise, Code Storage tries each registered key of the organization. A decoded token has a header and a set of claims. For example, this token reads and writes team/project-alpha:
The token claims set its identity, access, and lifetime: Set exp on every token. Use the shortest practical lifetime and only the scopes that the operation needs. A token can also carry ref policies that limit Git writes.

Scopes

The scopes claim sets the operations that a token can perform. Each scope name must match exactly. One scope does not include another. git:write does not include read access. Add git:read when a client must clone or fetch before it pushes. Tag deletion needs both git:read and git:write.

Repository claim

The repo claim names the one repository that a token covers. The rules depend on the scopes:
  • Set repo when the token has git:read, git:write, or repo:write. Code Storage rejects such a token without repo.
  • repo is optional when the only scope is org:read.
When a path contains {repo_name}, the path value must equal the repo claim exactly. Otherwise the request returns 404 with the code repository_not_found, the same response as for a repository that does not exist. So one repository-scoped token covers one repository. Percent-encode a / in the path value, for example team%2Fproject-alpha. POST /api/repos takes the name of the new repository from the repo claim. The optional body field repo_name must equal the claim. Otherwise the request returns 400 with the code repo_name_mismatch. For example, this token lists the branches of team/project-alpha with GET /api/repos/team%2Fproject-alpha/branches:
This token lists the repositories of the organization with GET /api/repos. It has no repo claim:

Restricted keys

A restricted key has a scope allowlist, allowed_scopes. Its tokens can request only scopes from that allowlist. A token does not get the allowed scopes automatically: its scopes claim must still list each scope that the operation needs. Tokens signed with a restricted key must meet these requirements:
  • Include both iat and exp as Unix timestamps in seconds.
  • Set iat to the issue time.
  • Set exp after iat, at most 3,600 seconds later.
  • Include only scopes from the key’s allowlist.
For example, a key that allows git:read and org:read can sign a token with only git:read. A token that also includes git:write fails verification, even if the request needs only read access. An unrestricted key has the allowlist ["*"] and no one-hour token lifetime limit. Its tokens must still list valid scopes. * is not a valid token scope.

How to sign a JWT

The SDK signs and sends JWTs for you. These examples show how to sign an ES256 token for organization reads without using the SDK. They use a PKCS8 PEM private key from the dashboard and expire after one hour. If you use a restricted key, its allowlist must include org:read. The examples read the organization name from PIERRE_ORG. For another operation, change the scopes and add the repo claim if the operation needs it. Install the signing library for your language:
Each example prints a bearer token. Keep the token out of shared logs. Use the token with the HTTP request setup in the API reference.

Resolve token errors

An HTTP error response has a code and a detail. These responses come from a missing or incorrect token: For Invalid or expired token, check these items:
  • The signature uses a registered key, and alg is ES256, ES384, ES512, or RS256.
  • iss is your organization name.
  • exp is in the future.
  • iss, sub, and scopes are present, and each scope name is in Scopes.
  • repo is present when a scope needs it. See Repository claim.
  • A token from a restricted key meets the restricted key requirements.
For all other HTTP error codes, see Error codes in the API reference.

Rotate or revoke a key

To rotate a key, create a replacement, update your token signers, then delete the old key from the dashboard. When you delete a key, Code Storage rejects the tokens that its private key signed.