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, time-limited, unlimited. |
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. |
Validation by package_type
| package_type | Required fields |
|---|---|
data-limited | size |
time-limited | 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
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
JKL012,2026-03-04,,,alice@example.com,Alice,,Thailand,unlimited,,30,en-USDownload 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