Developer reference

Sceneblue API

A versioned REST API and real-time WebSocket feed for the full indicator catalog — current values, signal strength, and history — built to drop into your models, dashboards, and execution systems.

Overview

The Sceneblue API is organized around REST: predictable resource-oriented URLs, JSON request and response bodies, and standard HTTP verbs, status codes, and authentication. All traffic is served over HTTPS — plain HTTP requests are rejected.

Base URL

https://api.sceneblue.com/v1
Quick start
curl https://api.sceneblue.com/v1/indicators \
  -H "Authorization: Bearer $SCENEBLUE_API_KEY"

Conventions

  • Request and response bodies are JSON, encoded as UTF-8.
  • All timestamps are ISO-8601 in UTC (e.g. 2026-05-29T13:30:00Z).
  • Indicator value and strength are normalized to a 0–100 scale; change_pct is a percentage (not a ratio).
  • List endpoints return a wrapper object with object: "list", a data array, and pagination fields. Single resources carry an object discriminator (e.g. "indicator").
  • We may add new fields and event types without notice; treat unknown fields as non-breaking and ignore them.

Authentication

Authenticate every request with an API key sent as a bearer token. Keys are provisioned per desk by our team — there is no self-service signup. A missing or invalid key returns 401 Unauthorized; a key not scoped to the requested indicators returns 403 Forbidden.

Request header
Authorization: Bearer $SCENEBLUE_API_KEY
  • Each desk gets separate live (sk_live_…) and test (sk_test_…) keys. Test keys hit the sandbox.
  • Keys are secrets — never expose them in browser code or commit them to source control. The API does not enable CORS for secret keys.
  • Rotate keys on demand from the desk console; the previous key stays valid for a 24-hour grace window.

Versioning & deprecation

The API is versioned in the URL path (/v1). Within a version we only make additive, backward-compatible changes. When a resource or field is slated for removal, affected responses include Deprecation and Sunset headers, and we give written notice at least 90 days before the sunset date.

Response headers (deprecated resource)
Deprecation: true
Sunset: Wed, 31 Dec 2026 23:59:59 GMT
Link: <https://docs.sceneblue.com/changelog>; rel="deprecation"

Errors

Sceneblue uses conventional HTTP status codes. Errors return a consistent envelope with a machine-readable type and code, a human-readable message, the offending param where applicable, a request_id, and a doc_url.

Error response · 404
{
  "error": {
    "type": "invalid_request_error",
    "code": "indicator_not_found",
    "message": "No indicator exists with id 999.",
    "param": "id",
    "request_id": "req_0Kd9Qa2mP7xViT8",
    "doc_url": "https://docs.sceneblue.com/errors/indicator_not_found"
  }
}

Error types

authentication_error
401 / 403
API key missing, invalid, or out of scope.
invalid_request_error
400 / 404 / 422
A parameter is missing, malformed, or rejected.
rate_limit_error
429
Too many requests in the current window.
api_error
500 / 503
Something went wrong on our side.
StatusMeaningNotes
200OKRequest succeeded.
304Not ModifiedResource unchanged since your cached ETag.
400Bad RequestMalformed request or invalid parameter.
401UnauthorizedMissing or invalid API key.
403ForbiddenKey is valid but not scoped to this resource.
404Not FoundNo resource matches the requested id.
409ConflictIdempotency key reused with a different payload.
422UnprocessableParameters valid in shape but semantically rejected.
429Too Many RequestsRate limit exceeded — back off and retry.
500Server ErrorUnexpected error on our side; safe to retry.
503Service UnavailableTemporarily down or in maintenance; retry with backoff.

Rate limits

Every response carries your current rate-limit state in headers. On a 429, honor Retry-After and back off exponentially with jitter.

Response headers
RateLimit-Limit: 600
RateLimit-Remaining: 598
RateLimit-Reset: 41
Retry-After: 2
Error response · 429
{
  "error": {
    "type": "rate_limit_error",
    "code": "too_many_requests",
    "message": "Request rate exceeded. Retry after 1.2s.",
    "request_id": "req_0Kd9Qa2mP7xViT8"
  }
}

Exact request rates, concurrent streams, and history depth are scoped per desk as part of your engagement — there are no fixed published tiers. Contact sales to set yours.

Pagination

List endpoints are cursor-paginated. Pass limit to size the page and cursor to fetch the next one. Responses include has_more and next_cursor, and mirror the cursor in a Link header. Cursors are opaque and stable under inserts and deletes — prefer them over offsets.

Query parameters

