API Reference

The Glassbrain REST API lets you ingest traces, create replay sessions, request AI fix suggestions, and query usage data programmatically. All endpoints accept and return JSON. This reference covers every available endpoint with request examples in curl, JavaScript, and Python.

Authentication

All API requests require authentication via an API key. Include your key in the x-api-key header of every request.

bash
curl -H "x-api-key: gb_live_your_api_key_here" \
  https://glassbrain.dev/api/v1/traces/trc_a1b2c3d4e5f6

To find your API key:

  1. Open your Glassbrain dashboard
  2. Navigate to your project settings
  3. Click the "API Keys" tab
  4. Copy your live or test key

Important: Keep your API key secret. Do not commit it to version control or expose it in client-side code. Use environment variables to store your key. Keys prefixed with gb_live_ have production access. Keys prefixed with gb_test_ are restricted to test environments.

Base URL

All API endpoints are relative to the following base URL:

https://glassbrain.dev/api/v1

All requests must use HTTPS. HTTP requests are rejected with a 301 redirect.

POST/api/v1/traces

Ingest a new trace. This is the primary endpoint for sending error, warning, and info events to Glassbrain. The SDK calls this endpoint automatically when auto-capture is enabled.

Request Body

FieldTypeRequiredDescription
project_idstringYesYour project ID
typestringYesOne of error, warning, or info
messagestringYesHuman-readable event description (max 10,000 characters)
stack_tracestringNoFull stack trace string
metadataobjectNoArbitrary JSON object (max 64 KB)
sourcestringNoOrigin of the trace, e.g. client or server
browserstringNoBrowser name and version
osstringNoOperating system name and version
urlstringNoURL where the event occurred
user_agentstringNoRaw user agent string

Examples

bashcurl
curl -X POST https://glassbrain.dev/api/v1/traces \
  -H "Content-Type: application/json" \
  -H "x-api-key: gb_live_your_api_key_here" \
  -d '{
    "project_id": "prj_x7y8z9w0",
    "type": "error",
    "message": "Cannot read properties of undefined (reading map)",
    "stack_trace": "TypeError: Cannot read properties of undefined (reading map)\n    at UserList (/app/components/UserList.tsx:14:22)",
    "metadata": { "component": "UserList" },
    "source": "client",
    "browser": "Chrome 120.0.6099.109",
    "os": "macOS 14.2.1",
    "url": "https://myapp.com/dashboard/users"
  }'
typescriptjavascript
400 font-semibold">const response = 400 font-semibold">await fetch(400 font-semibold">class="text-emerald-400">"https:400 font-semibold">class="text-[rgba(255,255,255,0.3)] italic">//glassbrain.dev/api/v1/traces", {
  method: 400 font-semibold">class="text-emerald-400">"POST",
  headers: {
    400 font-semibold">class="text-emerald-400">"Content-Type": 400 font-semibold">class="text-emerald-400">"application/json",
    400 font-semibold">class="text-emerald-400">"x-api-key": process.env.GLASSBRAIN_API_KEY!,
  },
  body: JSON.stringify({
    project_id: 400 font-semibold">class="text-emerald-400">"prj_x7y8z9w0",
    400 font-semibold">type: 400 font-semibold">class="text-emerald-400">"error",
    message: 400 font-semibold">class="text-emerald-400">"Cannot read properties 400 font-semibold">of 400">undefined (reading 'map')",
    stack_trace: error.stack,
    metadata: { component: 400 font-semibold">class="text-emerald-400">"UserList" },
    source: 400 font-semibold">class="text-emerald-400">"client",
    browser: navigator.userAgent,
    url: window.location.href,
  }),
});

400 font-semibold">const data = 400 font-semibold">await response.json();
console.log(data.trace.id); 400 font-semibold">class="text-[rgba(255,255,255,0.3)] italic">// 400 font-semibold">class="text-emerald-400">"trc_a1b2c3d4e5f6"
pythonpython
400 font-semibold">import requests
400 font-semibold">import os

response = requests.post(
    400 font-semibold">class="text-emerald-400">"https:400 font-semibold">class="text-[rgba(255,255,255,0.3)] italic">//glassbrain.dev/api/v1/traces",
    headers={
        400 font-semibold">class="text-emerald-400">"Content-Type": 400 font-semibold">class="text-emerald-400">"application/json",
        400 font-semibold">class="text-emerald-400">"x-api-key": os.environ[400 font-semibold">class="text-emerald-400">"GLASSBRAIN_API_KEY"],
    },
    json={
        400 font-semibold">class="text-emerald-400">"project_id": 400 font-semibold">class="text-emerald-400">"prj_x7y8z9w0",
        400 font-semibold">class="text-emerald-400">"type": 400 font-semibold">class="text-emerald-400">"error",
        400 font-semibold">class="text-emerald-400">"message": 400 font-semibold">class="text-emerald-400">"Cannot read properties of undefined (reading 'map')",
        400 font-semibold">class="text-emerald-400">"stack_trace": traceback.format_exc(),
        400 font-semibold">class="text-emerald-400">"metadata": {400 font-semibold">class="text-emerald-400">"component": 400 font-semibold">class="text-emerald-400">"UserList"},
        400 font-semibold">class="text-emerald-400">"source": 400 font-semibold">class="text-emerald-400">"server",
    },
)

