{"openapi":"3.1.0","info":{"title":"trawa Day-Ahead Electricity Demand API","version":"1.0.0","summary":"Upload a day-ahead electricity demand forecast as a CSV file.","description":"Upload a day-ahead electricity demand forecast for one German market location\n(Marktlokation) as a raw `text/csv` request body.\n\n- The MaLo-ID is taken from the request path only. It must never appear in the CSV.\n- The interval resolution is fixed at 15 minutes and `value_kwh` is\n  interval energy in kWh, not average power.\n- A file covers 1 to 7 contiguous UTC calendar days. The series may start\n  and end at any 15-minute boundary, but it must have no gaps: every\n  15-minute interval between the first and the last timestamp is required\n  (96 intervals per fully covered day).\n- Processing is synchronous and all-or-nothing: a file is either fully accepted or\n  fully rejected. A rejected file is not stored in any form and changes nothing.\n- Every request requires an `Idempotency-Key`. Retrying with the same key returns the\n  stored result instead of creating a second submission.\n\nAn accepted submission overwrites the latest stored value for every\n`(MaLo-ID, timestamp_utc)` it contains. Superseded submissions and their original\nfiles are retained unchanged for auditability.","contact":{"name":"trawa integrations","url":"https://trawa.de"},"license":{"name":"Proprietary","identifier":"LicenseRef-Proprietary"}},"externalDocs":{"description":"Interactive documentation","url":"https://electrolyzer.trawa.de/api-docs"},"servers":[{"url":"https://electrolyzer.trawa.de/api","description":"Production"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Demand forecasts","description":"Day-ahead electricity demand forecast submissions."}],"paths":{"/electricity/{MaLo-ID}/demand-forecasts":{"post":{"operationId":"uploadDemandForecast","tags":["Demand forecasts"],"summary":"Upload a day-ahead demand forecast CSV","description":"Validates the CSV synchronously, stores the original file, and upserts the\nvalidated values as the latest forecast for the given MaLo-ID.\n\n**Idempotency.** The first successful request returns `201 Created`. Repeating it\nwith the same `Idempotency-Key` and the same file returns the stored body with\n`200 OK` and `Idempotent-Replay: true`. Reusing the key with a different file\nreturns `409 Conflict`; a corrected file therefore needs a new key.\n\nA rejected submission is not stored and so does not consume the key: repeating it\nvalidates the file again and returns `422` again, and the same key may be reused\nfor the corrected file.","security":[{"bearerAuth":[]}],"parameters":[{"name":"MaLo-ID","in":"path","required":true,"description":"German market location ID (Marktlokations-ID): 11 digits with a valid BDEW check digit, configured for the authenticated customer. The internal route parameter is named `maloId`.","schema":{"type":"string","pattern":"^[1-9][0-9]{10}$"},"examples":{"maloId":{"value":"51238696781"}}},{"name":"Idempotency-Key","in":"header","required":true,"description":"Unique client-generated key for this submission, 8 to 255 printable ASCII characters. A UUID is recommended.","schema":{"type":"string","minLength":8,"maxLength":255},"examples":{"uuid":{"value":"6f9619ff-8b86-d011-b42d-00cf4fc964ff"}}},{"name":"X-Request-ID","in":"header","required":false,"description":"Optional client correlation ID, echoed in the response.","schema":{"type":"string","maxLength":200,"pattern":"^[A-Za-z0-9._:-]{1,200}$"}}],"requestBody":{"required":true,"description":"The CSV document itself. Exactly one header row `timestamp_utc,value_kwh`, followed by one row per 15-minute interval.","content":{"text/csv":{"schema":{"type":"string","contentMediaType":"text/csv","contentEncoding":"utf-8","maxLength":2000000,"description":"UTF-8 encoded CSV. `timestamp_utc` must use the format `YYYY-MM-DD HH:mm:ss+00:00`; `value_kwh` must be a finite decimal greater than or equal to zero using a dot as decimal separator."},"example":"timestamp_utc,value_kwh\n2026-09-11 00:00:00+00:00,6.87\n2026-09-11 00:15:00+00:00,6.42\n2026-09-11 00:30:00+00:00,6.11\n"}}},"responses":{"200":{"description":"Idempotent replay: this `Idempotency-Key` and file were already accepted. The body is the originally returned document, including its original `request_id`.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"Idempotent-Replay":{"$ref":"#/components/headers/Idempotent-Replay"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmissionAccepted"}}}},"201":{"description":"The forecast was accepted and stored.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmissionAccepted"}}}},"400":{"description":"Malformed request: empty body, malformed CSV structure, invalid encoding, invalid MaLo-ID, or a missing or malformed `Idempotency-Key`.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"Missing, malformed or invalid bearer token.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"WWW-Authenticate":{"description":"Authentication scheme.","schema":{"type":"string","examples":["Bearer"]}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Authenticated, but the customer is not authorized for this MaLo-ID.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"The `Idempotency-Key` was already used with different content, or a concurrent submission is using it.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"The request body exceeds the configured limit.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"415":{"description":"Unsupported `Content-Type`. Use `text/csv; charset=utf-8`.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The CSV was readable but contains invalid business data. Nothing was stored — not the file, not the rejection itself — and no forecast values were changed. The `Idempotency-Key` remains unused.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ValidationProblem"}}}},"429":{"description":"Rate limit exceeded.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"},"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer","minimum":1}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Unexpected server-side failure. Safe to retry with the same key.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Customer-specific bearer token issued by trawa, sent as\n`Authorization: Bearer <token>`.\n\nCredentials are never accepted in the query string, and separate tokens are used\nfor test and production. Contact trawa integrations to rotate a token."}},"headers":{"X-Request-ID":{"description":"Correlation ID of this request. Quote it in support requests.","schema":{"type":"string"}},"Idempotent-Replay":{"description":"Present and `true` when the response was replayed from a stored result.","schema":{"type":"string","enum":["true"]}}},"schemas":{"SubmissionAccepted":{"type":"object","required":["submission_id","status","delivery_start_date","delivery_end_date","forecast_days","interval_count","resolution_minutes","quantity_type","unit","file_sha256","request_id"],"properties":{"submission_id":{"type":"string","format":"uuid","description":"Immutable identifier of this submission."},"status":{"type":"string","enum":["ACCEPTED"]},"delivery_start_date":{"type":"string","format":"date","description":"First UTC calendar day covered by the forecast."},"delivery_end_date":{"type":"string","format":"date","description":"Last UTC calendar day covered by the forecast."},"forecast_days":{"type":"integer","minimum":1,"maximum":7,"description":"Inclusive number of UTC calendar days between start and end date. Computed, not stored."},"interval_count":{"type":"integer","minimum":1},"resolution_minutes":{"type":"integer","enum":[15]},"quantity_type":{"type":"string","enum":["interval_energy"]},"unit":{"type":"string","enum":["kWh"]},"file_sha256":{"type":"string","pattern":"^[0-9a-f]{64}$","description":"SHA-256 of the exact uploaded bytes."},"request_id":{"type":"string"},"warnings":{"type":"array","description":"Non-blocking findings recorded with the submission, for example implausibly large values. Present only when there are warnings.","items":{"$ref":"#/components/schemas/ValidationIssue"}}},"examples":[{"submission_id":"9f1d4a4c-8d34-4f7c-9bd9-1f3b0a1c2d3e","status":"ACCEPTED","delivery_start_date":"2026-09-11","delivery_end_date":"2026-09-17","forecast_days":7,"interval_count":672,"resolution_minutes":15,"quantity_type":"interval_energy","unit":"kWh","file_sha256":"9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca7","request_id":"req_2f1c0c2e-9c2e-4c9a-9e0b-63b4d8a1b0a1"}]},"ValidationIssue":{"type":"object","required":["row","field","code","message"],"properties":{"row":{"type":["integer","null"],"description":"Line number in the uploaded file, where the header row is line 1. `null` for findings about the dataset as a whole."},"field":{"type":["string","null"],"enum":["timestamp_utc","value_kwh",null]},"code":{"type":"string","enum":["METHOD_NOT_ALLOWED","MISSING_AUTHORIZATION","INVALID_AUTHORIZATION","MALO_ID_INVALID","MALO_ID_NOT_AUTHORIZED","INVALID_CONTENT_TYPE","PAYLOAD_TOO_LARGE","EMPTY_BODY","INVALID_ENCODING","MISSING_IDEMPOTENCY_KEY","INVALID_IDEMPOTENCY_KEY","IDEMPOTENCY_KEY_REUSED","RATE_LIMITED","INTERNAL_ERROR","STORAGE_WRITE_FAILED","VALIDATION_FAILED","CSV_PARSE_ERROR","MISSING_COLUMN","DUPLICATE_COLUMN","UNEXPECTED_COLUMN","NO_DATA_ROWS","TOO_MANY_ROWS","FIELD_TOO_LONG","INVALID_TIMESTAMP","INVALID_TIMESTAMP_FORMAT","TIMESTAMP_NOT_UTC","INTERVAL_NOT_15_MINUTES","INTERVAL_NOT_ALIGNED","INVALID_NUMBER","NEGATIVE_NOT_ALLOWED","DUPLICATE_INTERVAL","MISSING_INTERVAL","FORECAST_HORIZON_TOO_LONG","NON_CONTIGUOUS_FORECAST_HORIZON","EXTREME_VALUE","ALL_ZERO_VALUES"]},"message":{"type":"string"}}},"Problem":{"type":"object","required":["type","title","status","code","detail","request_id"],"properties":{"type":{"type":"string","format":"uri","examples":["https://electrolyzer.trawa.de/problems/bad-request"]},"title":{"type":"string","examples":["Bad request"]},"status":{"type":"integer","minimum":400,"maximum":599},"code":{"type":"string","description":"Stable machine-readable error code.","enum":["METHOD_NOT_ALLOWED","MISSING_AUTHORIZATION","INVALID_AUTHORIZATION","MALO_ID_INVALID","MALO_ID_NOT_AUTHORIZED","INVALID_CONTENT_TYPE","PAYLOAD_TOO_LARGE","EMPTY_BODY","INVALID_ENCODING","MISSING_IDEMPOTENCY_KEY","INVALID_IDEMPOTENCY_KEY","IDEMPOTENCY_KEY_REUSED","RATE_LIMITED","INTERNAL_ERROR","STORAGE_WRITE_FAILED","VALIDATION_FAILED","CSV_PARSE_ERROR","MISSING_COLUMN","DUPLICATE_COLUMN","UNEXPECTED_COLUMN","NO_DATA_ROWS","TOO_MANY_ROWS","FIELD_TOO_LONG","INVALID_TIMESTAMP","INVALID_TIMESTAMP_FORMAT","TIMESTAMP_NOT_UTC","INTERVAL_NOT_15_MINUTES","INTERVAL_NOT_ALIGNED","INVALID_NUMBER","NEGATIVE_NOT_ALLOWED","DUPLICATE_INTERVAL","MISSING_INTERVAL","FORECAST_HORIZON_TOO_LONG","NON_CONTIGUOUS_FORECAST_HORIZON","EXTREME_VALUE","ALL_ZERO_VALUES"]},"detail":{"type":"string"},"request_id":{"type":"string","examples":["req_2f1c0c2e-9c2e-4c9a-9e0b-63b4d8a1b0a1"]}}},"ValidationProblem":{"allOf":[{"$ref":"#/components/schemas/Problem"},{"type":"object","required":["errors"],"properties":{"errors":{"type":"array","items":{"$ref":"#/components/schemas/ValidationIssue"}},"errors_truncated":{"type":"boolean","description":"Present and `true` when more findings exist than were reported."}}}],"examples":[{"type":"https://electrolyzer.trawa.de/problems/validation-error","title":"Demand forecast rejected","status":422,"code":"VALIDATION_FAILED","detail":"The CSV contains validation errors. No forecast values were changed.","request_id":"req_2f1c0c2e-9c2e-4c9a-9e0b-63b4d8a1b0a1","errors":[{"row":18,"field":"value_kwh","code":"NEGATIVE_NOT_ALLOWED","message":"The kWh value must be zero or greater."}]}]}}}}