limit
integer
Page size, 1–100. Defaults to 25.
cursor
string
Opaque cursor returned as next_cursor on the previous page.
Response · 200 OK
{
  "object": "list",
  "url": "/v1/indicators",
  "has_more": true,
  "next_cursor": "ind_eyJpZCI6Mn0",
  "data": [
    {
      "object": "indicator",
      "id": 1,
      "name": "Gasoline Tightness Index",
      "value": 62.84,
      "strength": 51,
      "change_pct": 4.7,
      "as_of": "2026-05-29T13:30:00Z"
    },
    {
      "object": "indicator",
      "id": 2,
      "name": "Regional Gasoline Tightness Index",
      "value": 71.07,
      "strength": 68,
      "change_pct": 4.13,
      "as_of": "2026-05-29T13:30:00Z"
    }
  ]
}
Link header
Link: <https://api.sceneblue.com/v1/indicators?cursor=ind_eyJpZCI6Mn0&limit=2>; rel="next"
Auto-paginate (Python)
cursor = None
while True:
    page = client.indicators.list(limit=100, cursor=cursor)
    for indicator in page.data:
        handle(indicator)
    if not page.has_more:
        break
    cursor = page.next_cursor

Filtering, sorting & field expansion

Query parameters · GET /v1/indicators

category
string
Filter by group, e.g. tightness, disruption, freight, arbitrage.
sort
string
Sort key with optional leading '-' for descending, e.g. -strength.
expand
string[]
Inline related data, e.g. expand=latest,inputs.
curl "https://api.sceneblue.com/v1/indicators?category=freight&sort=-strength&expand=latest" \
  -H "Authorization: Bearer $SCENEBLUE_API_KEY"

Conditional requests & caching

Responses include an ETag and a Cache-Control header. Send the ETag back as If-None-Match; if the resource is unchanged you get a 304 Not Modified with no body, which does not count against your read quota.

curl https://api.sceneblue.com/v1/indicators/1 \
  -H "Authorization: Bearer $SCENEBLUE_API_KEY" \
  -H 'If-None-Match: "a1b2c3d4"'

# → HTTP/1.1 304 Not Modified

Idempotency & retries

All GET requests are safe and idempotent — retry them freely. For state-changing calls (creating a webhook endpoint or an alert subscription), pass an Idempotency-Key so a retried request after a network blip is processed at most once. Reusing a key with a different body returns 409 Conflict; keys are retained for 24 hours.

curl https://api.sceneblue.com/v1/webhook_endpoints \
  -H "Authorization: Bearer $SCENEBLUE_API_KEY" \
  -H "Idempotency-Key: 8f3a2c1e-..." \
  -d url=https://desk.example.com/hooks/sceneblue

Request IDs & tracing

Every response includes a Sceneblue-Request-Id header (also echoed in error bodies as request_id). Log it and include it in support requests so we can trace the exact call.

Sceneblue-Request-Id: req_0Kd9Qa2mP7xViT8

Endpoints

List indicators

GET/v1/indicators

Returns a cursor-paginated list of all 50 indicators with their latest value, signal strength, and day-over-day change. Supports the filtering and pagination parameters above.

Response · 200 OK
{
  "object": "list",
  "url": "/v1/indicators",
  "has_more": true,
  "next_cursor": "ind_eyJpZCI6Mn0",
  "data": [
    {
      "object": "indicator",
      "id": 1,
      "name": "Gasoline Tightness Index",
      "value": 62.84,
      "strength": 51,
      "change_pct": 4.7,
      "as_of": "2026-05-29T13:30:00Z"
    },
    {
      "object": "indicator",
      "id": 2,
      "name": "Regional Gasoline Tightness Index",
      "value": 71.07,
      "strength": 68,
      "change_pct": 4.13,
      "as_of": "2026-05-29T13:30:00Z"
    }
  ]
}

Retrieve an indicator

GET/v1/indicators/{id}

Path parameters

idrequired
integer
Indicator id, 1–50.
Response · 200 OK
{
  "object": "indicator",
  "id": 1,
  "name": "Gasoline Tightness Index",
  "inputs": [
    "gasoline inventories",
    "refinery utilization",
    "product tanker arrivals",
    "imports/exports",
    "RBOB spreads",
    "regional demand proxies"
  ],
  "data_sources": [
    "EIA WPSR",
    "EIA Petroleum & Other Liquids API",
    "EIA historical stocks by PADD/sub-PADD",
    "EIA WPSR refinery utilization",
    "EIA refinery inputs by PADD",
    "NOAA MarineCadastre AIS",
    "AISStream",
    "MarineTraffic/VesselFinder public pages",
    "port authority arrival/departure schedules",
    "terminal vessel lineups",
    "EIA WPSR imports/exports",
    "EIA Company Level Imports",
    "U.S. Census trade data",
    "AIS-derived tanker arrivals/departures",
    "CME RBOB futures settlements",
    "CME historical data",
    "ICE/market-data vendor if licensed",
    "EIA product supplied",
    "FHWA vehicle miles traveled",
    "BTS traffic data",
    "Apple/Google mobility where available",
    "AAA retail prices",
    "NOAA weather/temperature data"
  ],
  "latest": {
    "value": 62.84,
    "strength": 51,
    "change": 2.82,
    "change_pct": 4.7,
    "as_of": "2026-05-29T13:30:00Z"
  }
}

Indicator history

GET/v1/indicators/{id}/history

Query parameters