data = response.json()
print(data[400 font-semibold">class="text-emerald-400">"trace"][400 font-semibold">class="text-emerald-400">"id"])  400 font-semibold">class="text-[rgba(255,255,255,0.3)] italic"># 400 font-semibold">class="text-emerald-400">"trc_a1b2c3d4e5f6"

Response

Returns 201 Created on success with the full trace object:

jsonresponse - 201 Created
{
  "trace": {
    "id": "trc_a1b2c3d4e5f6",
    "project_id": "prj_x7y8z9w0",
    "type": "error",
    "message": "Cannot read properties of undefined (reading 'map')",
    "stack_trace": "TypeError: Cannot read properties of undefined (reading 'map')\n    at UserList (/app/components/UserList.tsx:14:22)",
    "metadata": { "component": "UserList" },
    "source": "client",
    "browser": "Chrome 120.0.6099.109",
    "os": "macOS 14.2.1",
    "url": "https://myapp.com/dashboard/users",
    "user_agent": null,
    "created_at": "2026-04-03T10:23:45.123Z"
  }
}

Error Responses

json400 Bad Request
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request body",
    "details": [
      { "field": "type", "message": "Must be one of: error, warning, info" },
      { "field": "message", "message": "Required field" }
    ]
  }
}
json401 Unauthorized
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid or missing API key. Include a valid key in the x-api-key header."
  }
}
json429 Too Many Requests
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Monthly trace limit reached. Upgrade your plan for higher limits.",
    "limit": 1000,
    "used": 1000,
    "resets_at": "2026-05-01T00:00:00.000Z"
  }
}

GET/api/v1/traces/:id

Retrieve a single trace by its ID. Returns the full trace object including all metadata.

Path Parameters

ParameterTypeDescription
idstringThe trace ID, prefixed with trc_

Examples

bashcurl
curl https://glassbrain.dev/api/v1/traces/trc_a1b2c3d4e5f6 \
  -H "x-api-key: gb_live_your_api_key_here"
typescriptjavascript
400 font-semibold">const response = 400 font-semibold">await fetch(
  400 font-semibold">class="text-emerald-400">"https:400 font-semibold">class="text-[rgba(255,255,255,0.3)] italic">//glassbrain.dev/api/v1/traces/trc_a1b2c3d4e5f6",
  {
    headers: {
      400 font-semibold">class="text-emerald-400">"x-api-key": process.env.GLASSBRAIN_API_KEY!,
    },
  }
);

400 font-semibold">const data = 400 font-semibold">await response.json();
console.log(data.trace.message);
console.log(data.trace.400 font-semibold">type);
pythonpython
400 font-semibold">import requests
400 font-semibold">import os

response = requests.get(
    400 font-semibold">class="text-emerald-400">"https:400 font-semibold">class="text-[rgba(255,255,255,0.3)] italic">//glassbrain.dev/api/v1/traces/trc_a1b2c3d4e5f6",
    headers={
        400 font-semibold">class="text-emerald-400">"x-api-key": os.environ[400 font-semibold">class="text-emerald-400">"GLASSBRAIN_API_KEY"],
    },
)

