Last updated

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

  1. Request temporary upload URL.
  2. Upload CSV via HTTP PUT.
  3. 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

ParameterTypeRequiredDescription
partnerIdstringYesPartner identifier (Firestore document ID). Must be alphanumeric, underscore, or hyphen, 1–64 characters. Partner must exist.
datestringYesDate in ISO format YYYY-MM-DD.
datasetstringNoDataset name (e.g. bookings). Default: bookings. Same character rules as partnerId.
overwritestringNoIf 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

StatusBody / 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:

HeaderValueRequired
Content-Typetext/csvYes

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

ColumnRequiredDescription
booking_idYesUnique booking reference. Deduplication uses partner + booking_id + departure_date.
departure_dateYesISO date YYYY-MM-DD.
return_dateNoISO date YYYY-MM-DD.
departure_locationNoOrigin (e.g. airport code).
emailYesCustomer email. Must be valid.
first_nameNoCustomer first name.
custom_brandingNoBranding key tied to partner configuration.
destinationYesCountry: ISO2, ISO3, or full name (resolved to ISO3).
package_typeYesOne of: starter, data-limited, unlimited. (time-limited is still accepted but deprecated — use data-limited with a package_duration instead.)
sizeConditionalData size, e.g. 1GB, 5GB (format: XGB). Required for data-limited, time-limited, starter.
package_durationConditionalDuration in days (positive integer). Required for time-limited, starter, unlimited.
localeNoOptional (e.g. en-US). See Supported locales.
package_quantityNoNumber 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_typeRequired fields
data-limitedsize
time-limited (deprecated)size, package_duration
startersize, package_duration
unlimitedpackage_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,3

The 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