Mesa uses API keys for authentication. Each key is scoped to an organization, prefixed with mesa_, and granted specific permission scopes to control what it can access.
Your First API Key
Your first API key is created in the Mesa dashboard. If you haven’t done this yet, follow the Quickstart to create this initial key with admin scope. See Permission Scopes for details on what each scope allows.
API keys are only displayed once when created. Store them securely in your secrets manager or environment variables.
Creating API Keys
You can create API keys through the SDK or API. This is useful if you want to generate scoped keys to put in a sandbox or within CI:
import { Mesa } from "@mesadev/rest";
const mesa = new Mesa({ apiKey: process.env.MESA_API_KEY });
const key = await mesa.admin.createApiKey({
org: "my-org",
body: {
name: "CI/CD Pipeline",
scopes: ["read", "write"],
expires_in_seconds: 2592000, // 30 days (optional)
tags: {
env: "production",
team: "platform",
},
},
});
// Store this securely - the full key is only shown once!
console.log(key.key); // mesa_abc123...
You can also create keys from the Mesa dashboard.
Tags are optional key-value labels you can attach to API keys to organize them by purpose, environment, team, or any other dimension. Tag keys are lowercased and limited to 64 characters, values are limited to 256 characters.
You can update tags on an existing key. Set a value to null to remove a tag:
await mesa.admin.updateApiKeyTags({
org: "my-org",
id: key.id,
body: {
tags: {
env: "staging", // set or overwrite
team: null, // remove
},
},
});
Tags are returned on every key when listing.
Listing Keys
const { apiKeys } = await mesa.admin.listApiKeys({ org: "my-org" });
for (const key of apiKeys) {
console.log(`${key.name}: ${key.id} (${key.scopes.join(", ")})`);
}
The full API key value is never returned when listing keys. Only the key ID, name, and metadata are returned.
Revoking Keys
await mesa.admin.revokeApiKey({
org: "my-org",
id: "mesa_abc123",
});
Revoked keys are immediately invalidated and cannot be used for authentication.
Using API Keys
SDK Authentication
import { Mesa } from "@mesadev/rest";
const mesa = new Mesa({
apiKey: "<API_KEY>",
});
Or using environment variables:
export MESA_API_KEY=<API_KEY>
const mesa = new Mesa({
apiKey: process.env.MESA_API_KEY,
});
HTTP API Authentication
Include the API key in the Authorization header:
curl -H "Authorization: Bearer <API_KEY>" \
https://api.mesa.dev/my-org/repo
Git Authentication
Use the API key as the password when cloning or pushing:
git clone https://t:<API_KEY>@api.mesa.dev/my-org/my-repo.git
Permission Scopes
Scopes are hierarchical. Each level includes all permissions from the levels below it.
| Scope | Description |
|---|
read | Clone, fetch, and view repositories, branches, commits, and content |
write | Everything read can do, plus push, create/update/delete repos, branches, and bookmarks |
admin | Everything write can do, plus API key management and webhook management |
Scope Requirements by Operation
| Operation | Required Scope |
|---|
| List/get repos, branches, commits, content, diffs, changes | read |
| Clone (git fetch) | read |
| Create/update/delete repos | write |
| Push (git push) | write |
| Create/move/delete bookmarks and changes | write |
| Manage webhooks | admin |
| Manage API keys | admin |
Default Scopes
When creating a key without specifying scopes, the default is:
This covers common development workflows without granting admin capabilities.
Repository Scoping
By default, API keys have access to all repositories in the organization. You can restrict a key to specific repositories by passing repo_ids when creating it:
const key = await mesa.admin.createApiKey({
org: "my-org",
body: {
name: "Agent - frontend repo",
scopes: ["read", "write"],
repo_ids: ["repo-id-1", "repo-id-2"],
},
});
A repo-scoped key can only access the repositories it was created for. Any attempt to access a repository outside its scope will return a FORBIDDEN error. This applies to all operations including API calls, git push/pull, and webhook management.
Repository scoping is independent of permission scopes. Both must be satisfied. For example, a key with read scope and repo_ids: ["repo-id-1"] can only read repo-id-1.
Use repo-scoped keys when giving agents access to a single repository or a subset of repos. This limits the blast radius if a key is leaked.
Key Expiration
API keys can optionally be set to expire by passing expires_in_seconds when creating a key. The value must be between 100 seconds and 1 year (31,536,000 seconds). Omit the field to create a key that never expires. Expired keys are automatically invalidated and will return UNAUTHORIZED on use.
You can also set expiration from the dashboard when creating a key.
Error Handling
Authentication Errors
try {
await mesa.repos.list({ org: "my-org" });
} catch (error) {
if (error.code === "UNAUTHORIZED") {
// Invalid or missing API key
console.error("Check your API key");
}
}
Permission Errors
try {
await mesa.repos.delete({ org: "my-org", repo: "important-repo" });
} catch (error) {
if (error.code === "FORBIDDEN") {
// API key lacks required scope
console.error("API key needs write scope");
}
}
Organization Access
API keys are scoped to a single organization. Attempting to access a different organization returns a forbidden error:
// Key belongs to "org-a"
const mesa = new Mesa({ apiKey: orgAKey });
// This will fail - wrong organization
await mesa.repos.list({ org: "org-b" }); // Error: FORBIDDEN
Best Practices
Use Scoped Keys
Create separate API keys for different purposes with minimal required scopes:
// Read-only key for monitoring or analytics
await mesa.admin.createApiKey({
org: "my-org",
body: {
name: "Analytics Service",
scopes: ["read"],
},
});
// CI/CD key with 90-day expiration
await mesa.admin.createApiKey({
org: "my-org",
body: {
name: "GitHub Actions",
scopes: ["read", "write"],
expires_in_seconds: 7776000,
},
});
- Set expirations on keys used in CI or sandboxes. Long-lived keys should be limited to
read or write scope.
- Rotate keys regularly. If a key is compromised, revoke it immediately from the dashboard or SDK.
- Keys are always bound to a single organization. Even an
admin key with no repo restrictions cannot access another organization’s resources.