data = response.json()
print(data[400 font-semibold">class="text-emerald-400">"trace"][400 font-semibold">class="text-emerald-400">"message"])
print(data[400 font-semibold">class="text-emerald-400">"trace"][400 font-semibold">class="text-emerald-400">"type"])

Response

Returns 200 OK with the trace object:

jsonresponse - 200 OK
{
  "trace": {
    "id": "trc_a1b2c3d4e5f6",
    "project_id": "prj_x7y8z9w0",
    "type": "error",
    "message": "Cannot read properties of undefined (reading 'map')",
    "stack_trace": "TypeError: Cannot read properties of undefined...",
    "metadata": { "component": "UserList" },
    "source": "client",
    "browser": "Chrome 120.0.6099.109",
    "os": "macOS 14.2.1",
    "url": "https://myapp.com/dashboard/users",
    "user_agent": "Mozilla/5.0 ...",
    "created_at": "2026-04-03T10:23:45.123Z"
  }
}

Error Responses

json404 Not Found
{
  "error": {
    "code": "NOT_FOUND",
    "message": "Trace not found. Verify the trace ID and ensure it belongs to a project associated with your API key."
  }
}

POST/api/v1/replay

Create a new replay session for a trace. The session lets you step through the trace execution and optionally modify inputs to test alternative scenarios. See the Time-Travel Replay documentation for concepts and use cases.

Request Body

FieldTypeRequiredDescription
trace_idstringYesThe ID of the trace to replay
modestringYesEither snapshot or live
modificationsobjectNoObject containing input overrides, environment variable overrides, and mock responses

The modifications object supports the following nested fields:

FieldTypeDescription
inputsobjectKey-value pairs to override in the request input (e.g., request_body, query_params, headers)
env_overridesobjectEnvironment variable overrides as key-value pairs
mock_responsesobjectKeyed by span ID, each value replaces that span's response data

Examples

bashcurl
curl -X POST https://glassbrain.dev/api/v1/replay \
  -H "Content-Type: application/json" \
  -H "x-api-key: gb_live_your_api_key_here" \
  -d '{
    "trace_id": "trc_a1b2c3d4e5f6",
    "mode": "snapshot",
    "modifications": {
      "inputs": {
        "request_body": {
          "user_id": "usr_456",
          "include_deleted": false
        }
      },
      "mock_responses": {
        "span_db_query_001": {
          "rows": [{ "id": 1, "name": "Alice", "active": true }]
        }
      }
    }
  }'
typescriptjavascript
400 font-semibold">const response = 400 font-semibold">await fetch(400 font-semibold">class="text-emerald-400">"https:400 font-semibold">class="text-[rgba(255,255,255,0.3)] italic">//glassbrain.dev/api/v1/replay", {
  method: 400 font-semibold">class="text-emerald-400">"POST",
  headers: {
    400 font-semibold">class="text-emerald-400">"Content-Type": 400 font-semibold">class="text-emerald-400">"application/json",
    400 font-semibold">class="text-emerald-400">"x-api-key": process.env.GLASSBRAIN_API_KEY!,
  },
  body: JSON.stringify({
    trace_id: 400 font-semibold">class="text-emerald-400">"trc_a1b2c3d4e5f6",
    mode: 400 font-semibold">class="text-emerald-400">"snapshot",
    modifications: {
      inputs: {
        request_body: { user_id: 400 font-semibold">class="text-emerald-400">"usr_456" },
      },
    },
  }),
});

400 font-semibold">const data = 400 font-semibold">await response.json();
console.log(data.replay.id); 400 font-semibold">class="text-[rgba(255,255,255,0.3)] italic">// 400 font-semibold">class="text-emerald-400">"rpl_f1e2d3c4b5a6"
console.log(data.replay.total_steps); 400 font-semibold">class="text-[rgba(255,255,255,0.3)] italic">// 8
pythonpython
400 font-semibold">import requests
400 font-semibold">import os

response = requests.post(
    400 font-semibold">class="text-emerald-400">"https:400 font-semibold">class="text-[rgba(255,255,255,0.3)] italic">//glassbrain.dev/api/v1/replay",
    headers={
        400 font-semibold">class="text-emerald-400">"Content-Type": 400 font-semibold">class="text-emerald-400">"application/json",
        400 font-semibold">class="text-emerald-400">"x-api-key": os.environ[400 font-semibold">class="text-emerald-400">"GLASSBRAIN_API_KEY"],
    },
    json={
        400 font-semibold">class="text-emerald-400">"trace_id": 400 font-semibold">class="text-emerald-400">"trc_a1b2c3d4e5f6",
        400 font-semibold">class="text-emerald-400">"mode": 400 font-semibold">class="text-emerald-400">"snapshot",
        400 font-semibold">class="text-emerald-400">"modifications": {
            400 font-semibold">class="text-emerald-400">"inputs": {
                400 font-semibold">class="text-emerald-400">"request_body": {400 font-semibold">class="text-emerald-400">"user_id": 400 font-semibold">class="text-emerald-400">"usr_456"},
            },
        },
    },
)

