Legacy API (
api.hubbyesim.com). For the core Partner API, read Native eSIM Integration.
CSV Upload Integration
Overview
Hubby eSIM provides a secure, serverless ingestion mechanism for partners to deliver daily booking data via CSV files.
This integration uses temporary signed upload URLs. Partners do not need:
- Google Cloud accounts
- SFTP access
- Persistent credentials
Workflow
- Request temporary upload URL.
- Upload CSV via HTTP PUT.
- Hubby eSIM automatically validates and processes the file.
Get Upload URL
Endpoint: GET /api/bucket/upload-url
Base URL: https://api.hubbyesim.com
Example:
https://api.hubbyesim.com/api/bucket/upload-url?partnerId=acme&date=2026-02-11&dataset=bookings
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
partnerId | string | Yes | Partner identifier (Firestore document ID). Must be alphanumeric, underscore, or hyphen, 1–64 characters. Partner must exist. |
date | string | Yes | Date in ISO format YYYY-MM-DD. |
dataset | string | No | Dataset name (e.g. bookings). Default: bookings. Same character rules as partnerId. |
overwrite | string | No | If a file already exists at the object path, set to true to get a URL that allows replacing it. Default: false. |
Success Response: 200 OK
{
"bucket": "<GCS_UPLOAD_BUCKET name>",
"object_path": "<partnerId>/<dataset>/<date>/<dataset>.csv",
"upload_url": "https://storage.googleapis.com/....",
"expires_at": "<ISO 8601 timestamp>",
"required_headers": {
"Content-Type": "text/csv"
}
}Object path format: The object path is {partnerId}/{dataset}/{date}/{dataset}.csv. Example: acme/bookings/2025-03-04/bookings.csv. Clients upload to the signed URL; they do not choose the path in the PUT request (it is encoded in the URL).
Error Responses
| Status | Body / condition |
|---|---|
400 | { "error": "invalid_partner_id", "hint": "partnerId must be alphanumeric, 1-64 chars" } — invalid or missing partnerId. |
400 | { "error": "invalid_date", "hint": "use YYYY-MM-DD" } — missing or invalid date. |
400 | { "error": "invalid_dataset" } — invalid dataset. |
404 | { "error": "partner_not_found", "hint": "No partner found for the given partnerId" } — partner does not exist in Firestore. |
409 | { "error": "already_exists", "object_path": "...", "hint": "set overwrite=true if you intend to replace today's file" } — file exists and overwrite is not true. |
500 | { "error": "bucket_not_configured" } — GCS_UPLOAD_BUCKET not set. |
500 | { "error": "internal" } — unexpected server error. |
Upload CSV to the Signed URL
Method: PUT (to the upload_url value from the response above).
Request headers:
| Header | Value | Required |
|---|---|---|
Content-Type | text/csv | Yes |
Request body: Raw CSV content (UTF-8), with header row and one row per booking.
Success response: 200 OK (from Google Cloud Storage). Body may be minimal or empty.
Processing: After a successful upload, a backend trigger processes the file. Processing is asynchronous; bookings are created and import/promo logic runs downstream. There is no separate HTTP response for "booking created" — that is visible in the system (e.g. Firestore, logs) after the trigger runs. Duplicate detection uses partner + booking_id + departure_date; in-file duplicate booking_id is skipped.
Example request:
curl -X PUT -H "Content-Type: text/csv" --upload-file bookings_2026-02-11.csv "UPLOAD_URL_FROM_RESPONSE"Operational Rules
- Upload URL expires automatically (typically within 2 hours).
- Always request a new upload URL before uploading.
- One file per partner per day unless explicitly agreed otherwise. To replace that day's file, request a new upload URL with
overwrite=true.
CSV Format Requirements
Encoding
- UTF-8
- Comma-separated values (CSV)
- Header row required
- ISO 8601 date format where specified
Columns
| Column | Required | Description |
|---|---|---|
booking_id | Yes | Unique booking reference. Deduplication uses partner + booking_id + departure_date. |
departure_date | Yes | ISO date YYYY-MM-DD. |
return_date | No | ISO date YYYY-MM-DD. |
departure_location | No | Origin (e.g. airport code). |
email | Yes | Customer email. Must be valid. |
first_name | No | Customer first name. |
custom_branding | No | Branding key tied to partner configuration. |
destination | Yes | Country: ISO2, ISO3, or full name (resolved to ISO3). |
package_type | Yes | One of: starter, data-limited, unlimited. (time-limited is still accepted but deprecated — use data-limited with a package_duration instead.) |
size | Conditional | Data size, e.g. 1GB, 5GB (format: XGB). Required for data-limited, time-limited, starter. |
package_duration | Conditional | Duration in days (positive integer). Required for time-limited, starter, unlimited. |
locale | No | Optional (e.g. en-US). See Supported locales. |
package_quantity | No | Number of times the row's package specification is granted on the booking (one promo code each). Whole number between 1 and 20. Default: 1 when absent or blank. Rows with an invalid value are rejected. All replicas are identical — multiple different package specifications per row are not supported. |
Validation by package_type
| package_type | Required fields |
|---|---|
data-limited | size |
time-limited (deprecated) | size, package_duration |
starter | size, package_duration |
unlimited | package_duration |
For detailed definitions of package tiers and configuration rules, see Tiered Branding.
Example CSV
booking_id,departure_date,return_date,departure_location,email,first_name,custom_branding,destination,package_type,size,package_duration,locale,package_quantity
ABC123,2026-03-01,2026-03-10,AMS,john@example.com,John,bronze,Japan,data-limited,5GB,,en-US,
DEF456,2026-03-02,2026-03-08,BRU,jane@example.com,Jane,gold,USA,time-limited,2GB,14,en-US,
GHI789,2026-03-03,2026-03-05,LHR,bob@example.com,Bob,,Spain,starter,1GB,2,en-GB,1
JKL012,2026-03-04,,,alice@example.com,Alice,,Thailand,unlimited,,30,en-US,3The last row grants three identical unlimited packages (three promo codes) on one booking via package_quantity=3. Leaving the column blank (or omitting it entirely) grants one.
If you subscribe to the booking.created webhook, each granted package appears as its own entry in the payload's packages array (the deduplicated destinations field does not reflect quantity) — see the webhook reference in the API documentation.
Download a sample CSV with one row per package type: bookings-upload-sample.csv.
Error Handling Recommendations
Partners SHOULD:
- Retry requesting upload URL on API failure.
- Retry upload if HTTP response is non-2xx.
- Log request and response metadata.
Security Model
- Signed upload URLs are time-limited.
- No long-lived cloud credentials required.
- HTTPS-only transport.
Support
support@hubbyesim.com