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/v1curl 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
valueandstrengthare normalized to a 0–100 scale;change_pctis a percentage (not a ratio). - List endpoints return a wrapper object with
object: "list", adataarray, and pagination fields. Single resources carry anobjectdiscriminator (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.
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.
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": {
"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. |
| Status | Meaning | Notes |
|---|---|---|
| 200 | OK | Request succeeded. |
| 304 | Not Modified | Resource unchanged since your cached ETag. |
| 400 | Bad Request | Malformed request or invalid parameter. |
| 401 | Unauthorized | Missing or invalid API key. |
| 403 | Forbidden | Key is valid but not scoped to this resource. |
| 404 | Not Found | No resource matches the requested id. |
| 409 | Conflict | Idempotency key reused with a different payload. |
| 422 | Unprocessable | Parameters valid in shape but semantically rejected. |
| 429 | Too Many Requests | Rate limit exceeded — back off and retry. |
| 500 | Server Error | Unexpected error on our side; safe to retry. |
| 503 | Service Unavailable | Temporarily 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.
RateLimit-Limit: 600
RateLimit-Remaining: 598
RateLimit-Reset: 41
Retry-After: 2{
"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. |
{
"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: <https://api.sceneblue.com/v1/indicators?cursor=ind_eyJpZCI6Mn0&limit=2>; rel="next"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_cursorFiltering, 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 ModifiedIdempotency & 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/sceneblueRequest 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_0Kd9Qa2mP7xViT8Endpoints
List indicators
/v1/indicatorsReturns 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.
{
"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
/v1/indicators/{id}Path parameters
| idrequired integer | Indicator id, 1–50. |
{
"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
/v1/indicators/{id}/historyQuery 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. |
{
"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/streamAuthenticate 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.
wscat -c "wss://api.sceneblue.com/v1/stream" \
-H "Authorization: Bearer $SCENEBLUE_API_KEY"
# then send:
{
"action": "subscribe",
"indicators": [
1,
11,
17
]
}{
"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. |
{
"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.
Sceneblue-Signature: t=1769693400,v1=5257a869e7 ... 7b1eimport 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.
pip install sceneblue # Python
npm install @sceneblue/sdk # Node / TypeScriptfrom 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/v1Field 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