data = response.json()
print(data[400 font-semibold">class="text-emerald-400">"replay"][400 font-semibold">class="text-emerald-400">"id"])  400 font-semibold">class="text-[rgba(255,255,255,0.3)] italic"># 400 font-semibold">class="text-emerald-400">"rpl_f1e2d3c4b5a6"
print(data[400 font-semibold">class="text-emerald-400">"replay"][400 font-semibold">class="text-emerald-400">"total_steps"])  400 font-semibold">class="text-[rgba(255,255,255,0.3)] italic"># 8

Response

Returns 201 Created with the replay session:

jsonresponse - 201 Created
{
  "replay": {
    "id": "rpl_f1e2d3c4b5a6",
    "trace_id": "trc_a1b2c3d4e5f6",
    "mode": "snapshot",
    "status": "ready",
    "total_steps": 8,
    "current_step": 0,
    "modifications_applied": 2,
    "created_at": "2026-04-03T14:30:00.000Z",
    "expires_at": "2026-04-03T15:30:00.000Z"
  }
}

Error Responses

json400 Bad Request
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request body",
    "details": [
      { "field": "mode", "message": "Must be one of: snapshot, live" }
    ]
  }
}
json404 Not Found
{
  "error": {
    "code": "NOT_FOUND",
    "message": "Trace not found. Cannot create a replay for a non-existent trace."
  }
}

POST/api/v1/suggestions

Request AI-powered fix suggestions for a trace. The AI engine analyzes the trace's error message, stack trace, and metadata to generate one or more code fix suggestions with diffs and confidence scores. See AI Fix Suggestions for details on how the engine works.

Request Body

FieldTypeRequiredDescription
trace_idstringYesThe ID of the trace to analyze
max_suggestionsnumberNoMaximum number of suggestions to return (default: 3, max: 5)

Examples

bashcurl
curl -X POST https://glassbrain.dev/api/v1/suggestions \
  -H "Content-Type: application/json" \
  -H "x-api-key: gb_live_your_api_key_here" \
  -d '{
    "trace_id": "trc_a1b2c3d4e5f6",
    "max_suggestions": 3
  }'
typescriptjavascript
400 font-semibold">const response = 400 font-semibold">await fetch(400 font-semibold">class="text-emerald-400">"https:400 font-semibold">class="text-[rgba(255,255,255,0.3)] italic">//glassbrain.dev/api/v1/suggestions", {
  method: 400 font-semibold">class="text-emerald-400">"POST",
  headers: {
    400 font-semibold">class="text-emerald-400">"Content-Type": 400 font-semibold">class="text-emerald-400">"application/json",
    400 font-semibold">class="text-emerald-400">"x-api-key": process.env.GLASSBRAIN_API_KEY!,
  },
  body: JSON.stringify({
    trace_id: 400 font-semibold">class="text-emerald-400">"trc_a1b2c3d4e5f6",
    max_suggestions: 3,
  }),
});

400 font-semibold">const data = 400 font-semibold">await response.json();

