Last updated

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, time-limited, unlimited.
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.

Validation by package_type

package_typeRequired fields
data-limitedsize
time-limitedsize, 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
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-US

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