Producer API
Base URL: https://telescope.energy/api/v2
Send a producer API key as a Bearer token in the Authorization header.
Download OpenAPI · Authentication · Troubleshooting
| Method | Endpoint | Task |
|---|---|---|
GET | /api/v2/producer/definitions | List allowed forecast definitions |
POST | /api/v2/producer/submissions | Submit forecast snapshots |
GET | /api/v2/producer/submissions | List your accepted submissions |
GET | /api/v2/producer/submissions/{id} | Inspect one accepted submission |
GET | /api/v2/producer/forecasts/{definition}/data | Read your point-in-time forecast range |
Read the current definition before preparing a submission.
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 allowed forecast definitions
GET /api/v2/producer/definitions
Lists the forecast definitions available to your provider account. Each definition specifies the required time grid, series, outputs, units and submission policy.
Required scope: catalog:read.
Request
curl 'https://telescope.energy/api/v2/producer/definitions' \
-H "Authorization: Bearer $TELESCOPE_API_KEY"
import json
import os
from urllib.request import Request, urlopen
request = Request(
"https://telescope.energy/api/v2/producer/definitions",
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/producer/definitions", {
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.
{
"definitions": [
{
"key": "afrr-capacity-price-de",
"title": "Automatic Frequency Restoration Reserve (aFRR) · DE",
"market": "afrr",
"zone": "DE",
"target": "capacity-price",
"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" }
],
"horizon": { "firstDeliveryDayOffset": 1, "maxHorizonDays": 7 },
"submissionRules": { "mode": "gate_closure", "gateDaysBeforeDelivery": 1, "gateLocalTime": "09:00", "submissionLeadMinutes": 5, "requireCompleteDeliveryDays": true }
}
]
}
| Field | Meaning |
|---|---|
definitions[].key | Use this key in your submission. |
definitions[] | Resolution, time zone and forecast target. |
definitions[].series / outputs | Every required series and output, including units and bounds. |
definitions[].horizon / submissionRules | Allowed delivery dates, coverage and deadline rules. |
Usage notes
- Keys are case-sensitive. Refresh definitions before scheduled submissions.
- See submission scheduling for rolling coverage and gate-closure deadlines.
- 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.
Submit forecast snapshots
POST /api/v2/producer/submissions
Validates and accepts a complete forecast submission. If any value or delivery window is invalid, the entire submission is rejected and no forecast values are stored.
Required scope: submissions:write.
Parameters
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
Idempotency-Key | Header | string | Yes | Persist one key per logical submission and reuse it only for identical retries. |
definition | Body | string | Yes | Definition key returned by the producer catalog. |
model | Body | string | No | Registered internal model key; omit for the managed default model. |
generatedAt | Body | string | Yes | Producer-reported generation time; eligibility uses trusted receipt time. |
deliveryWindows | Body | array | Yes | Local-day containers. Product-specific grid, coverage and horizon rules apply. |
deliveryWindows[].deliveryDay | Body | string | Yes | Local delivery day; validated against the product contract. |
deliveryWindows[].values | Body | array | Yes | Required intervals in ascending timestamp order. |
deliveryWindows[].values[].validTime | Body | string | Yes | RFC 3339 timestamp with seconds and Z or an explicit UTC offset. |
deliveryWindows[].values[].outputs | Body | object | No | Every configured output for a single-series definition. |
deliveryWindows[].values[].series | Body | object | No | Every configured series, each containing all outputs. |
Supply exactly one of outputs or series per timestamp. Multi-series definitions require series. Prepare a complete payload with First submission.
Request
Save your complete payload as submission.json and set TELESCOPE_IDEMPOTENCY_KEY to the persisted key for this logical submission. Reuse both unchanged on retry.
curl --fail-with-body 'https://telescope.energy/api/v2/producer/submissions' \
-H "Authorization: Bearer $TELESCOPE_API_KEY" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $TELESCOPE_IDEMPOTENCY_KEY" \
--data-binary @submission.json
import json
import os
from urllib.request import Request, urlopen
from pathlib import Path
request = Request(
"https://telescope.energy/api/v2/producer/submissions",
data=Path("submission.json").read_bytes(),
headers={
"Authorization": "Bearer " + os.environ["TELESCOPE_API_KEY"],
"Content-Type": "application/json",
"Idempotency-Key": os.environ["TELESCOPE_IDEMPOTENCY_KEY"],
},
)
with urlopen(request, timeout=30) as response:
print(json.dumps(json.load(response), indent=2))
import { readFile } from "node:fs/promises"
const response = await fetch("https://telescope.energy/api/v2/producer/submissions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TELESCOPE_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": process.env.TELESCOPE_IDEMPOTENCY_KEY!,
},
body: await readFile("submission.json", "utf8"),
})
if (!response.ok) throw new Error(await response.text())
console.log(await response.json())
Response
201 Created · 200 OK on replay
Illustrative excerpt; fields and arrays are shortened. See the downloadable OpenAPI for schemas and expanded examples.
{
"replayed": false,
"runId": "m57abc123",
"status": "accepted",
"deliveryWindows": [
{ "deliveryDay": "2026-07-27", "expectedTimestamps": 6, "timestampCount": 6, "valueCount": 36, "coverageRatio": 1, "deadlineAt": "2026-07-27T08:45:00.000Z" }
],
"definition": "afrr-capacity-price-de",
"receivedAt": "2026-07-26T16:01:04.000Z"
}
| Field | Meaning |
|---|---|
runId | Identifier of the accepted submission. |
definition / receivedAt | Canonical definition key and original receipt timestamp. |
replayed | True when this is the original result of an identical retry. |
deliveryWindows | Accepted days, interval counts and coverage; gate-closure days also include deadlineAt. |
Usage notes
- Use exactly one of outputs or series per timestamp. Multiple configured series require series; include all configured outputs.
- Unknown fields, duplicate timestamps, incomplete grids, non-finite values and values outside the configured bounds are rejected. Quantiles must be ordered within each series.
- Bodies must not exceed 1 MiB (1,048,576 UTF-8 bytes) or 10,000 scalar forecast values across all timestamps, series and outputs. Oversized bodies return 413 SUBMISSION_TOO_LARGE; excess values return 422 TOO_MANY_VALUES or a SUBMISSION_REJECTED issue with that code. generatedAt must not be more than five minutes in the future.
- Persist one Idempotency-Key per logical submission. Retry with exactly the same body and key. A changed body with the same key returns 409 IDEMPOTENCY_KEY_CONFLICT.
- A validation failure returns 422 SUBMISSION_REJECTED with at most 100 details.issues containing path, code, message and optionally deliveryDay. details.issueCount reports the total; details.issuesTruncated indicates omitted issues. An identical retry returns the same stored issue summary. Correct the payload and use a new key.
- Acceptance confirms receipt of your forecast. Customer publication is a separate step.
- Account rate limit: 30 submissions per minute, with a burst capacity of 20. The token bucket refills continuously and is shared across all API keys of the account. 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 your accepted submissions
GET /api/v2/producer/submissions
Lists your accepted submissions, newest first.
Required scope: submissions:read.
Parameters
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
limit | Query | integer · 1–200 | No | Maximum accepted runs per page, newest first. Defaults to 50. |
cursor | Query | string | No | Continuation cursor returned by the previous page. |
Request
curl 'https://telescope.energy/api/v2/producer/submissions?limit=25' \
-H "Authorization: Bearer $TELESCOPE_API_KEY"
import json
import os
from urllib.request import Request, urlopen
request = Request(
"https://telescope.energy/api/v2/producer/submissions?limit=25",
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/producer/submissions?limit=25", {
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.
{
"submissions": [
{
"runId": "m57abc123",
"definition": "afrr-capacity-price-de",
"model": { "key": null, "isDefault": true, "version": "1.0" },
"idempotencyKey": "forecast-2026-07-27-v1",
"generatedAt": "2026-07-26T16:00:00.000Z",
"receivedAt": "2026-07-26T16:01:04.000Z",
"status": "accepted",
"valueCount": 36
}
],
"pagination": { "hasMore": false, "nextCursor": null }
}
| Field | Meaning |
|---|---|
submissions[].runId | Use this identifier to inspect a submission. |
submissions[].generatedAt / receivedAt | Forecast generation and acceptance times as RFC 3339 timestamps. |
submissions[].valueCount | Number of numeric output values accepted. |
pagination.nextCursor | Continue with this cursor; null marks the end. |
Usage notes
- Only submissions belonging to your provider account are returned.
- 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.
Inspect one accepted submission
GET /api/v2/producer/submissions/{id}
Retrieves one of your accepted submissions, its delivery windows and forecast values.
Required scope: submissions: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/producer/submissions/m57abc123' \
-H "Authorization: Bearer $TELESCOPE_API_KEY"
import json
import os
from urllib.request import Request, urlopen
request = Request(
"https://telescope.energy/api/v2/producer/submissions/m57abc123",
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/producer/submissions/m57abc123", {
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.
{
"submission": {
"run": {
"runId": "m57abc123",
"definition": "afrr-capacity-price-de",
"model": { "key": null, "isDefault": true, "version": "1.0" },
"idempotencyKey": "forecast-2026-07-27-v1",
"generatedAt": "2026-07-26T16:00:00.000Z",
"receivedAt": "2026-07-26T16:01:04.000Z",
"status": "accepted",
"valueCount": 36
},
"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" }
],
"deliveryWindows": [
{ "deliveryDay": "2026-07-27", "expectedTimestamps": 6, "timestampCount": 6, "valueCount": 36, "coverageRatio": 1, "deadlineAt": "2026-07-27T08:45:00.000Z" }
],
"values": [
{ "deliveryDay": "2026-07-27", "validTime": "2026-07-26T22:00:00.000Z", "seriesKey": "positive", "outputKey": "p50", "value": 68.4 }
]
}
}
| Field | Meaning |
|---|---|
submission.run | Accepted submission metadata. |
submission.deliveryWindows | Coverage and interval counts per local day. |
submission.values | Rows identified by validTime, seriesKey and outputKey. |
Usage notes
- A missing, invalid or inaccessible identifier returns 404 SUBMISSION_NOT_FOUND.
- Timestamps use RFC 3339.
- 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 your point-in-time forecast range
GET /api/v2/producer/forecasts/{definition}/data
Retrieves the forecast values your provider account had successfully submitted by a fixed time, over an inclusive range of local delivery dates.
Required scope: submissions:read.
Parameters
| Parameter | Location | Type | Required | Description |
|---|---|---|---|---|
definition | Path | string | Yes | Resource identifier returned by the catalog or list endpoint. |
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 | Yes | Availability cutoff; must not be in the future. |
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. |
model | Query | string | No | Internal model key; omit for the managed default model. |
Request
curl 'https://telescope.energy/api/v2/producer/forecasts/day-ahead-price-de-lu/data?deliveryFrom=2026-08-01&deliveryTo=2026-08-02&asOf=2026-07-31T10:00:00Z&model=fundamental-v3' -H "Authorization: Bearer $TELESCOPE_API_KEY"
import json
import os
from urllib.request import Request, urlopen
request = Request(
"https://telescope.energy/api/v2/producer/forecasts/day-ahead-price-de-lu/data?deliveryFrom=2026-08-01&deliveryTo=2026-08-02&asOf=2026-07-31T10:00:00Z&model=fundamental-v3",
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/producer/forecasts/day-ahead-price-de-lu/data?deliveryFrom=2026-08-01&deliveryTo=2026-08-02&asOf=2026-07-31T10:00:00Z&model=fundamental-v3", {
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.
{
"definition": "day-ahead-price-de-lu",
"model": { "key": "fundamental-v3", "isDefault": false },
"requested": { "deliveryFrom": "2026-08-01", "deliveryTo": "2026-08-02", "asOf": "2026-07-31T10:00:00.000Z", "limit": 7 },
"page": { "deliveryFrom": "2026-08-01", "deliveryTo": "2026-08-02", "deliveryDayCount": 2 },
"resolution": "PT15M",
"timezone": "Europe/Berlin",
"unit": "EUR_MWH",
"outputs": [
{ "key": "p50", "valueType": "number", "unit": "EUR_MWH", "quantileLevel": 0.5 }
],
"series": [
{ "key": "value", "label": "Value" }
],
"deliveryDays": [
{
"deliveryDay": "2026-08-01",
"available": true,
"coverageRatio": 1,
"runIds": [
"m57newer",
"m57older"
]
},
{
"deliveryDay": "2026-08-02",
"available": false,
"coverageRatio": 0,
"reason": "NO_ACCEPTED_FORECAST_AT_AS_OF",
"runIds": []
}
],
"runs": [
{ "runId": "m57newer", "receivedAt": "2026-07-31T09:00:00.000Z", "modelVersion": "3.0" },
{ "runId": "m57older", "receivedAt": "2026-07-31T08:00:00.000Z", "modelVersion": "3.0" }
],
"values": [
{ "validTime": "2026-07-31T22:00:00.000Z", "seriesKey": "value", "outputKey": "p50", "value": 81.4, "runId": "m57older" },
{ "validTime": "2026-07-31T22:15:00.000Z", "seriesKey": "value", "outputKey": "p50", "value": 82.1, "runId": "m57newer" }
],
"pagination": { "hasMore": false, "nextCursor": null }
}
| Field | Meaning |
|---|---|
values | Values with runId identifying the accepted submission for each row. |
runs | Referenced submissions and their acceptance times. |
deliveryDays | Availability and coverage per local day. |
pagination.nextCursor | Use this cursor for the next page; null marks the end. |
Usage notes
- Selection uses receivedAt, not generatedAt. All outputs and series for one interval come from the same accepted submission.
- Results may combine several submissions. Customer publication does not affect this endpoint.
- available means at least some values exist; check coverageRatio for completeness.
- Follow nextCursor with the same definition, date range and asOf. Pages contain complete local days.
- 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.