400 font-semibold">for (400 font-semibold">const suggestion 400 font-semibold">of data.suggestions) {
  console.log(suggestion.title);
  console.log(400 font-semibold">class="text-emerald-400">"Confidence:", suggestion.confidence);
  console.log(400 font-semibold">class="text-emerald-400">"File:", suggestion.file_path);
  console.log(400 font-semibold">class="text-emerald-400">"---");
}
pythonpython
400 font-semibold">import requests
400 font-semibold">import os

response = requests.post(
    400 font-semibold">class="text-emerald-400">"https:400 font-semibold">class="text-[rgba(255,255,255,0.3)] italic">//glassbrain.dev/api/v1/suggestions",
    headers={
        400 font-semibold">class="text-emerald-400">"Content-Type": 400 font-semibold">class="text-emerald-400">"application/json",
        400 font-semibold">class="text-emerald-400">"x-api-key": os.environ[400 font-semibold">class="text-emerald-400">"GLASSBRAIN_API_KEY"],
    },
    json={
        400 font-semibold">class="text-emerald-400">"trace_id": 400 font-semibold">class="text-emerald-400">"trc_a1b2c3d4e5f6",
        400 font-semibold">class="text-emerald-400">"max_suggestions": 3,
    },
)

data = response.json()

400 font-semibold">for suggestion 400 font-semibold">in data[400 font-semibold">class="text-emerald-400">"suggestions"]:
    print(suggestion[400 font-semibold">class="text-emerald-400">"title"])
    print(f400 font-semibold">class="text-emerald-400">"Confidence: {suggestion['confidence']}")
    print(f400 font-semibold">class="text-emerald-400">"File: {suggestion['file_path']}")
    print(400 font-semibold">class="text-emerald-400">"---")

Response

Returns 200 OK with an array of suggestions sorted by confidence (highest first):

jsonresponse - 200 OK
{
  "suggestions": [
    {
      "id": "sug_m1n2o3p4q5r6",
      "trace_id": "trc_a1b2c3d4e5f6",
      "title": "Add null check before mapping users array",
      "description": "The error occurs because the 'users' prop is null when the component renders. The .map() method is called on a null value, which throws a TypeError. Adding a nullish coalescing operator with a fallback to an empty array prevents the error.",
      "code_diff": "--- a/components/UserList.tsx\n+++ b/components/UserList.tsx\n@@ -12,7 +12,7 @@\n export function UserList({ users }: UserListProps) {\n   return (\n     <ul>\n-      {users.map((user) => (\n+      {(users ?? []).map((user) => (\n         <li key={user.id}>{user.name}</li>\n       ))}\n     </ul>",
      "confidence": 0.94,
      "file_path": "components/UserList.tsx",
      "line_start": 15,
      "line_end": 15,
      "created_at": "2026-04-03T10:25:12.000Z"
    },
    {
      "id": "sug_s7t8u9v0w1x2",
      "trace_id": "trc_a1b2c3d4e5f6",
      "title": "Add default prop value for users",
      "description": "Set a default value for the users prop in the component definition so it is never undefined when accessed.",
      "code_diff": "--- a/components/UserList.tsx\n+++ b/components/UserList.tsx\n@@ -10,7 +10,7 @@\n-export function UserList({ users }: UserListProps) {\n+export function UserList({ users = [] }: UserListProps) {\n   return (",
      "confidence": 0.87,
      "file_path": "components/UserList.tsx",
      "line_start": 11,
      "line_end": 11,
      "created_at": "2026-04-03T10:25:12.000Z"
    }
  ],
  "trace_id": "trc_a1b2c3d4e5f6",
  "generated_at": "2026-04-03T10:25:12.000Z"
}

Error Responses

json403 Forbidden
{
  "error": {
    "code": "FORBIDDEN",
    "message": "Suggestions are only available for error-type traces. This trace has type 'info'."
  }
}
json429 Too Many Requests
{
  "error": {
    "code": "SUGGESTION_LIMIT_EXCEEDED",
    "message": "Monthly suggestion limit reached (10/10). Upgrade to Pro for unlimited suggestions.",
    "limit": 10,
    "used": 10,
    "resets_at": "2026-05-01T00:00:00.000Z"
  }
}

GET/api/v1/projects/:id/usage

Retrieve usage statistics for a project. Returns current period counts for traces, suggestions, and replays along with plan limits.

Path Parameters

ParameterTypeDescription
idstringThe project ID, prefixed with prj_

Examples

bashcurl
curl https://glassbrain.dev/api/v1/projects/prj_x7y8z9w0/usage \
  -H "x-api-key: gb_live_your_api_key_here"
typescriptjavascript
400 font-semibold">const response = 400 font-semibold">await fetch(
  400 font-semibold">class="text-emerald-400">"https:400 font-semibold">class="text-[rgba(255,255,255,0.3)] italic">//glassbrain.dev/api/v1/projects/prj_x7y8z9w0/usage",
  {
    headers: {
      400 font-semibold">class="text-emerald-400">"x-api-key": process.env.GLASSBRAIN_API_KEY!,
    },
  }
);

400 font-semibold">const data = 400 font-semibold">await response.json();
console.log(400 font-semibold">class="text-emerald-400">"Traces used:", data.usage.traces.used);
console.log(400 font-semibold">class="text-emerald-400">"Traces limit:", data.usage.traces.limit);
pythonpython
400 font-semibold">import requests
400 font-semibold">import os

response = requests.get(
    400 font-semibold">class="text-emerald-400">"https:400 font-semibold">class="text-[rgba(255,255,255,0.3)] italic">//glassbrain.dev/api/v1/projects/prj_x7y8z9w0/usage",
    headers={
        400 font-semibold">class="text-emerald-400">"x-api-key": os.environ[400 font-semibold">class="text-emerald-400">"GLASSBRAIN_API_KEY"],
    },
)

data = response.json()
print(f400 font-semibold">class="text-emerald-400">"Traces: {data['usage']['traces']['used']}/{data['usage']['traces']['limit']}")
print(f400 font-semibold">class="text-emerald-400">"Suggestions: {data['usage']['suggestions']['used']}/{data['usage']['suggestions']['limit']}")

Response

Returns 200 OK with usage data:

jsonresponse - 200 OK
{
  "usage": {
    "project_id": "prj_x7y8z9w0",
    "plan": "pro",
    "period_start": "2026-04-01T00:00:00.000Z",
    "period_end": "2026-05-01T00:00:00.000Z",
    "traces": {
      "used": 12453,
      "limit": 50000
    },
    "suggestions": {
      "used": 87,
      "limit": -1
    },
    "diff_views": {
      "used": 87,
      "limit": -1
    },
    "replays": {
      "used": 34,
      "limit": -1
    }
  }
}

A limit of -1 indicates unlimited usage for that feature on the current plan.

Rate Limits

API rate limits are enforced per project on a monthly billing cycle. When you exceed your plan's limit, the API returns a 429 Too Many Requests response. Limits reset on the first day of each calendar month.

PlanTraces / MonthSuggestions / MonthDiff Views / Month
Free1,000105
Pro50,000UnlimitedUnlimited
Team200,000UnlimitedUnlimited
Business500,000UnlimitedUnlimited

In addition to monthly limits, the API enforces a per-second rate limit of 100 requests per second across all endpoints. This protects the service from burst traffic. If you exceed this limit, you will receive a 429 response with a Retry-After header indicating how many seconds to wait before retrying.

Error Codes

All error responses follow a consistent format with an error object containing a machine-readable code and a human-readable message:

jsonerror-format.json
{
  "error": {
    "code": "ERROR_CODE",
    "message": "A human-readable description of what went wrong."
  }
}
HTTP StatusError CodeDescription
400VALIDATION_ERRORThe request body is malformed or missing required fields. The response includes a details array listing each invalid field and the specific validation error.
401UNAUTHORIZEDThe x-api-key header is missing or contains an invalid API key. Verify your key in your project settings.
403FORBIDDENYour API key is valid but does not have permission for the requested operation. This occurs when accessing a resource that belongs to a different project, or when requesting a feature not available on your plan.
404NOT_FOUNDThe requested resource does not exist. Verify the ID in the URL path and ensure the resource belongs to a project associated with your API key.
429RATE_LIMIT_EXCEEDEDYou have exceeded your plan's monthly limit or the per-second rate limit. The response includes limit, used, and resets_at fields. Check the Retry-After header for per-second limits.
500INTERNAL_ERRORAn unexpected error occurred on the Glassbrain server. These errors are automatically reported and investigated. If you encounter persistent 500 errors, contact support at support@glassbrain.dev.