Customer API
Base URL: https://telescope.energy/api/v2
Send a customer API key as a Bearer token in the Authorization header.
Download OpenAPI · Authentication · Troubleshooting
| Method | Endpoint | Task |
|---|---|---|
GET | /api/v2/customer/catalog | List published forecast offerings |
GET | /api/v2/customer/forecasts/{offering}/current | Read the current forecast |
GET | /api/v2/customer/forecasts/{offering}/data | Read a delivery range at a fixed cutoff or constant lead |
GET | /api/v2/customer/forecasts/{offering}/versions | List immutable forecast history |
GET | /api/v2/customer/forecast-versions/{id} | Read one reproducible forecast version |
Use the catalog to select a product and read its current units and delivery horizon.
Examples use illustrative identifiers and dates. Python uses the standard library; TypeScript runs server-side with fetch support.
Set TELESCOPE_API_KEY and choose products available to your account.
List published forecast offerings
GET /api/v2/customer/catalog
Lists the products available to your customer account, with their variants, units, series, outputs and delivery horizons.
Required scope: catalog:read.
Request
curl 'https://telescope.energy/api/v2/customer/catalog' \
-H "Authorization: Bearer $TELESCOPE_API_KEY"
import json
import os
from urllib.request import Request, urlopen
request = Request(
"https://telescope.energy/api/v2/customer/catalog",
headers={
"Authorization": "Bearer " + os.environ["TELESCOPE_API_KEY"],
},
)
with urlopen(request, timeout=30) as response:
print(json.dumps(json.load(response), indent=2))
const response = await fetch("https://telescope.energy/api/v2/customer/catalog", {
headers: {
Authorization: `Bearer ${process.env.TELESCOPE_API_KEY}`,
},
})
if (!response.ok) throw new Error(await response.text())
console.log(await response.json())
Response
200 OK
Illustrative excerpt; fields and arrays are shortened. See the downloadable OpenAPI for schemas and expanded examples.
{
"products": [
{
"slug": "afrr-de",
"title": "Automatic Frequency Restoration Reserve (aFRR) · DE",
"description": "Probabilistic forecasts for German aFRR capacity prices, energy prices, and activated reserve power.",
"category": "Balancing",
"variants": [
{
"key": "capacity-price",
"label": "Capacity Price",
"isDefault": true,
"publicationStatus": "published",
"offeringKey": "afrr-capacity-price-de",
"timezone": "Europe/Berlin",
"resolution": "PT4H",
"unit": "EUR_MWH",
"outputs": [
{ "key": "p10", "valueType": "number", "unit": "EUR_MWH", "quantileLevel": 0.1 },
{ "key": "p50", "valueType": "number", "unit": "EUR_MWH", "quantileLevel": 0.5 },
{ "key": "p90", "valueType": "number", "unit": "EUR_MWH", "quantileLevel": 0.9 }
],
"series": [
{ "key": "positive", "label": "Positive", "direction": "positive" },
{ "key": "negative", "label": "Negative", "direction": "negative" }
],
"firstDeliveryDayOffset": 1,
"maxHorizonDays": 7,
"updatePolicy": { "mode": "gate_closure", "gateDaysBeforeDelivery": 1, "gateLocalTime": "09:00" },
"historySince": "2026-07-27",
"latestReleasedAt": "2026-07-26T17:00:00.000Z"
},
{
"key": "energy-price",
"label": "Energy Price",
"isDefault": false,
"publicationStatus": "published",
"offeringKey": "afrr-energy-price-de",
"timezone": "Europe/Berlin",
"resolution": "PT15M",
"unit": "EUR_MWH",
"outputs": [
{ "key": "p10", "valueType": "number", "unit": "EUR_MWH", "quantileLevel": 0.1 },
{ "key": "p50", "valueType": "number", "unit": "EUR_MWH", "quantileLevel": 0.5 },
{ "key": "p90", "valueType": "number", "unit": "EUR_MWH", "quantileLevel": 0.9 }
],
"series": [
{ "key": "positive", "label": "Positive", "direction": "positive" },
{ "key": "negative", "label": "Negative", "direction": "negative" }
],
"firstDeliveryDayOffset": 1,
"maxHorizonDays": 7,
"updatePolicy": { "mode": "gate_closure", "gateDaysBeforeDelivery": 1, "gateLocalTime": "09:00" },
"historySince": "2026-07-27",
"latestReleasedAt": "2026-07-26T17:00:00.000Z"
}
]
}
]
}
| Field | Meaning |
|---|---|
products[].slug | Use in customer forecast URLs for the default variant. |
products[].variants[].offeringKey | Use in customer forecast URLs to choose a specific variant. |
products[].variants[].timezone / resolution | Local delivery-day boundaries and interval resolution. |
products[].variants[].series / outputs | Value dimensions, units and output types. |
variants[].historySince / latestReleasedAt | On each variant: earliest published delivery date and most recent release time (RFC 3339), or null. These do not guarantee complete coverage. |
Usage notes
- Read delivery and output metadata from the selected variant.
- firstDeliveryDayOffset and maxHorizonDays are the first and last allowed delivery-day offsets from today, in the product time zone.
- Refresh the catalog to detect configuration changes.
- Account rate limit: 120 read requests per minute, with a burst capacity of 30. The token bucket refills continuously and is shared across all API keys of the account and both Customer and Producer read endpoints. Authenticated, authorized requests consume quota even if subsequent validation fails; retries also count. An exhausted bucket returns 429 RATE_LIMIT_EXCEEDED with Retry-After in whole seconds. Wait before retrying; reuse the same Idempotency-Key and payload for a submission retry.
Read the current forecast
GET /api/v2/customer/forecasts/{offering}/current
Retrieves the latest published forecast for every requested delivery day.
Required scope: forecasts:read.
Parameters
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
offering | Path | string | Yes | Product slug for its default variant, or variants.offeringKey for a specific variant. |
horizon | Query | string | No | Consecutive delivery days, P1D through P8D, subject to the offering horizon. Defaults to P1D. |
start | Query | YYYY-MM-DD | No | First local delivery day; defaults to the offering offset. |
Request
curl 'https://telescope.energy/api/v2/customer/forecasts/afrr-activations-de/current?horizon=P1D' \
-H "Authorization: Bearer $TELESCOPE_API_KEY"
import json
import os
from urllib.request import Request, urlopen
request = Request(
"https://telescope.energy/api/v2/customer/forecasts/afrr-activations-de/current?horizon=P1D",
headers={
"Authorization": "Bearer " + os.environ["TELESCOPE_API_KEY"],
},
)
with urlopen(request, timeout=30) as response:
print(json.dumps(json.load(response), indent=2))
const response = await fetch("https://telescope.energy/api/v2/customer/forecasts/afrr-activations-de/current?horizon=P1D", {
headers: {
Authorization: `Bearer ${process.env.TELESCOPE_API_KEY}`,
},
})
if (!response.ok) throw new Error(await response.text())
console.log(await response.json())
Response
200 OK
Illustrative excerpt; fields and arrays are shortened. See the downloadable OpenAPI for schemas and expanded examples.
{
"offering": "afrr-activations-de",
"requested": { "horizon": "P1D", "startDay": "2026-07-27" },
"resolved": { "from": "2026-07-26T22:00:00.000Z", "to": "2026-07-27T22:00:00.000Z", "timezone": "Europe/Berlin" },
"resolution": "PT15M",
"unit": "MW",
"outputs": [
{ "key": "p10", "valueType": "number", "unit": "MW", "quantileLevel": 0.1 },
{ "key": "p50", "valueType": "number", "unit": "MW", "quantileLevel": 0.5 },
{ "key": "p90", "valueType": "number", "unit": "MW", "quantileLevel": 0.9 }
],
"series": [
{ "key": "positive", "label": "Positive", "direction": "positive" },
{ "key": "negative", "label": "Negative", "direction": "negative" }
],
"deliveryDays": [
{ "deliveryDay": "2026-07-27", "forecastVersionId": "n97abc123", "version": 4, "status": "rolling", "coverageRatio": 1, "releasedAt": "2026-07-26T17:00:00.000Z" }
],
"values": [
{ "validTime": "2026-07-26T22:00:00.000Z", "seriesKey": "negative", "outputKey": "p50", "value": 420.1 },
{ "validTime": "2026-07-26T22:00:00.000Z", "seriesKey": "positive", "outputKey": "p50", "value": 385.6 }
]
}
| Field | Meaning |
|---|---|
deliveryDays[].forecastVersionId | Store this identifier if you need to reproduce a decision. |
deliveryDays[].status | preliminary: may be updated before finalization; final: finalized forecast; rolling: regularly updated forecast. Released version IDs remain immutable. |
resolved | Requested delivery range expressed as RFC 3339 timestamps, with the product time zone. |
values | Rows sorted by validTime, seriesKey and outputKey. |
Usage notes
- If any requested day is unavailable, the entire request returns 404 FORECAST_NOT_PUBLISHED.
- Omit start to use the product’s first configured delivery day. Every requested day must fall within the product horizon; otherwise the request returns 422 DELIVERY_DAYS_OUTSIDE_OFFERING_HORIZON.
- Timestamps use RFC 3339. Use the unit on the corresponding output.
- Use the data endpoint for historical selection and the version endpoint to retrieve a saved version.
- Account rate limit: 120 read requests per minute, with a burst capacity of 30. The token bucket refills continuously and is shared across all API keys of the account and both Customer and Producer read endpoints. Authenticated, authorized requests consume quota even if subsequent validation fails; retries also count. An exhausted bucket returns 429 RATE_LIMIT_EXCEEDED with Retry-After in whole seconds. Wait before retrying; reuse the same Idempotency-Key and payload for a submission retry.
Read a delivery range at a fixed cutoff or constant lead
GET /api/v2/customer/forecasts/{offering}/data
Retrieves historical forecasts using either one knowledge timestamp or a constant decision lead. The range includes both deliveryFrom and deliveryTo, in the product time zone.
Required scope: forecasts:read.
Parameters
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
offering | Path | string | Yes | Product slug for its default variant, or variants.offeringKey for a specific variant. |
deliveryFrom | Query | YYYY-MM-DD | Yes | First local delivery day, inclusive. |
deliveryTo | Query | YYYY-MM-DD | Yes | Last local delivery day, inclusive; at most 366 days per range. |
asOf | Query | string | No | Fixed availability cutoff; must not be in the future. Supply either asOf or both relativeTo and leadMinutes. |
limit | Query | integer · 1–8 | No | Maximum delivery days per page. Defaults to 7. |
cursor | Query | string | No | Opaque continuation cursor bound to the original request. |
relativeTo | Query | gateClosure, validTime | No | Explicit reference for the lead: gateClosure (market gate closure) or validTime (delivery interval start). Required with leadMinutes; cannot be combined with asOf. |
leadMinutes | Query | integer ≥ 0 | No | Non-negative whole minutes before the reference time. Required with relativeTo; zero includes releases exactly at the reference time. |
Request
curl 'https://telescope.energy/api/v2/customer/forecasts/day-ahead-price-de-lu/data?deliveryFrom=2026-08-01&deliveryTo=2026-08-03&asOf=2026-07-31T10:00:00Z&limit=2' \
-H "Authorization: Bearer $TELESCOPE_API_KEY"
import json
import os
from urllib.request import Request, urlopen
request = Request(
"https://telescope.energy/api/v2/customer/forecasts/day-ahead-price-de-lu/data?deliveryFrom=2026-08-01&deliveryTo=2026-08-03&asOf=2026-07-31T10:00:00Z&limit=2",
headers={
"Authorization": "Bearer " + os.environ["TELESCOPE_API_KEY"],
},
)
with urlopen(request, timeout=30) as response:
print(json.dumps(json.load(response), indent=2))
const response = await fetch("https://telescope.energy/api/v2/customer/forecasts/day-ahead-price-de-lu/data?deliveryFrom=2026-08-01&deliveryTo=2026-08-03&asOf=2026-07-31T10:00:00Z&limit=2", {
headers: {
Authorization: `Bearer ${process.env.TELESCOPE_API_KEY}`,
},
})
if (!response.ok) throw new Error(await response.text())
console.log(await response.json())
Response
200 OK
Illustrative excerpt; fields and arrays are shortened. See the downloadable OpenAPI for schemas and expanded examples.
{
"offering": "day-ahead-price-de-lu",
"requested": { "deliveryFrom": "2026-08-01", "deliveryTo": "2026-08-03", "asOf": "2026-07-31T10:00:00.000Z", "limit": 2 },
"page": { "deliveryFrom": "2026-08-01", "deliveryTo": "2026-08-02", "deliveryDayCount": 2 },
"resolution": "PT15M",
"timezone": "Europe/Berlin",
"unit": "EUR_MWH",
"outputs": [
{ "key": "p10", "valueType": "number", "unit": "EUR_MWH" },
{ "key": "p50", "valueType": "number", "unit": "EUR_MWH" },
{ "key": "p90", "valueType": "number", "unit": "EUR_MWH" }
],
"series": [
{ "key": "value", "label": "Value" }
],
"deliveryDays": [
{
"deliveryDay": "2026-08-01",
"available": true,
"forecastVersionId": "n97abc123",
"version": 3,
"status": "final",
"coverageRatio": 1,
"releasedAt": "2026-07-31T09:45:00.000Z"
},
{ "deliveryDay": "2026-08-02", "available": false, "reason": "NO_VERSION_AT_AS_OF" }
],
"values": [
{ "validTime": "2026-07-31T22:00:00.000Z", "seriesKey": "value", "outputKey": "p50", "value": 81.4 }
],
"pagination": { "hasMore": true, "nextCursor": "eyJ2ZXJzaW9uIjoxLCJvZmZlcmluZ1NsdWciOiJkYXktYWhlYWQtcHJpY2UtZGUtbHUifQ" }
}
For relative selection, a gap example and a complete pagination loop, see Historical data & backtesting.
| Field | Meaning |
|---|---|
deliveryDays | Availability and coverage per day; fixed asOf results also identify each day’s version. |
values | Normalized rows sorted by time, series and output. |
intervals | Relative queries only: cutoff, availability and version for each expected interval. Join to values by validTime. |
evaluatedAt | Relative queries only: evaluation time fixed for the pagination sequence. |
pagination.nextCursor | Opaque cursor for the next page; null when complete. |
Usage notes
- Supply either asOf or both relativeTo and leadMinutes. Mixing modes or omitting a partner parameter returns 422.
- asOf selects the latest version published at or before that timestamp for each day. A missing day has available: false and reason: NO_VERSION_AT_AS_OF.
- relativeTo=gateClosure uses the local market gate minus leadMinutes. Provider submission lead is not subtracted. Products without a gate return 422 GATE_CLOSURE_NOT_SUPPORTED.
- relativeTo=validTime uses each interval’s start minus leadMinutes. Zero lead includes releases exactly at the cutoff.
- An interval uses the latest eligible publication containing it. All its series and outputs come from that version. Incomplete intervals are not filled using older versions.
- Later publications never fill historical gaps. A day may contain multiple versions; retain interval version IDs for reproducibility.
- Relative gaps use NO_VERSION_AT_AS_OF, NO_VALUE_AT_AS_OF, INCOMPLETE_INTERVAL or CUTOFF_NOT_REACHED. Missing intervals have no values rows. available on a day means at least one complete interval; check coverageRatio.
- Follow nextCursor with the original product, date range and selection parameters. Relative pages retain evaluatedAt; restart without a cursor to evaluate again.
- For 422 FORECAST_RANGE_TOO_COMPLEX, reduce limit. Contact support if a single day still fails.
- Account rate limit: 120 read requests per minute, with a burst capacity of 30. The token bucket refills continuously and is shared across all API keys of the account and both Customer and Producer read endpoints. Authenticated, authorized requests consume quota even if subsequent validation fails; retries also count. An exhausted bucket returns 429 RATE_LIMIT_EXCEEDED with Retry-After in whole seconds. Wait before retrying; reuse the same Idempotency-Key and payload for a submission retry.
List immutable forecast history
GET /api/v2/customer/forecasts/{offering}/versions
Lists released forecast versions, newest first, so you can find a snapshot to retrieve again.
Required scope: forecasts:read.
Parameters
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
offering | Path | string | Yes | Product slug for its default variant, or variants.offeringKey for a specific variant. |
limit | Query | integer · 1–100 | No | Maximum released versions, newest first. Defaults to 30. |
Request
curl 'https://telescope.energy/api/v2/customer/forecasts/afrr-capacity-price-de/versions?limit=20' \
-H "Authorization: Bearer $TELESCOPE_API_KEY"
import json
import os
from urllib.request import Request, urlopen
request = Request(
"https://telescope.energy/api/v2/customer/forecasts/afrr-capacity-price-de/versions?limit=20",
headers={
"Authorization": "Bearer " + os.environ["TELESCOPE_API_KEY"],
},
)
with urlopen(request, timeout=30) as response:
print(json.dumps(json.load(response), indent=2))
const response = await fetch("https://telescope.energy/api/v2/customer/forecasts/afrr-capacity-price-de/versions?limit=20", {
headers: {
Authorization: `Bearer ${process.env.TELESCOPE_API_KEY}`,
},
})
if (!response.ok) throw new Error(await response.text())
console.log(await response.json())
Response
200 OK
Illustrative excerpt; fields and arrays are shortened. See the downloadable OpenAPI for schemas and expanded examples.
{
"versions": [
{
"forecastVersionId": "n97abc123",
"offering": "afrr-capacity-price-de",
"deliveryDay": "2026-07-27",
"version": 4,
"releasedAt": "2026-07-26T17:00:00.000Z",
"coverageRatio": 1,
"valueCount": 36,
"status": "preliminary"
}
]
}
| Field | Meaning |
|---|---|
versions[].forecastVersionId | Version identifier for the forecast-version endpoint. |
versions[].deliveryDay / releasedAt | Local delivery date and publication timestamp. |
versions[].status / coverageRatio | Publication status and coverage. |
Usage notes
- This endpoint returns metadata. Retrieve a version by its forecastVersionId to obtain values.
- An unavailable product returns 404 OFFERING_NOT_FOUND.
- Account rate limit: 120 read requests per minute, with a burst capacity of 30. The token bucket refills continuously and is shared across all API keys of the account and both Customer and Producer read endpoints. Authenticated, authorized requests consume quota even if subsequent validation fails; retries also count. An exhausted bucket returns 429 RATE_LIMIT_EXCEEDED with Retry-After in whole seconds. Wait before retrying; reuse the same Idempotency-Key and payload for a submission retry.
Read one reproducible forecast version
GET /api/v2/customer/forecast-versions/{id}
Retrieves the saved values of one immutable published forecast version.
Required scope: forecasts:read.
Parameters
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
id | Path | string | Yes | Resource identifier returned by the catalog or list endpoint. |
Request
curl 'https://telescope.energy/api/v2/customer/forecast-versions/n97abc123' \
-H "Authorization: Bearer $TELESCOPE_API_KEY"
import json
import os
from urllib.request import Request, urlopen
request = Request(
"https://telescope.energy/api/v2/customer/forecast-versions/n97abc123",
headers={
"Authorization": "Bearer " + os.environ["TELESCOPE_API_KEY"],
},
)
with urlopen(request, timeout=30) as response:
print(json.dumps(json.load(response), indent=2))
const response = await fetch("https://telescope.energy/api/v2/customer/forecast-versions/n97abc123", {
headers: {
Authorization: `Bearer ${process.env.TELESCOPE_API_KEY}`,
},
})
if (!response.ok) throw new Error(await response.text())
console.log(await response.json())
Response
200 OK
Illustrative excerpt; fields and arrays are shortened. See the downloadable OpenAPI for schemas and expanded examples.
{
"forecastVersion": {
"forecastVersionId": "n97abc123",
"offering": "afrr-capacity-price-de",
"deliveryDay": "2026-07-27",
"version": 4,
"releasedAt": "2026-07-26T17:00:00.000Z",
"coverageRatio": 1,
"valueCount": 36,
"status": "preliminary",
"resolution": "PT4H",
"timezone": "Europe/Berlin",
"unit": "EUR_MWH",
"outputs": [
{ "key": "p10", "valueType": "number", "unit": "EUR_MWH", "quantileLevel": 0.1 },
{ "key": "p50", "valueType": "number", "unit": "EUR_MWH", "quantileLevel": 0.5 },
{ "key": "p90", "valueType": "number", "unit": "EUR_MWH", "quantileLevel": 0.9 }
],
"series": [
{ "key": "positive", "label": "Positive", "direction": "positive" },
{ "key": "negative", "label": "Negative", "direction": "negative" }
],
"values": [
{ "validTime": "2026-07-26T22:00:00.000Z", "seriesKey": "positive", "outputKey": "p50", "value": 68.4 }
]
}
}
| Field | Meaning |
|---|---|
forecastVersion | Version identifier, delivery day, release time and coverage. |
forecastVersion.values | Values identified by validTime, seriesKey and outputKey. |
Usage notes
- A missing or invalid identifier returns 404 FORECAST_VERSION_NOT_FOUND.
- Retrieving the same version preserves the forecast even after newer versions are published.
- Account rate limit: 120 read requests per minute, with a burst capacity of 30. The token bucket refills continuously and is shared across all API keys of the account and both Customer and Producer read endpoints. Authenticated, authorized requests consume quota even if subsequent validation fails; retries also count. An exhausted bucket returns 429 RATE_LIMIT_EXCEEDED with Retry-After in whole seconds. Wait before retrying; reuse the same Idempotency-Key and payload for a submission retry.
Errors and next steps
Errors contain error.code, error.message, optional error.details and requestId. The same request ID is returned in X-Request-ID. See Troubleshooting for recovery steps and retain the request ID when reporting a failure.