Send telemetry in minutes.

Bolt Sight ingests standard OpenTelemetry (OTLP) over HTTP. Create a project and an ingest key, point an exporter at one endpoint and add one header.

Quickstart

Five steps from nothing to a span in the console. The base URL for every example is https://bsight.techsstuff.com.

  1. Create an account and a project. Sign up with a name, email, password and a name for your first project, or continue with Google. The project holds your telemetry, keys, members and credits.
  2. Check the project’s credits. Ingest and queries draw on prepaid credits. A project with none available is blocked until a pack is bought: ingest answers 429 and console queries answer 402. Buy from the project’s Billing page; see pricing.
  3. Create an ingest key. Open Settings › API Keys and create one key per producer. The key is shown once. Owners can create keys.
  4. Point your exporter at the endpoint. Set the base URL and the Authorization header; SDKs add /v1/traces, /v1/metrics and /v1/logs themselves.
  5. Look for a 204. The curl request sends one span. Open Investigate and choose the quickstart service to see it. Services appear from service.name; nothing needs registering.
bsight.techsstuff.com
# Send one span. Create the key in the app under Settings › API Keys.
$ BOLT_SIGHT_API_KEY=bsk_your_project_key
# NOW is the current time in nanoseconds
$ NOW=$(( $(date +%s) * 1000000000 )); END=$(( NOW + 120000000 ))
$ curl -sS -o /dev/null -w "%{http_code}\n" -X POST "https://bsight.techsstuff.com/v1/traces" \
    -H "Authorization: Bearer $BOLT_SIGHT_API_KEY" \
    -H "Content-Type: application/json" \
    --data-binary @- <<EOF
  {"resourceSpans":[{
    "resource":{"attributes":[{"key":"service.name","value":{"stringValue":"quickstart"}}]},
    "scopeSpans":[{"spans":[{
      "traceId":"5b8efff798038103d269b633813fc60c","spanId":"eee19b7ec3c1b174",
      "name":"GET /hello","kind":2,
      "startTimeUnixNano":"$NOW","endTimeUnixNano":"$END",
      "status":{"code":1}
    }]}]
  }]}
EOF
204
# Any OpenTelemetry SDK, in any language
OTEL_SERVICE_NAME=billing-worker
OTEL_EXPORTER_OTLP_ENDPOINT=https://bsight.techsstuff.com
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer%20bsk_your_project_key
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,service.version=1.4.2
// Node.js SDK: call init() once, in its own module, before any other import.
// Install steps are on the Developers page in the app.

init({
  endpoint: "https://bsight.techsstuff.com",
  apiKey: process.env.BOLT_SIGHT_API_KEY!, // bsk_…, one key per producer
  serviceName: "checkout-api",
  serviceVersion: process.env.APP_VERSION,
  environment: "production",
});

// Flush buffered telemetry before the process exits.
process.on("SIGTERM", () => void shutdown().finally(() => process.exit(0)));
exporter, err := otlptracehttp.New(ctx,
    otlptracehttp.WithEndpointURL("https://bsight.techsstuff.com/v1/traces"),
    otlptracehttp.WithHeaders(map[string]string{
        "Authorization": "Bearer " + os.Getenv("BOLT_SIGHT_API_KEY"),
    }),
)
if err != nil { return err }
tp := sdktrace.NewTracerProvider(
    sdktrace.WithBatcher(exporter),
    sdktrace.WithResource(resource.NewWithAttributes(semconv.SchemaURL,
        semconv.ServiceName("billing-worker"),
    )),
)
otel.SetTracerProvider(tp) // call tp.Shutdown(ctx) on exit
# pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-http
provider = TracerProvider(resource=Resource.create({"service.name": "billing-worker"}))
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(
    endpoint="https://bsight.techsstuff.com/v1/traces",
    headers={"Authorization": f"Bearer {os.environ['BOLT_SIGHT_API_KEY']}"},
)))
trace.set_tracer_provider(provider)

Endpoints

One origin for everything: https://bsight.techsstuff.com. The hosted endpoint accepts OTLP over HTTP; use protobuf (application/x-protobuf) or JSON (application/json), uncompressed or with Content-Encoding: gzip.

Ingest endpoints
EndpointSignalBody
POST /v1/tracesSpansOTLP ExportTraceServiceRequest. Server spans, client spans and exception events all arrive here.
POST /v1/metricsMetricsGauge, sum and histogram data points. Exponential histograms and summaries are not stored.
POST /v1/logsLogs and product eventsLog records with severity, body, attributes and optional trace and span IDs.
POST /v1/job-eventsJob lifecycle eventsJSON only, up to 1,000 events per request. See job events.

Authentication and keys

Ingest keys are the only credential an exporter needs.

