REST API Reference
Send data to GoodLogs from any language using HTTP. All endpoints accept JSON and return JSON.
Authentication
Include your API key in the Authorization header:
Authorization: Bearer gl_sk_eu_your_key_here
| Key Type | Format | Permissions |
|---|---|---|
| Public | gl_pk_{region}_... | Events only (/v1/events). Safe for client-side. |
| Secret | gl_sk_{region}_... | Logs + events. Server-side only. |
Base URLs
Requests are routed to the regional API matching your key's region prefix:
POST /v1/events
Ingest product analytics events. Works with both public and secret keys.
curl -X POST https://api.goodlogs.ajaysharma.dev/v1/events \
-H "Authorization: Bearer gl_pk_eu_your_key" \
-H "Content-Type: application/json" \
-d '{
"batch": [
{
"event": "signup",
"distinctId": "user_123",
"properties": {
"plan": "pro",
"source": "google",
"amount": 29.99
}
},
{
"event": "page_view",
"properties": {
"path": "/pricing",
"referrer": "https://google.com"
}
}
]
}'
Event Fields
| Field | Type | Required | Description |
|---|---|---|---|
| event | string | Yes | Event name (e.g. 'signup', 'purchase') |
| distinctId | string | No | User ID for analytics. Stored as distinct_id in properties. |
| properties | object | No | Any key-value data — all fields are searchable via GQL |
| timestamp | ISO 8601 | No | Override timestamp (default: server time) |
Tip: Pass any properties you want — they're stored flat and queryable. No schema required.
POST /v1/logs
Ingest structured log entries. Requires a secret key.
curl -X POST https://api.goodlogs.ajaysharma.dev/v1/logs \
-H "Authorization: Bearer gl_sk_eu_your_secret" \
-H "Content-Type: application/json" \
-d '{
"batch": [
{
"severity": "error",
"message": "Payment failed: card_declined",
"properties": {
"orderId": "ord_123",
"amount": 29,
"service": "billing",
"env": "prod"
}
},
{
"severity": "info",
"message": "User signed up",
"properties": {
"userId": "u_456",
"method": "google"
}
}
]
}'
Log Fields
| Field | Type | Required | Description |
|---|---|---|---|
| severity | string | Yes | debug, info, warn, error, fatal |
| message | string | Yes | Log message (max 64KB) |
| properties | object | No | Any key-value data — all fields are searchable via GQL |
| timestamp | ISO 8601 | No | Override timestamp (default: server time) |
Legacy fields (metadata, context, code_location) are still accepted for backward compatibility — they get flattened into properties automatically.
Response Format
{
"accepted": 5,
"rejected": 0
}
Status 202 Accepted on success.
API Key Endpoints (/v1)
These endpoints authenticate with API keys (no JWT needed). The project is determined from the key.
GET /v1/info
Get project info, org details, usage, and key count. Scope: data:read.
GET /v1/schema
Get discovered schema — event names, property fields, types, and volumes. Scope: data:read.
POST /v1/gql/query
Run a single GQL query. Scope: data:read.
curl -X POST https://api.goodlogs.ajaysharma.dev/v1/gql/query \
-H "Authorization: Bearer gl_sk_..." \
-H "Content-Type: application/json" \
-d '{"q": "count | where severity = '\''error'\'' | last 1h"}'
Response: {query, data} — direct result. Errors return standard error format.
POST /v1/gql
Batch: run multiple GQL queries in one request. Max 10 queries. Scope: data:read.
curl -X POST https://api.goodlogs.ajaysharma.dev/v1/gql \
-H "Authorization: Bearer gl_sk_..." \
-H "Content-Type: application/json" \
-d '{"queries": [
"from:events | count_distinct(distinct_id) | last:1d",
"from:events | count | last:30d",
"from:events | count by event_name | top 10 | last:30d"
]}'
Response: array [{status: "ok", query, data}, ...] — partial failures return {status: "error", error: "msg"} per query.
GET /v1/gql/autocomplete
GQL autocomplete suggestions. Pass q (partial query) and optional cursor position.
POST /v1/gql/nl
Convert natural language to GQL. Body: {"prompt": "...", "mode": "explorer"}. Scope: data:read.
GET /v1/info
Project info, org, usage, and key count. Scope: data:read.
GET /v1/schema
Discovered schema — event names, property fields, types, and volumes. Scope: data:read.
Alerts (CRUD)
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /v1/alerts | alerts:read | List all alert rules |
| POST | /v1/alerts | alerts:write | Create alert rule |
| PUT | /v1/alerts/:id | alerts:write | Update alert rule |
| DELETE | /v1/alerts/:id | alerts:write | Delete alert rule |
For alert timeline, use GQL: from:alert_events | last 7d
For analytics (DAU, trends), use GQL batch:
{"queries": [
"count distinct $distinct_id from:events | last 1d",
"count from:events | last 30d",
"count by event from:events | sort count desc | limit 10"
]}
AI Chat
| Method | Path | Scope | Description |
|---|---|---|---|
| POST | /v1/ai/chat | ai:chat | Send message, get response |
| POST | /v1/ai/chat/stream | ai:chat | SSE streaming response |
Body: {"message": "...", "session_id": "..."}.
Session Replay
| Method | Path | Scope | Description |
|---|---|---|---|
| POST | /v1/replay/sessions | ingest:write | Upsert session metadata |
| POST | /v1/replay/chunks?session_id=...&seq=N&start_ts=...&end_ts=...&event_count=N | ingest:write | Upload gzipped rrweb chunk |
Continuous Profiling
| Method | Path | Scope | Description |
|---|---|---|---|
| POST | /v1/profiles/chunks?profiler_id=...&profile_type=cpu&sample_count=N&... | ingest:write | Upload profile chunk (JSON body, up to 10 MiB) |
Public Status Pages (no auth)
| Method | Path | Description |
|---|---|---|
| GET | /v1/status/{slug} | JSON status snapshot (status, uptime, incidents) |
| GET | /v1/status/{slug}/incidents?days=N | Paged incident history |
| GET | /v1/status-pages/{slug}/resolve | Resolve slug → region + title |
Dashboard Endpoints (/api)
These endpoints use JWT authentication (from login/signup).
GET /api/orgs/:org/projects/:project/gql
Execute a GQL query. Requires JWT auth (from login). Pass the query as a URL parameter.
# Search
curl "https://api.goodlogs.ajaysharma.dev/api/orgs/{org_id}/projects/{project_id}/gql?q=severity:error%20last:24h" \
-H "Authorization: Bearer {jwt_token}"
# Aggregate
curl "https://api.goodlogs.ajaysharma.dev/api/orgs/{org_id}/projects/{project_id}/gql?q=severity:error%20|%20count%20by%20message%20|%20top%2010" \
-H "Authorization: Bearer {jwt_token}"
Response
{
"query": {
"source": "logs",
"mode": "aggregate",
"filters": [{"field": "severity", "op": "eq", "value": "error"}],
"pipeline": [{"type": "aggregate", "func": "count", "group_by": ["message"]}]
},
"data": {
"mode": "aggregate",
"table": "logs",
"results": [
{"message": "Connection timeout", "value": 42},
{"message": "Payment declined", "value": 15}
],
"total_groups": 2
}
}
GET /api/orgs/:org/projects/:project/gql/autocomplete
Schema-aware GQL autocomplete and validation.
curl "https://api.goodlogs.ajaysharma.dev/api/orgs/{org_id}/projects/{project_id}/gql/autocomplete?q=severity:&cursor=9" \
-H "Authorization: Bearer {jwt_token}"
{
"suggestions": [
{"text": "error", "description": "12,345 logs", "kind": "value"},
{"text": "warning", "description": "5,678 logs", "kind": "value"}
],
"valid": true,
"parsed": { "source": "logs", "filters": [] }
}
POST /api/orgs/:org/projects/:project/gql/nl
Convert natural language to a GQL query using AI. Consumes 1 AI query from quota.
curl -X POST "https://api.goodlogs.ajaysharma.dev/api/orgs/{org_id}/projects/{project_id}/gql/nl" \
-H "Authorization: Bearer {jwt_token}" \
-H "Content-Type: application/json" \
-d '{"prompt": "show me errors from the last hour", "mode": "explorer"}'
{
"gql": "severity:error last:1h",
"valid": true,
"parsed": { "source": "logs", "filters": [] }
}
| Field | Type | Description |
|---|---|---|
| prompt | string | Natural language query (max 500 chars) |
| mode | string | "explorer" (search/aggregate) or "alert" (threshold conditions) |
GET /api/orgs/:org/projects/:project/schema
Get discovered schema — all property keys, types, example values, and event names.
{
"log_properties": [
{"key": "orderId", "type": "string", "examples": ["ord_123"], "volume": 1500},
{"key": "duration_ms", "type": "number", "examples": [125, 340], "volume": 8200}
],
"event_properties": [
{"key": "plan", "type": "string", "examples": ["pro", "free"], "volume": 3400}
],
"event_names": [
{"name": "signup", "volume": 1200},
{"name": "purchase", "volume": 890}
]
}
Schema is auto-discovered as you ingest data — no manual configuration needed.
Server-Injected Fields
The API automatically parses the User-Agent header and adds these fields to every log and event:
| Field | Example |
|---|---|
| $browser | Chrome 125 |
| $os | macOS 14 |
| $device | Desktop |
| $ip | 203.0.113.42 |
Limits
| Limit | Value |
|---|---|
| Max batch size | 500 items per request |
| Max request body | 2 MB |
| Max log message | 64 KB per entry |
| Rate limit (Free) | 10 req/s |
| Rate limit (Starter) | 20 req/s |
| Rate limit (Pro) | 50 req/s |
| Rate limit (Team) | 100 req/s |
Error Codes
| Status | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | Invalid request body or GQL parse error |
| 401 | UNAUTHORIZED | Missing or invalid API key / JWT |
| 402 | QUOTA_EXCEEDED | Plan limit reached |
| 403 | FORBIDDEN | Insufficient permissions or CORS blocked |
| 404 | NOT_FOUND | Resource not found |
| 422 | VALIDATION_ERROR | GQL query error or invalid input |
| 429 | RATE_LIMITED | Too many requests |
| 500 | INTERNAL_ERROR | Server error |
Warning: Error responses include a descriptive message in error.message — use it for debugging.