Skip to content

Timber

Timber is the project log trail for Protokol. It shows user-facing platform events emitted by services and accepts custom application logs.

Timber is available as the protokol-timber integration with three tiers:

Tier Retention Custom Logs Usage Metering
Free 7 days No No
Standard 30 days Yes Yes
Pro 90 days Yes Yes

Query Events

GET /v1/timber/logs

Query parameters:

Parameter Description
limit Number of events to return (default 50, max 500).
cursor Cursor from meta.next_cursor for pagination.
source system or user.
level debug, info, warn, or error.
action Event action such as create, update, delete, execute, deploy.
resource_type Resource family, for example workflow, media_file, runtime.
resource_id Resource identifier.
actor_id User, API key, or service actor id.
actor_name Actor display name (partial match).
actor_type user, api_key, service, or system.
request_uuid Correlate events to a specific request.
search Text search over message, resource, and actor fields.
from / to RFC3339 time range.

cURL

curl "https://timber.integration.protokol.cloud/logs?source=system&level=info&limit=50" \
  -H "Authorization: Bearer <your-api-token>" \
  -H "X-Project-Uuid: <your-project-uuid>"

Platform Functions

const { Timber } = $sdk.version('0.10')
const timber = new Timber()

const result = await timber.query({
  source: 'system',
  level: 'info',
  limit: 50,
})

response.json(result)

SDK

import { Timber } from "@ptkl/sdk/beta"

const timber = new Timber()

const result = await timber.query({
  source: 'system',
  level: 'error',
  from: new Date('2026-05-01'),
  to: new Date('2026-05-27'),
  limit: 100,
})

// Pagination
if (result.meta.has_more) {
  const page2 = await timber.query({ cursor: result.meta.next_cursor })
}

Response

{
  "data": [
    {
      "id": "01J...",
      "timestamp": "2026-05-24T12:00:00Z",
      "source": "system",
      "level": "info",
      "service": "workflow",
      "project_uuid": "project-uuid",
      "actor": { "type": "user", "id": "user-uuid", "name": "John" },
      "action": "execute",
      "resource_type": "workflow",
      "resource_id": "workflow-uuid",
      "message": "Workflow executed"
    }
  ],
  "meta": {
    "limit": 50,
    "has_more": false,
    "next_cursor": ""
  }
}

Write Custom Logs

POST /v1/timber/logs

Custom logs require Timber Standard or Pro. Payloads must be 64 KB or smaller.

Field Required Description
message yes Log body. Maximum 10,000 characters.
level no debug, info, warn, or error. Defaults to info.
service no Service name. Defaults to user-app.
resource_type no Resource family.
resource_id no Resource identifier.
resource_name no Human-readable name.
attributes no Up to 64 custom key-value pairs.
trace_id, span_id, request_id no Correlation identifiers.

cURL

curl "https://timber.integration.protokol.cloud/logs" \
  -X POST \
  -H "Authorization: Bearer <your-api-token>" \
  -H "X-Project-Uuid: <your-project-uuid>" \
  -H "Content-Type: application/json" \
  -d '{
    "level": "info",
    "service": "checkout",
    "message": "Order confirmation sent",
    "resource_type": "order",
    "resource_id": "ord_123",
    "attributes": {
      "channel": "email"
    }
  }'

Platform Functions

const { Timber } = $sdk.version('0.10')
const timber = new Timber()

// Convenience methods — debug(), info(), warn(), error()
await timber.info('Order confirmation sent', {
  service: 'checkout',
  resource_type: 'order',
  resource_id: $input.body.order_id,
  attributes: { channel: 'email' },
})

// Or use write() with an explicit level
await timber.write({
  level: 'warn',
  message: 'Payment retry scheduled',
  service: 'billing',
  attributes: { attempt: 3 },
})

response.json({ ok: true })

SDK

import { Timber } from "@ptkl/sdk/beta"

const timber = new Timber()

await timber.info('User exported report', {
  service: 'reports',
  resource_type: 'report',
  resource_id: 'rpt_456',
  attributes: { format: 'pdf', pages: 42 },
})

await timber.error('Payment failed', {
  service: 'billing',
  attributes: { reason: 'card_declined', provider: 'stripe' },
})

Usage

GET /v1/timber/usage

Returns current Timber tier and metered custom log bytes for the project.

Platform Functions

const { Timber } = $sdk.version('0.10')
const timber = new Timber()

const usage = await timber.usage()
response.json(usage)

Response

{
  "tier": "standard",
  "period": "2026-05",
  "bytes_ingested": 32768,
  "gb_ingested": 0.000030517578125
}

CLI

Timber logs are also available via the Protokol Toolkit CLI:

# Query recent logs
ptkl logs

# Filter by level and search
ptkl logs --level error --search "payment failed"

# Tail mode — poll for new events
ptkl logs -t

# Output as JSON for piping to jq
ptkl logs --json | jq '.message'
Flag Description
--source <source> Filter by system or user.
--level <level> Filter by debug, info, warn, or error.
--action <action> Filter by action.
--resource-type <type> Filter by resource type.
-s, --search <text> Search messages, actors, or resources.
-l, --limit <n> Number of logs to fetch (default 50).
-t, --tail Poll and print new logs continuously.
--interval <seconds> Tail polling interval (default 3).
--json Output as newline-delimited JSON.

OpenTelemetry (OTLP)

Timber accepts logs in the OTLP JSON format, so you can point any OpenTelemetry SDK or Collector at Timber for log ingestion.

POST /v1/timber/logs/otlp

Requires Timber Standard or Pro. Maximum payload size is 1 MB.

The endpoint accepts the standard OTLP Logs JSON structure. Timber maps the fields as follows:

OTLP field Timber field
resource.attributes["service.name"] service
body.stringValue message
severityNumber / severityText level
attributes attributes
traceId trace_id
spanId span_id
timeUnixNano timestamp

Example

curl "https://timber.integration.protokol.cloud/logs/otlp" \
  -X POST \
  -H "Authorization: Bearer <your-api-token>" \
  -H "X-Project-Uuid: <your-project-uuid>" \
  -H "Content-Type: application/json" \
  -d '{
    "resourceLogs": [{
      "resource": {
        "attributes": [
          { "key": "service.name", "value": { "stringValue": "checkout" } }
        ]
      },
      "scopeLogs": [{
        "logRecords": [
          {
            "timeUnixNano": "1716825600000000000",
            "severityNumber": 9,
            "body": { "stringValue": "Order confirmed" },
            "attributes": [
              { "key": "order_id", "value": { "stringValue": "ord_123" } }
            ],
            "traceId": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
            "spanId": "1a2b3c4d5e6f7a8b"
          }
        ]
      }]
    }]
  }'

OpenTelemetry SDK Configuration

To send logs from an OTEL-instrumented application, configure the OTLP exporter to point at Timber:

export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://timber.integration.protokol.cloud/logs/otlp
export OTEL_EXPORTER_OTLP_LOGS_HEADERS="Authorization=Bearer <your-api-token>,X-Project-Uuid=<your-project-uuid>"
export OTEL_EXPORTER_OTLP_LOGS_PROTOCOL=http/json

Only log collection is supported. Traces and metrics should be sent to your own observability backend.


Retention

Expired events are automatically cleaned up daily based on your project tier. No manual action is needed — events older than the retention window are permanently deleted.