Authentication
Two auth mechanisms are accepted on all data endpoints:
API Key
Pass as Authorization: Bearer <key>. Works for ingestion and all read endpoints.
Session Cookie
Obtained via email/password login. Used by the dashboard. Cookie is HttpOnly and valid for 24 hours.
/api/auth/register Public Create a new user account and organization. Open to anyone when no users exist (first-user bootstrap). Existing users can invite new members.
Request
{
"email": "[email protected]",
"password": "securepassword",
"name": "Jane Doe",
"orgName": "Acme Inc"
} Response 201
{
"success": true,
"user": { "id": "...", "email": "[email protected]", "name": "Jane Doe" },
"org": { "id": "...", "name": "Acme Inc", "slug": "acme-inc" },
"project": { "id": "...", "name": "Default", "slug": "default" },
"apiKey": "sw_a1b2c3..."
} /api/auth/login Public Authenticate with email and password. Sets an HttpOnly session cookie valid for 24 hours.
{
"email": "[email protected]",
"password": "securepassword"
} /api/auth/logout Session Invalidates the current session and clears the cookie.
/api/auth/me Session or API Key Returns the authenticated user's profile and their organizations with projects.
{
"user": { "id": "...", "email": "[email protected]", "name": "Jane Doe" },
"orgs": [
{
"id": "...", "name": "Acme Inc", "slug": "acme-inc", "role": "owner",
"projects": [{ "id": "...", "name": "Default", "slug": "default" }]
}
]
} Multi-Tenancy
ScryWatch uses an Organization → Project hierarchy. All data endpoints are namespaced:
/api/orgs/:orgSlug/projects/:projectSlug/... For example, querying logs for the "default" project in "acme-inc":
GET /api/orgs/acme-inc/projects/default/logs?level=error | Concept | Description |
|---|---|
| Organization | A team or company. Has a unique slug. Members have roles: owner, admin, or member. |
| Project | A data silo within an org. API keys are scoped to a single project. |
| API Key | Used for ingest and read access. The project is determined automatically from the key. |
Members
/api/orgs/:orgSlug/members Session List all members of the organization with their roles and join dates.
/api/orgs/:orgSlug/members/invite Session (admin+) Invite a user to the organization. If the email doesn't match an existing user, a new account is created with a temporary password.
Request
{ "email": "...", "name": "Jane Doe", "role": "member" } Response 201
{ "user_id": "...", "temp_password": "abc123..." } temp_password is null if the email matched an existing user. Share temporary passwords securely./api/orgs/:orgSlug/members/:userId Session (admin+) Update a member's role. Only owners can promote to owner or admin.
{ "role": "admin" } /api/orgs/:orgSlug/members/:userId Session (admin+) Remove a member from the organization. Cannot remove yourself.
/api/orgs/:orgSlug/members/:userId/reset-password Session (admin+) Generate a new temporary password for a member.
{ "temp_password": "abc123..." } Projects
/api/orgs/:orgSlug/projects Session List all projects in the organization.
/api/orgs/:orgSlug/projects Session (admin+) Create a new project.
{ "name": "Mobile App" } { "id": "...", "name": "Mobile App", "slug": "mobile-app" } /api/orgs/:orgSlug/projects/:projectSlug Session (admin+) Rename a project.
{ "name": "New Name" } /api/orgs/:orgSlug/projects/:projectSlug Session (owner only) Delete a project and all its data. Cannot delete the last project in an org.
Ingest Events
/api/ingest API Key Submit a batch of log events. Max 50 events per request.
Request
{
"events": [
{
"timestamp": 1709312400000,
"level": "error",
"type": "crash",
"message": "NullPointerException in UserService",
"service": "auth-worker",
"environment": "production",
"user_id": "user_123",
"session_id": "sess_abc",
"trace_id": "abc123",
"span_id": "span001",
"metadata": { "stack_trace": "...", "app_version": "2.1.0" }
}
]
} Fields
| Field | Type | Required | Description |
|---|---|---|---|
timestamp | number | Yes | Unix ms |
level | string | Yes | error | warn | info | debug |
type | string | Yes | crash | session | navigation | api_call | custom | cron |
message | string | Yes | Human-readable log message |
id | string | No | Custom ID; auto-generated ULID if omitted |
service | string | No | Originating service name |
environment | string | No | e.g. production, staging |
user_id | string | No | User who triggered the event |
session_id | string | No | Groups events into a session |
device_type | string | No | Auto-detected from User-Agent if omitted |
trace_id | string | No | Links this log to a distributed trace |
span_id | string | No | Links this log to a specific span |
metadata | object | No | Arbitrary JSON payload |
Response 202
{ "status": 202, "inserted": 1 } Query Logs
/api/orgs/:org/projects/:project/logs Session or API Key Search and filter log events with pagination. Automatically queries both D1 hot storage and R2 cold archive when from is older than 24 hours.
Query Parameters
| Param | Type | Description |
|---|---|---|
level | string | Comma-separated: error,warn |
type | string | Comma-separated event types |
service | string | Exact match |
device_type | string | Exact match |
environment | string | Exact match |
user_id | string | Filter by user |
session_id | string | Filter by session |
q | string | Full-text search on message |
from | number | Start timestamp (ms) |
to | number | End timestamp (ms) |
limit | number | Per page (default 50, max 200) |
offset | number | Pagination offset |
{ "results": [ { ...event } ], "total": 142 } /api/orgs/:org/projects/:project/logs/:id Session or API Key Fetch a single log event by ID. Returns 404 if not found.
/api/orgs/:org/projects/:project/services Session or API Key Returns distinct service names. Also available: /device-types and /environments.
{ "services": ["auth-worker", "billing-api", "flutter-app"] } Facets
/api/orgs/:org/projects/:project/facets Session or API Key Get value counts for key fields (level, type, service, environment, device_type), scoped to the same filters as the logs endpoint. Powers the faceted sidebar in Log Explorer.
Accepts all the same query parameters as the logs endpoint.
{
"facets": {
"level": [{ "value": "error", "count": 142 }, { "value": "warn", "count": 89 }],
"type": [{ "value": "crash", "count": 23 }],
"service": [ ... ],
"environment": [ ... ],
"device_type": [ ... ]
}
} Patterns
/api/orgs/:org/projects/:project/patterns Session or API Key Group logs by normalizing variable parts of messages (IDs, numbers, UUIDs) into placeholders. Useful for identifying recurring issues without noise.
{
"patterns": [
{
"pattern": "NullPointerException in {id}",
"level": "error",
"type": "crash",
"count": 47,
"sample": "NullPointerException in UserService user_abc123",
"services": ["auth-worker", "billing-api"],
"first_seen": 1709300000000,
"last_seen": 1709312400000
}
]
} AI Insights
On-demand AI summaries of recent log activity powered by Workers AI (@cf/meta/llama-3-8b-instruct), plus persistent pattern detection refreshed automatically every 5 minutes by a background cron.
/api/orgs/:org/projects/:project/insights/summary Session Generate a plain-English summary of recent log activity for a given time window. Aggregates event counts, top services, and error patterns — then asks Workers AI to summarize. The result is stored for 7 days.
Request
{ "minutes": 15 } Valid values: 5, 15, 30, 60. Defaults to 15 if omitted.
Response 200
{
"id": "01JNXYZ...",
"summary_text": "In the last 15 minutes, 142 events were logged...",
"error_count": 12,
"warn_count": 34,
"event_count": 142,
"created_at": 1741234567890,
"time_window_minutes": 15
} 503 if the Workers AI binding is unavailable./api/orgs/:org/projects/:project/insights/summary Session Returns the most recent stored AI summary for the project, or null if none has been generated yet.
{ "summary": { ...summary row } | null } /api/orgs/:org/projects/:project/insights/patterns Session List persistent log patterns stored by the background cron. Patterns are normalized (IDs, numbers, and UUIDs replaced with placeholders) and deduplicated by hash. Updated every 5 minutes automatically.
Query Parameters
| Param | Description |
|---|---|
limit | Max rows (default 50, max 200) |
level | Filter by level: error, warn, info, debug |
service | Filter by service name (exact match) |
Response
{
"patterns": [
{
"id": "...",
"normalized_message": "Connection timeout after {num}ms",
"level": "error",
"service": "api",
"first_seen": 1741230000000,
"last_seen": 1741234500000,
"occurrence_count": 47,
"is_new": false
}
]
} is_new is true when first_seen is within the last hour — useful for surfacing newly emerging error patterns.Lenses (Saved Views)
Named, color-coded filter presets for the Log Explorer. Appear in the sidebar, lens bar, and command palette.
/api/orgs/:org/projects/:project/lenses Session or API Key List all saved lenses ordered by sort_order.
{
"lenses": [
{
"id": "01HQXYZ...", "name": "Prod Errors",
"filters": "{"level":"error","env":"production"}",
"color": "red", "sort_order": 0
}
]
} /api/orgs/:org/projects/:project/lenses Session or API Key Create a new lens. Color options: accent, red, orange, yellow, green, blue.
{ "name": "Prod Errors", "filters": "{...}", "color": "red" } /api/orgs/:org/projects/:project/lenses/:id Session or API Key Update name, filters, color, or sort order. All fields optional.
/api/orgs/:org/projects/:project/lenses/:id Session or API Key Delete a lens by ID.
Ingest Traces — Custom JSON
Send spans for distributed trace correlation. Spans are held for 60 seconds then tail-sampled — errors and slow traces (>2s) are retained for 24 hours; the rest are purged.
/api/traces API Key Submit a batch of spans. Max 50 per request. The trace row is derived automatically from the root span (no parent_span_id).
Request
{
"spans": [
{
"trace_id": "abc123",
"span_id": "span001",
"parent_span_id": null,
"name": "HTTP GET /api/users",
"service": "api-gateway",
"start_time": 1709312400000,
"end_time": 1709312400142,
"status": "ok",
"attributes": {
"http.method": "GET",
"http.url": "/api/users",
"http.status_code": 200
},
"environment": "production"
},
{
"trace_id": "abc123",
"span_id": "span002",
"parent_span_id": "span001",
"name": "DB SELECT users",
"service": "db-service",
"start_time": 1709312400020,
"end_time": 1709312400100,
"status": "ok"
}
]
} Span Fields
| Field | Type | Required | Description |
|---|---|---|---|
trace_id | string | Yes | Shared ID for all spans in the trace |
span_id | string | Yes | Unique ID for this span |
parent_span_id | string | No | Parent span ID; omit for root spans |
name | string | Yes | Operation name |
service | string | Yes | Originating service name |
start_time | number | Yes | Unix ms |
end_time | number | Yes | Unix ms |
status | string | No | ok (default) or error |
attributes | object | No | http.method, http.url, http.status_code indexed on the trace row |
environment | string | No | e.g. production |
Response 202
{ "status": 202, "inserted": 2 } Ingest Traces — OTLP
Compatible with any OpenTelemetry SDK exporter. Timestamps in nanoseconds are converted automatically.
/api/traces/otlp API Key Submit spans in OTLP/JSON format. Service name is extracted from resource.attributes["service.name"]. OTel status code 2 maps to error.
SDK Configuration
// OpenTelemetry JS / Python / Go / etc.
endpoint: "https://api.scrywatch.com/api/traces/otlp"
headers: { "Authorization": "Bearer YOUR_API_KEY" }
protocol: "http/json" Example Payload
{
"resourceSpans": [{
"resource": {
"attributes": [{ "key": "service.name", "value": { "stringValue": "api-gateway" } }]
},
"scopeSpans": [{
"spans": [{
"traceId": "abc123",
"spanId": "span001",
"name": "HTTP GET /api/users",
"startTimeUnixNano": "1709312400000000000",
"endTimeUnixNano": "1709312400142000000",
"status": { "code": 1 }
}]
}]
}]
} Query Traces
/api/orgs/:org/projects/:project/traces Session or API Key List sampled traces. Only traces that passed tail-based sampling are returned.
Query Parameters
| Param | Description |
|---|---|
from / to | Time range in ms |
service | Filter by root service |
status | ok or error |
environment | Exact match |
min_duration / max_duration | Duration range in ms |
limit / offset | Pagination (default limit 50) |
{
"traces": [
{
"id": "abc123", "name": "HTTP GET /api/users",
"service": "api-gateway", "start_time": 1709312400000,
"duration_ms": 142, "status": "ok", "span_count": 4
}
],
"total": 87
} /api/orgs/:org/projects/:project/traces/:traceId Session or API Key Fetch a single trace with all its spans for rendering a waterfall view.
{
"trace": { "id": "abc123", "name": "...", "duration_ms": 142, "status": "ok", ... },
"spans": [
{ "id": "span001", "parent_span_id": null, "name": "HTTP GET /api/users", "duration_ms": 142, ... },
{ "id": "span002", "parent_span_id": "span001", "name": "DB SELECT users", "duration_ms": 80, ... }
]
} Service Map
/api/orgs/:org/projects/:project/traces/service-map Session or API Key Per-service throughput and latency metrics derived from sampled traces. Defaults to the last hour.
Query Parameters
from and to timestamps in ms.
{
"services": [
{ "service": "api-gateway", "request_count": 1204, "error_count": 12, "p50_ms": 45, "p95_ms": 320 },
{ "service": "db-service", "request_count": 987, "error_count": 0, "p50_ms": 8, "p95_ms": 42 }
]
} p50_ms uses AVG(duration_ms) (not a true median). p95_ms uses MAX(duration_ms) (not a true 95th percentile). For exact percentiles use an external OLAP store.Stats
/api/orgs/:org/projects/:project/stats Session or API Key Event counts grouped by level, type, and hour. Defaults to the last 24 hours. Accepts from and to ms timestamps.
{
"stats": [{ "level": "error", "type": "crash", "count": 12, "hour": 1709308800000 }],
"from": 1709226000000,
"to": 1709312400000
} Sessions
/api/orgs/:org/projects/:project/sessions Session or API Key List unique sessions with event counts and severity breakdowns, ordered by most recent activity.
Query Parameters
limit (default 50, max 200), offset, environment.
{
"sessions": [
{
"session_id": "sess_abc", "event_count": 47,
"first_event": 1709300000000, "last_event": 1709312400000,
"error_count": 3, "warn_count": 7, "crash_count": 1
}
]
} Alerts
Alert rules trigger notifications when log events matching a condition exceed a threshold within a rolling time window. Supports webhook delivery (Slack, Discord, any HTTP endpoint) and email.
/api/orgs/:org/projects/:project/alerts Session or API Key List all alert rules.
{
"rules": [
{
"id": "...", "name": "High Error Rate",
"condition_field": "level", "condition_value": "error",
"threshold": 10, "window_minutes": 5, "cooldown_minutes": 15,
"notify_type": "webhook", "webhook_url": "https://hooks.slack.com/...",
"notify_email": null, "enabled": true
}
]
} /api/orgs/:org/projects/:project/alerts Session or API Key Create a new alert rule. Use condition_field + condition_value to match against any log field.
Fields
| Field | Required | Description |
|---|---|---|
name | Yes | Display name for the rule |
condition_field | Yes | Log field to match: level, type, service, environment, device_type |
condition_value | Yes | Value to match, e.g. error, crash, auth-worker |
threshold | Yes | Number of matching events to trigger |
window_minutes | Yes | Rolling evaluation window in minutes |
notify_type | No | webhook (default) or email |
webhook_url | If webhook | URL to POST the alert payload to |
notify_email | If email | Email address to notify |
cooldown_minutes | No | Minimum minutes between repeated firings (default 15) |
Example — webhook on all errors
{
"name": "High Error Rate",
"condition_field": "level",
"condition_value": "error",
"threshold": 10,
"window_minutes": 5,
"notify_type": "webhook",
"webhook_url": "https://hooks.slack.com/..."
} Example — email on crashes from auth service
{
"name": "Auth Crashes",
"condition_field": "service",
"condition_value": "auth-worker",
"threshold": 1,
"window_minutes": 15,
"notify_type": "email",
"notify_email": "[email protected]"
} /api/orgs/:org/projects/:project/alerts/:id Session or API Key Update any alert rule field. All fields optional. Use enabled: false to pause a rule without deleting it.
/api/orgs/:org/projects/:project/alerts/:id Session or API Key Delete an alert rule.
/api/orgs/:org/projects/:project/alerts/history Session or API Key Recent alert firings with timestamps, matched event count, and webhook delivery status.
Archive
/api/orgs/:org/projects/:project/archive Session or API Key Retrieve events from R2 cold storage by date. All log filters work identically against the archive.
Query Parameters
| Param | Required | Description |
|---|---|---|
date | Yes | YYYY-MM-DD |
hour | No | Specific hour (0–23) |
level, type, service, environment, q | No | Same as logs endpoint |
Live Tail (WebSocket)
/api/orgs/:org/projects/:project/ws/tail Session or API Key Real-time log streaming via WebSocket. Events are broadcast as JSON arrays as they are ingested.
const ws = new WebSocket(
'wss://api.scrywatch.com/api/orgs/acme-inc/projects/default/ws/tail'
);
ws.onmessage = (e) => {
const events = JSON.parse(e.data);
console.log('Live events:', events);
}; Settings
/api/orgs/:org/projects/:project/settings/profile Session or API Key Get or update the authenticated user's name and password.
/api/orgs/:org/projects/:project/settings/api-keys Session or API Key List API keys (masked) or create a new one. The full key is returned only once — store it immediately.
// POST request
{ "name": "Production Key" }
// POST response (201)
{ "id": "...", "key": "sw_a1b2c3d4e5f6...", "name": "Production Key" } /api/orgs/:org/projects/:project/settings/api-keys/:id Session or API Key Revoke an API key immediately.
/api/orgs/:org/projects/:project/settings/purge-logs Session or API Key Danger Zone. Permanently deletes all log events from hot storage. Archived R2 data is not affected.
Event Schema Reference
{
"id": "string — ULID, auto-generated if omitted",
"timestamp": "number — Unix ms, required",
"level": "string — error | warn | info | debug",
"type": "string — crash | session | navigation | api_call | custom | cron",
"message": "string — required",
"service": "string? — e.g. auth-worker",
"environment": "string? — e.g. production",
"user_id": "string? — optional",
"session_id": "string? — groups events into a session",
"device_type": "string? — auto-detected from User-Agent if omitted",
"trace_id": "string? — links log to a distributed trace",
"span_id": "string? — links log to a specific span",
"metadata": "object? — arbitrary JSON payload"
} device_type in payload,
(2) User-Agent header (classified as mobile/tablet/desktop),
(3) Flutter SDK auto-detection (ios/android/macos/windows/linux).
Flutter SDK
Located at flutter/lib/log_client.dart in the repository.
Setup
final logger = LogClient(
endpoint: 'https://api.scrywatch.com',
apiKey: 'your-api-key',
environment: 'production',
service: 'my-flutter-app',
// deviceType auto-detected (ios, android, macos, etc.)
); Usage
logger.startSession();
logger.setUserId('user_123');
logger.log(LogLevel.info, 'User opened settings');
logger.logNavigation('SettingsScreen');
logger.logApiCall('GET', '/api/profile', 200, 142);
logger.logError(error, stackTrace);
logger.endSession(); // flushes buffer
logger.dispose(); Quick cURL test
curl -X POST https://api.scrywatch.com/api/ingest \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": [{
"timestamp": '$(date +%s000)',
"level": "info",
"type": "custom",
"message": "Hello from curl",
"service": "test-app"
}]
}' JavaScript / TypeScript SDK
Zero-dependency SDK for Node.js, Deno, Bun, and browsers. Located at packages/sdk/.
Install
npm install @scrywatch/sdk Setup
import { LogClient } from '@scrywatch/sdk';
const logger = new LogClient({
endpoint: 'https://api.scrywatch.com',
apiKey: 'your-api-key',
environment: 'production',
service: 'my-node-app',
}); Usage
logger.startSession();
logger.setUserId('user_123');
logger.info('Server started on port 3000');
logger.warn('Slow query detected', { duration_ms: 2300 });
logger.error('Payment failed', { order_id: 'ord_456' });
logger.logNavigation('/dashboard');
logger.logApiCall('POST', '/api/checkout', 500, 340);
logger.endSession();
await logger.dispose(); // flush + stop timers