API Reference
POST /ingest/csv

POST /ingest/csv

Upload a CSV file containing a new EPRA pricing cycle. Records are upserted — if a (town, valid_from) combination already exists, its values are updated rather than duplicated.

⚠️

This endpoint requires a Bearer token. See Authentication for details. It is intended for the FuelKenya data pipeline and is not a public endpoint.

Request

POST /v1/ingest/csv
Content-Type: multipart/form-data
Authorization: Bearer <token>

Form field

FieldTypeRequiredDescription
filefileYesThe CSV file. Accepted MIME types: text/csv, application/vnd.ms-excel, application/octet-stream.

CSV format

The CSV must include the following columns (in any order). The header row is required. Column names are parsed leniently — spaces, punctuation, and case are normalised.

EPRA column headerCanonical nameExample value
Fromvalid_from15-06-2026
Tovalid_to14-07-2026
TowntownNairobi
Super (PMS)super_petrol214.03
Diesel (AGO)diesel199.73
Kerosene (IK)kerosene196.53

Date formats

Both DD-MM-YYYY and YYYY-MM-DD are accepted. Do not mix formats within a single file.

Decimal separator

Use . as the decimal separator. Comma-separated decimals (199,73) are not supported.

Example CSV

From,To,Town,Super (PMS),Diesel (AGO),Kerosene (IK)
15-06-2026,14-07-2026,Nairobi,214.03,199.73,196.53
15-06-2026,14-07-2026,Mombasa,208.24,194.53,191.20
15-06-2026,14-07-2026,Kisumu,213.69,199.20,196.10
15-06-2026,14-07-2026,Nakuru,212.92,198.54,195.40
15-06-2026,14-07-2026,Eldoret,212.50,198.12,194.85

Response

201 Created

{
  "status": "success",
  "records_processed": 224,
  "timestamp": "2026-06-15T08:00:00Z"
}
FieldDescription
statusAlways "success" on a 201 response.
records_processedNumber of rows that were inserted or updated.
timestampUTC ISO 8601 timestamp of when the ingestion completed.

Error responses

StatusConditionDetail
400CSV header columns do not match expected columns"CSV header must include From, To, Town, Super (PMS), Diesel (AGO), Kerosene (IK)"
400A date value cannot be parsed"Invalid date format: <value>"
400File contains no data rows"CSV payload contains no rows."
401Missing or incorrect Authorization header"Invalid authorization token for ingestion endpoint."
415Uploaded file is not a CSV"Uploaded file must be a CSV file."

Example

curl -X POST https://api.fuelkenya.com/v1/ingest/csv \
  -H "Authorization: Bearer YOUR_INGEST_TOKEN" \
  -F "file=@epra_june_2026.csv"

Python:

import httpx
 
def ingest_csv(csv_path: str, token: str) -> dict:
    with open(csv_path, "rb") as f:
        r = httpx.post(
            "https://api.fuelkenya.com/v1/ingest/csv",
            headers={"Authorization": f"Bearer {token}"},
            files={"file": ("prices.csv", f, "text/csv")},
        )
    r.raise_for_status()
    return r.json()
 
result = ingest_csv("epra_june_2026.csv", token="secret-token")
print(result)
# {'status': 'success', 'records_processed': 224, 'timestamp': '2026-06-15T08:00:00Z'}

Side effects

After a successful ingest the server clears its in-memory price cache. The next call to /prices/latest will hit the database and repopulate the cache with the newly ingested cycle.