window
string
Lookback span, e.g. 7d, 90d, 1y. Defaults to 90d.
interval
string
Bucket size, e.g. 1h, 1d, 1w. Defaults to 1d.
start
string
ISO-8601 start bound (alternative to window).
end
string
ISO-8601 end bound. Defaults to now.
Response · 200 OK
{
  "object": "history",
  "id": 1,
  "window": "90d",
  "interval": "1d",
  "points": [
    {
      "day": -2,
      "value": 61.47
    },
    {
      "day": -1,
      "value": 60.02
    },
    {
      "day": 0,
      "value": 62.84
    }
  ]
}

Streaming (WebSocket)

For intraday, push-based updates, connect over WebSocket instead of polling. You receive a message the moment a subscribed indicator re-prints — typically milliseconds after the underlying data lands.

WebSocket URL

wss://api.sceneblue.com/v1/stream

Authenticate with the same API key, then send a subscribe frame. On subscribe you receive a snapshot, then deltas. Every message carries a monotonic seq; if you detect a gap, resync via the REST endpoint. Reply to ping frames with pong (~30s heartbeat), and reconnect with exponential backoff on disconnect.

Connect & subscribe
wscat -c "wss://api.sceneblue.com/v1/stream" \
  -H "Authorization: Bearer $SCENEBLUE_API_KEY"

# then send:
{
  "action": "subscribe",
  "indicators": [
    1,
    11,
    17
  ]
}
Update frame
{
  "type": "update",
  "seq": 10472,
  "id": 11,
  "name": "Crude Supply Disruption Index",
  "value": 52.44,
  "strength": 56,
  "change_pct": 1.06,
  "ts": "2026-05-29T13:30:05Z"
}

Concurrent connections and per-socket subscription counts are set with your desk; larger desks can request a dedicated, co-located feed.

Webhooks

Rather than hold a socket open, you can register an HTTPS endpoint and have Sceneblue POST events to it — for example when an indicator crosses a threshold you define. Endpoints are provisioned with your keys.

Event types

indicator.updated
event
A subscribed indicator re-printed.
indicator.threshold_crossed
event
An indicator crossed a configured level.
Delivery payload
{
  "object": "event",
  "id": "evt_1Pq7Zd2eAbCdEf",
  "type": "indicator.threshold_crossed",
  "created": "2026-05-29T13:30:00Z",
  "data": {
    "indicator": {
      "id": 11,
      "name": "Crude Supply Disruption Index",
      "value": 52.44,
      "strength": 56
    },
    "threshold": {
      "direction": "above",
      "level": 50
    }
  }
}

Each delivery is signed. The Sceneblue-Signature header contains a timestamp t and an HMAC-SHA256 v1 signature over {t}.{raw_body}, keyed with your endpoint secret. Verify it and reject timestamps outside a ~5-minute tolerance to prevent replay. Non-2xx responses are retried with exponential backoff for up to 24 hours.

Signature header
Sceneblue-Signature: t=1769693400,v1=5257a869e7 ... 7b1e
Verify (Node.js)
import crypto from "node:crypto";

function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`)
    .digest("hex");
  const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
  if (!ok || Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) {
    throw new Error("Invalid or stale signature");
  }
}

SDKs & tools

Official client libraries wrap auth, pagination, retries, and streaming. The full surface is described by an OpenAPI 3.1 spec, with a downloadable Postman collection.

Install
pip install sceneblue        # Python
npm install @sceneblue/sdk    # Node / TypeScript
Python quickstart
from sceneblue import Sceneblue

client = Sceneblue(api_key="sk_live_...")
gti = client.indicators.retrieve(1)
print(gti.latest.value, gti.latest.strength)
  • OpenAPI spec: https://api.sceneblue.com/v1/openapi.json
  • Postman collection: https://docs.sceneblue.com/sceneblue.postman.json

Sandbox / test environment

Build against the sandbox with your sk_test_… key. It mirrors the production API surface but serves synthetic data and never affects live quotas or billing.

Sandbox base URL

https://sandbox.api.sceneblue.com/v1

Field reference

Fields returned on the indicator object.

id
integer
Stable indicator id, 1–50.
name
string
Human-readable indicator name.
value
number
Latest normalized level, 0–100.
strength
number
Signal strength / conviction, 0–100.
change
number
Absolute change vs. the prior print, in points.
change_pct
number
Percentage change vs. the prior print.
as_of
string (ISO-8601)
Timestamp of the latest print, UTC.
inputs
string[]
Input metrics that feed the indicator.
data_sources
string[]
Underlying physical & financial data sources.
object
string
Type discriminator, e.g. "indicator".

Changelog & status

Backward-compatible changes ship continuously and are recorded in the changelog. Live availability and incident history are published on the status page, and we operate to a contractual uptime SLA agreed with your desk.

  • Changelog: https://docs.sceneblue.com/changelog
  • Status: https://status.sceneblue.com

Request API access

Tell us about your desk and how you'd like to consume the data, and we'll issue keys scoped to the indicators you need.

Contact sales