Skip to main content
API keys authenticate the gavAI CLI, SDK, and any signed-API consumer you build. Each key carries a fixed set of scopes that constrain what it can read or mutate.

Mint a key

  1. Sign in to /console/settings/api-keys as a workspace admin.
  2. Click Mint new key, name it (e.g. ci-pipeline), pick the scopes it needs.
  3. Copy the secret immediately — it is shown exactly once.
Keys are stored hashed; gavAI cannot recover the plaintext. If you lose it, revoke and re-mint.

Scopes

Scopes are namespaced <surface>:<action>. Common ones:
ScopeMeaning
workspace:readRead workspace + member listings
workspace:auditRead tenant audit log
workspace:billingRead invoices, manage portal session
workspace:membersInvite / remove / role-change members
workspace:adminFull admin parity (use sparingly)
apps:read, apps:writeRead / publish tenant apps
submissions:read, submissions:revealRead submissions; reveal masked fields
Issue the narrowest scope that satisfies the job — admin parity is rarely the right choice for automation.

Signing requests

The CLI / SDK sign requests with HMAC-SHA256 over a canonical string:
<HTTP-METHOD> \n
<request-path> \n
<unix-timestamp-seconds> \n
<sha256-hex(payload-bytes)>
Send three headers:
HeaderValue
AuthorizationBearer <key-id>
X-Gavai-Signaturev1=<hex(hmac_sha256(secret, canonical_string))>
X-Gavai-TimestampThe same unix-timestamp used in canonical_string

Sample (Node)

import { createHmac, createHash } from "node:crypto";

function signRequest(opts: {
  method: string;
  path: string;
  body: Uint8Array;
  keyId: string;
  secret: string;
}): Record<string, string> {
  const ts = Math.floor(Date.now() / 1000).toString();
  const payloadDigest = createHash("sha256").update(opts.body).digest("hex");
  const canonical = `${opts.method.toUpperCase()}\n${opts.path}\n${ts}\n${payloadDigest}`;
  const sig = createHmac("sha256", opts.secret).update(canonical).digest("hex");
  return {
    Authorization: `Bearer ${opts.keyId}`,
    "X-Gavai-Signature": `v1=${sig}`,
    "X-Gavai-Timestamp": ts,
  };
}

Sample (Python)

import hashlib
import hmac
import time

def sign_request(method, path, body, key_id, secret):
    ts = str(int(time.time()))
    payload_digest = hashlib.sha256(body).hexdigest()
    canonical = f"{method.upper()}\n{path}\n{ts}\n{payload_digest}"
    sig = hmac.new(secret.encode(), canonical.encode(), hashlib.sha256).hexdigest()
    return {
        "Authorization": f"Bearer {key_id}",
        "X-Gavai-Signature": f"v1={sig}",
        "X-Gavai-Timestamp": ts,
    }

Verification on the server side

gavAI rejects requests where:
  • The timestamp is more than 5 minutes off the server clock (replay protection).
  • The HMAC computed over the canonical string does not match.
  • The key_id is revoked or the scope does not cover the requested action.
Use a constant-time comparison for the HMAC check.

Rotation

When a key needs rotating:
  1. Mint a new key with the same scopes.
  2. Roll over the key in your secret store / config.
  3. Revoke the old key from /console/settings/api-keys.
Revoked keys reject immediately. There is no grace period — coordinate the rollover before revoking.

Audit trail

Each mint, revoke, and authenticated request is recorded in the tenant audit log (/console/activity). Use this to spot keys that aren’t doing what you expect.