Ingest key facts
HeaderAuthorization: Bearer bsk_… on every request.
ScopeA key belongs to one project; everything sent with it is stored in that project.
PermissionWrite-only. A key can send telemetry. It cannot read data or sign in.
StorageThe key is shown once when you create it. Only a SHA-256 hash and a short prefix are kept, so a lost key cannot be recovered; create a new one.
RevocationRevoking a key takes effect on the next request. To rotate, deploy the new key first, then revoke the old one.
One per producerCreate a key for each browser app, server runtime and gateway. Rate limits are applied per key and signal, so a noisy producer cannot starve another.
Browser keysA key in a browser bundle is public by design. Give the browser its own key and restrict it with allowed origins. When the list is empty, requests from any origin are accepted.
Who creates keysProject owners and administrators.

More request examples

The same request shape for logs, metrics and job events. A 204 means accepted. Example data only.

bsight.techsstuff.com
# NOW is the current time in nanoseconds
$ NOW=$(( $(date +%s) * 1000000000 )); END=$(( NOW + 120000000 ))
$ curl -sS -o /dev/null -w "%{http_code}\n" -X POST "https://bsight.techsstuff.com/v1/logs" \
    -H "Authorization: Bearer $BOLT_SIGHT_API_KEY" \
    -H "Content-Type: application/json" \
    --data-binary @- <<EOF
  {"resourceLogs":[{
    "resource":{"attributes":[{"key":"service.name","value":{"stringValue":"quickstart"}}]},
    "scopeLogs":[{"logRecords":[{
      "timeUnixNano":"$NOW","severityNumber":17,"severityText":"ERROR",
      "body":{"stringValue":"payment failed: card declined"},
      "traceId":"5b8efff798038103d269b633813fc60c","spanId":"eee19b7ec3c1b174"
    }]}]
  }]}
EOF
204
# NOW is the current time in nanoseconds
$ NOW=$(( $(date +%s) * 1000000000 )); END=$(( NOW + 120000000 ))
$ curl -sS -o /dev/null -w "%{http_code}\n" -X POST "https://bsight.techsstuff.com/v1/metrics" \
    -H "Authorization: Bearer $BOLT_SIGHT_API_KEY" \
    -H "Content-Type: application/json" \
    --data-binary @- <<EOF
  {"resourceMetrics":[{
    "resource":{"attributes":[{"key":"service.name","value":{"stringValue":"quickstart"}}]},
    "scopeMetrics":[{"metrics":[{
      "name":"quickstart.queue.depth",
      "gauge":{"dataPoints":[{
        "timeUnixNano":"$NOW","asDouble":42,
        "attributes":[{"key":"queue","value":{"stringValue":"invoices"}}]
      }]}
    }]}]
  }]}
EOF
204
$ curl -sS -o /dev/null -w "%{http_code}\n" -X POST "https://bsight.techsstuff.com/v1/job-events" \
    -H "Authorization: Bearer $BOLT_SIGHT_API_KEY" \
    -H "Content-Type: application/json" \
    --data-binary @- <<EOF
  {"events":[{
    "schema_version":"1.0.0",
    "event_id":"0b9f6f2e-8a53-4c8e-9c3f-3f2d1f3a9b11",
    "source_timestamp":"2026-09-30T10:15:00Z",
    "source_system":"bullmq","source_service":"billing-worker",
    "environment":"production","queue_name":"invoices","job_name":"send-invoice",
    "job_id":"4821","job_run_id":"4821-1","attempt":1,"state":"completed"
  }]}
EOF
204

Response codes

Every ingest response is an HTTP status. Retry 429 and 503 with backoff; fix the request for the others.

OTLP/HTTP response codes
StatusMeaning and what to do
204Accepted.
400The body is not valid OTLP, or its gzip stream is corrupt. Send protobuf or JSON, optionally gzip-compressed. JSON trace and span IDs are hex strings.
401Missing, unknown or revoked API key. Check the Authorization: Bearer header and the key.
403The browser origin is not in the project’s allowed origins. Add the exact origin, for example https://app.example.com.
413Body over 4 MB, before or after decompression, or more than 10,000 records. Lower the exporter batch size.
415Unsupported Content-Encoding. Use gzip or no compression.
429Ingest rate limit reached, or ingest blocked because the project has no credits. Retry after the Retry-After header; check the project’s Billing page if it persists.
503Temporarily unavailable. Retry with backoff.

Limits

What the receiver enforces. Data over a per-record limit is bounded or dropped, not rejected.

Ingest limits
Per request4 MB after decompression and 10,000 records.
Per recordUp to 64 attributes (the rest are dropped), attribute keys up to 128 bytes, attribute values up to 1 KB, log bodies up to 8 KB, span, service and metric names up to 256 bytes, up to 64 events and 64 links per span.
Rate, per key and signalDefaults per minute: 240 requests or 64 MiB. Requests with the same key and signal share a lane; traces, metrics and logs use separate lanes.
Rate, per projectDefaults per minute: 2,880 requests or 768 MiB across all keys and signals.
Keys and originsUp to 50 ingest keys and 20 allowed origins (each up to 256 characters) per project.
Job eventsUp to 1,000 per request; JSON over HTTP only.
Browser SDKA per-tab cap on new traces: 60 per minute, a burst of 20 and 300 per page lifetime by default; values can only be lowered.

SDKs

The Node.js and browser packages are conveniences; standard OpenTelemetry works without them. Install steps are on the Developers page in the app, after you sign in. Email support@techsstuff.com if you cannot reach it.

SDKs
SDKWhat it doesNotes
Node.jsA thin wrapper around the OpenTelemetry Node SDK. init() configures trace, metric and log exporters, automatic instrumentation for HTTP and common frameworks and databases, and turns console output into log records with the active trace ID. shutdown() and forceFlush() flush before exit or at the end of a serverless call.Options include endpoint, apiKey, serviceName, serviceVersion, environment, enabled, debug, sampler, propagation, an attribute policy and per-package instrumentation overrides.
BrowserinitBrowser() reports Web Vitals, page-load spans and XMLHttpRequest spans. tracedFetch traces only requests that match an exact origin, method and path policy. Uncaught errors are opt-in, and error messages and stack traces are off by default.Options include endpoint, apiKey, serviceName, sample rate, root-span budget, ignore URLs and error capture.
React and Next.jsA useBoltSight() hook, navigation spans reduced to your route templates so IDs in URLs never reach telemetry, error reporting, and Next.js (App Router) support.Same options as the browser SDK.
Any other languageUse the OpenTelemetry SDK of the language with the endpoint and header above. No Bolt Sight package is needed.Go, Python, Java, .NET, Ruby, PHP or a Collector. See Integrations.

Webhooks

Add a webhook channel under Alerts › Channels and attach it to alert rules. Bolt Sight sends a JSON POST when a rule fires and again when it resolves.

payload.json
{
  "rule_name": "Checkout p95 latency",
  "description": "p95 above 800 ms for 5 minutes",
  "severity": "critical",
  "state": "firing",
  "value": 912.4,
  "fired_at": "2026-09-30T10:30:00Z",
  "labels": { "service": "checkout-api" },
  "annotations": {}
}
  • severity is info, warning or critical; state is firing or resolved.
  • value, resolved_at, labels and annotations are included when available.

Each request carries X-Webhook-Signature: sha256=<hex>, an HMAC-SHA256 of the raw body with the channel’s signing secret. The secret is generated when you create the channel (or you can supply your own, at least 16 characters) and is shown only once. Verify against the raw bytes: parsing and re-serializing the JSON changes them. The Node.js SDK also ships a verification helper.

import hashlib, hmac

def verify(body: bytes, signature: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(signature, expected)
func verify(body []byte, signature, secret string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(body)
    expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(signature), []byte(expected))
}

Product events

Emit a log record whose body is the event name and put its dimensions in attributes, from the browser SDK or from any OpenTelemetry logs SDK. The Events page understands these names.

Product event names
Event (log body)AttributesUsed for
section_impressionsection.id, section.positionSection click-through
section_clicksection.id, section.positionSection click-through
upsell_clickupsell.surfaceFunnel
upsell_conversionupsell.surface, conversion.type, payment.amount, payment.currencyFunnel and revenue
rec_impressionrec.sourceRecommendations
rec_clickrec.sourceRecommendations
rec_watch_throughrec.sourceRecommendations
events.ts
// Browser SDK: a product event is a log record whose body is the event name.
getLogger("product-events").emit({
  body: "upsell_conversion",
  attributes: {
    "upsell.surface": "paywall",
    "conversion.type": "subscription",
    "payment.amount": "9.99",   // decimal string
    "payment.currency": "USD",  // ISO 4217, upper case
  },
});

Job events

The Jobs page follows each run of a background job through its states. Post a JSON event for every transition (enqueued, active, retry_scheduled, completed, failed, stalled and others) to /v1/job-events, up to 1,000 per request; the request examples include one. Add trace_id and span_id to link a run to its trace. source_system accepts bullmq only today; job events are not accepted over gRPC.

Troubleshooting

Troubleshooting
Nothing shows upConfirm the endpoint and key with the curl request, then check the time range in the top bar. Data appears within a few seconds of export.
Browser requests fail with CORS or 403Add the page’s exact origin to the project’s allowed origins.
Frontend and backend spans are separate tracesYour API must accept the traceparent header (CORS Access-Control-Allow-Headers), and the call must go through tracedFetch or XMLHttpRequest.
A Node.js service has no spansThe instrumentation module must be imported before any other module.
Telemetry stops after a whileCheck the project’s credits on the Billing page. With none available, ingest answers 429.
Still stuckSet debug: true in the Node.js SDK to print exporter errors to the console, then contact us with the status code and what you expected.

Create a project and send your first span.