Hubby Partner API (5.20.0)

Every response of a matched route carries the header X-Hubby-Call-Id, the id of the call in Hubby's records. A request refused before it reaches a route carries no call id: an unknown path (404, code not_found), a method the path does not take (405, code method_not_allowed), or a query string PHP cannot parse whole (400, code malformed_query_string). Those two refusals, 405 and 400, can answer any path and are not listed per operation. Every 4xx refusal body carries a message and a code to branch on. A 5xx can carry a message alone: the issue call's 500 (POST /v1/bookings/{booking}/items/{item}/package) has no code; retry it with the same Idempotency-Key.

Download OpenAPI description
Languages
Servers
Production
https://core.hubbyesim.com/api/
Staging
https://core-staging.hubbyesim.com/api/

Analytics

Operations

Booking

Operations

BookingItemPackage

Operations

Bundle

Operations

CurrentPartner

Operations

Destination

Operations

DeviceCompatibility

Operations

Esim

Operations

Read an eSIM and its packages

Request

One of your travellers' eSIMs with its packages and their usage, from Hubby's last readout of the network; refreshed_at says when that was. The plain read is cheap and may be polled. Add refresh=true to read the network now: that is rate-limited per Partner. Any ICCID that is not held by one of your own travellers answers 404.

Path
iccidstringrequired
Query
refreshstring or null

true reads the eSIM from the network now instead of answering the last readout. Rate-limited per Partner; 429 with Retry-After past the limit.

Enum"1""0""true""false"null
curl -i -X GET \
  'https://core.hubbyesim.com/api/v1/esims/{iccid}?refresh=1' \
  -H 'X-Api-Key: YOUR_API_KEY_HERE' \
  -H 'X-Signature: YOUR_API_KEY_HERE' \
  -H 'X-Timestamp: YOUR_API_KEY_HERE'

Responses

Esim

Headers
X-Hubby-Call-Idstringrequired

The id of this call in Hubby's records. Quote it when you ask Hubby about a call.

Bodyapplication/json
dataobject(Esim)required
data.​iccidstringrequired

The eSIM's ICCID.

data.​external_user_idstring or nullrequired

Your own identifier for the traveller holding this eSIM.

data.​installed_atstring or null(date-time)required

When the eSIM was first installed on a phone. Null until then.

data.​packagesArray of objects(EsimPackage)required

Every package on this eSIM, oldest first, ended ones included.

data.​packages[].​idstringrequired
data.​packages[].​booking_idstring or nullrequired

The booking this package was issued from: Hubby's booking id (the id of GET /v1/bookings/{id}), not your reference. The webhooks send your external_reference under the same name, so do not join the two on it. Null for a package the traveller bought in the Hubby app, and for a package redeemed with a promo code that belongs to no booking.

data.​packages[].​statestring(PackageLifecycleState)required

Where this package is in its lifecycle, the same state its booking item carries: redeemed, installed, activated, expired (the validity elapsed, or the provider ended the package after it activated; a package that used up its data stays activated until the provider ends it) or terminated (ended before activation).

Enum"queued""unavailable""redeemed""installed""activated""expired""terminated""cancelled"
data.​packages[].​bundle_idstringrequired
data.​packages[].​destinationobject(DestinationSummary)required

The destination this package is for, the same object a bundle carries in GET /v1/bundles. Always set, also on a package that has ended and on a package whose bundle is no longer sold.

data.​packages[].​destination.​idstringrequired

The destination's id: the id of GET /v1/destinations/{destination}.

data.​packages[].​destination.​namestring or nullrequired

The destination's English name, for example Spain or Europe.

data.​packages[].​destination.​typestring(DestinationKind)required

country or region.

Enum"country""region"
data.​packages[].​destination.​iso2string or nullrequired

The country's ISO 3166-1 alpha-2 code, or the region's code when the region keeps it here (for example EU). Null when the region keeps its code in iso3.

data.​packages[].​destination.​iso3string or nullrequired

The country's ISO 3166-1 alpha-3 code, or the region's code when the region keeps it here (for example XCA). Null when the region keeps its code in iso2.

data.​packages[].​activated_atstring or null(date-time)required

When the package activated and its validity started: the time the connectivity provider reported the package activated. Hubby does not observe the network attach itself. Null before.

data.​packages[].​expires_atstring or null(date-time)required

The latest moment this package can still be activated: the deadline the connectivity provider sets when the package is issued, after which an unused package can no longer be started. It is not when the package's validity ends: a package with a few days of validity is typically still activatable for about a year. Use valid_until for when the package stops working.

data.​packages[].​valid_untilstring or null(date-time)required

When this package's validity ends and its data stops working: the moment it activated plus its time allowance. Null until the package activates, because the clock starts at activation, not at issue. Also null for a package that carries no time allowance.

data.​packages[].​data_used_bytesintegerrequired

The total data used since activation, in bytes, from Hubby's last readout of the network. It is not the use of the current day, and the API gives no daily figure: on an unlimited package, do not compare it with fair_use_throttle.throttle_after_bytes, which applies per day.

data.​packages[].​data_remaining_bytesinteger or nullrequired

Data left, in bytes. 0 once the package has ended (state is expired or terminated), whatever was unused: an ended package has no data the traveller can still use. Null on an unlimited package, or when the allowance is not known.

data.​refreshed_atstring or null(date-time)required

When the usage figures were last read from the network. Null when they never were. Send refresh=true to read them now.

Response
application/json
{ "data": { "iccid": "string", "external_user_id": "string", "installed_at": "2019-08-24T14:15:22Z", "packages": [ … ], "refreshed_at": "2019-08-24T14:15:22Z" } }

Read what a traveller can still use on an eSIM, per destination

Request

What the traveller can still use on this eSIM, one entry per destination: packages for the same destination add up into one entry, so two Spain top-ups are one Spain entry with their data added together. Data and unlimited packages for one destination are two entries; a country and a region that covers it are two entries. An entry exists while at least one of its packages is live; packages that ended earlier on the same trip still count in its totals. Newest first, by the most recently added live package. An empty data means nothing on the eSIM can still be used. Packages the traveller bought in the Hubby app count too. Answers for every ICCID that GET /v1/esims/{iccid} answers for, and takes refresh the same way, under the same rate limit. Any ICCID that is not held by one of your own travellers answers 404.

Path
iccidstringrequired
Query
refreshstring or null

true reads the eSIM from the network now instead of answering the last readout. Rate-limited per Partner; 429 with Retry-After past the limit.

Enum"1""0""true""false"null
curl -i -X GET \
  'https://core.hubbyesim.com/api/v1/esims/{iccid}/usage?refresh=1' \
  -H 'X-Api-Key: YOUR_API_KEY_HERE' \
  -H 'X-Signature: YOUR_API_KEY_HERE' \
  -H 'X-Timestamp: YOUR_API_KEY_HERE'

Responses

Array of DestinationUsage

Headers
X-Hubby-Call-Idstringrequired

The id of this call in Hubby's records. Quote it when you ask Hubby about a call.

Bodyapplication/json
dataArray of objects(DestinationUsage)required
data[].​destinationobject(DestinationSummary)required

The destination every package in this entry is for, the same object a bundle carries in GET /v1/bundles. A country and a region that covers it are two entries. Top up this entry with a bundle for this destination.

data[].​destination.​idstringrequired

The destination's id: the id of GET /v1/destinations/{destination}.

data[].​destination.​namestring or nullrequired

The destination's English name, for example Spain or Europe.

data[].​destination.​typestring(DestinationKind)required

country or region.

Enum"country""region"
data[].​destination.​iso2string or nullrequired

The country's ISO 3166-1 alpha-2 code, or the region's code when the region keeps it here (for example EU). Null when the region keeps its code in iso3.

data[].​destination.​iso3string or nullrequired

The country's ISO 3166-1 alpha-3 code, or the region's code when the region keeps it here (for example XCA). Null when the region keeps its code in iso2.

data[].​is_unlimitedbooleanrequired

True for unlimited packages. total_bytes and remaining_bytes are then null; used_bytes is still set. A destination with unlimited and data packages has one entry of each.

data[].​total_bytesinteger or nullrequired

The data allowance of every package in package_ids added up, in bytes, ended packages included. Always used_bytes + remaining_bytes. Draw a gauge from remaining_bytes against total_bytes, as the Hubby app does. Null when is_unlimited.

data[].​used_bytesintegerrequired

The data that is no longer available, in bytes: total_bytes - remaining_bytes, the figure the Hubby app shows as used. Data an ended package of the same trip did not use counts as used, because it can no longer be used. On unlimited entries, where there is no total, the data the packages in package_ids used, added up.

data[].​remaining_bytesinteger or nullrequired

The data the traveller can still use, in bytes: what the live packages have left, added up, and never below 0. A provider can report a package slightly over its allowance; that overrun is taken from what the entry's other live packages have left. 0 means used up, even when valid_until is still ahead; a top-up adds to it. Null when is_unlimited.

data[].​valid_untilstring or null(date-time)required

When this entry's time runs out. The packages in one entry are for one destination, so they run one after the other: a package waits until the one before it is used up or ends. This is the end of the running package plus the whole validity of every package waiting after it. For data, it is the latest end: a package that is used up early starts the next one early, and the entry's data stops when remaining_bytes reaches 0. Null while no package in this entry is running: every live package in it still waits to start, and the first one starts on first use. An entry with a running package always has a date. Null does not mean nothing was used: the entry also lists its packages that have already ended, and a package of the other type for the same destination can be running while this entry waits for it. See remaining_seconds for the time left.

data[].​remaining_secondsintegerrequired

The time left on this entry, in seconds, the figure behind "N days left" in the Hubby app: the time left on the running package plus the whole validity of every package waiting after it. Set even while valid_until is null: then it is the validity of every package in the entry, added up. Counts down only once a package has started.

data[].​package_idsArray of stringsrequired

Every package this entry adds up, oldest first, ended packages of the same trip included: the id of packages in GET /v1/esims/{iccid}. Packages the traveller bought in the Hubby app count too.

data[].​synced_atstring or null(date-time)required

When these figures were last read from the network: the oldest read among the entry's live packages. Null when one of them was never read. Send refresh=true to read them now.

Response
application/json
{ "data": [ { … } ] }

Read the installation instructions for an eSIM

Request

Step-by-step installation instructions for this eSIM, per phone family, in the traveller's language, with the eSIM's own activation material and the Partner name (name in GET /v1/me) filled in. The copy is the guided setup of the Hubby web app. Copy and media are maintained by Hubby and may change between calls without a version bump; do not cache them longer than a week. Answers for every ICCID that GET /v1/esims/{iccid} answers for, also after you terminated its packages or cancelled their booking. The language is locale from the query, else the locale of your latest booking that issued a package on this eSIM (live or terminated), else your default_locale (GET /v1/me), else en-US. The eSIM label is the Partner name followed by eSIM; a Visual identity has no effect on it. An Android family can also carry an APN setup (apn), for an eSIM that has signal but no internet; it is null on the iPhone. Any ICCID that is not held by one of your own travellers answers 404. 503 when the instructions cannot be read right now; retry later.

Path
iccidstringrequired
Query
localeSupportedLocale (string) or null

The language of the instructions, one of the SupportedLocale tags such as de-DE; another tag is refused with 422. Default: the locale of your latest booking that issued a package on this eSIM (live or terminated), else your default_locale (GET /v1/me), else en-US. Copy without a translation in that language is English.

Any of:

The locale tags locale accepts on GET /v1/esims/{iccid}/instructions: BCP-47 language-region tags. A tag outside this list is refused with 422. A booking locale is freer: it can name a language outside this list, for example fi or ja, and the instructions follow it when copy exists in that language. Copy without a translation in the requested language is English.

string(SupportedLocale)

The locale tags locale accepts on GET /v1/esims/{iccid}/instructions: BCP-47 language-region tags. A tag outside this list is refused with 422. A booking locale is freer: it can name a language outside this list, for example fi or ja, and the instructions follow it when copy exists in that language. Copy without a translation in the requested language is English.

Enum"en-US""en-EU""en-GB""en-CA""nl-NL""de-DE""fr-FR""fr-CA""it-IT""es-ES"
curl -i -X GET \
  'https://core.hubbyesim.com/api/v1/esims/{iccid}/instructions?locale=en-US' \
  -H 'X-Api-Key: YOUR_API_KEY_HERE' \
  -H 'X-Signature: YOUR_API_KEY_HERE' \
  -H 'X-Timestamp: YOUR_API_KEY_HERE'

Responses

InstallInstructions

Headers
X-Hubby-Call-Idstringrequired

The id of this call in Hubby's records. Quote it when you ask Hubby about a call.

Bodyapplication/json
dataobject(InstallInstructions)required
data.​iccidstringrequired

The eSIM's ICCID.

data.​localestringrequired

The locale the instructions were asked in: locale from the query, else the locale of your latest booking that issued a package on this eSIM (live or terminated), else your default_locale (GET /v1/me), else en-US. A booking's locale is echoed as stored, so this can be a short tag such as es. The copy exists in English, German, Spanish and Swedish; for any other language it is English, and this field still echoes the locale asked for.

data.​esim_labelstringrequired

The name the traveller gives the eSIM on the phone: the Partner name (name in GET /v1/me) followed by eSIM. The steps already name this label wherever the traveller types or selects it. A Visual identity has no effect on it.

data.​iosArray of objects(InstallInstructionSet)required

One entry per iOS phone family: iphone.

data.​ios[].​familystringrequired

The phone family: iphone on iOS; pixel, samsung or xiaomi on Android. For an Android phone you cannot place, use samsung. Find a family by this value, not by its position in the list.

data.​ios[].​one_tapobjectrequired

Install on the phone itself: open install_link on that phone, then follow the steps the OS asks. On a manual-only family (Xiaomi today) there is no link, and the steps are the manual steps.

data.​ios[].​one_tap.​titlestringrequired
data.​ios[].​one_tap.​install_linkstring or nullrequired

The OS's own one-tap install link for this eSIM. Null for a manual-only family (Xiaomi today): that phone has no one-tap install, so show the manual section. Also null when the eSIM's activation string is not in the standard LPA:1$… form.

data.​ios[].​one_tap.​clip_urlstring or nullrequired
data.​ios[].​one_tap.​stepsArray of objects(InstallInstructionStep)required
data.​ios[].​one_tap.​steps[].​textstringrequired

The step, in the response locale where its section is translated; the qr_code section is English only on every phone family. Tap targets are marked with markdown bold, **Activate**, and nothing else; render or strip it.

data.​ios[].​one_tap.​steps[].​screenshot_urlstring or nullrequired

An image of the screen for this step, in this language. Null when none exists.

data.​ios[].​qr_codeobjectrequired

Install from a second screen: render lpa as a QR code and scan it with the phone. On every phone family this section is Hubby's own English copy, whatever the locale, with clip_url null and no screenshots.

data.​ios[].​qr_code.​titlestringrequired
data.​ios[].​qr_code.​lpastring or nullrequired

The eSIM's activation string, to render as a QR code.

data.​ios[].​qr_code.​clip_urlstring or nullrequired
data.​ios[].​qr_code.​stepsArray of objects(InstallInstructionStep)required
data.​ios[].​qr_code.​steps[].​textstringrequired

The step, in the response locale where its section is translated; the qr_code section is English only on every phone family. Tap targets are marked with markdown bold, **Activate**, and nothing else; render or strip it.

data.​ios[].​qr_code.​steps[].​screenshot_urlstring or nullrequired

An image of the screen for this step, in this language. Null when none exists.

data.​ios[].​manualobjectrequired

Install by typing the SM-DP+ address and the matching id in the phone's settings. The steps are localized on every phone family, in the locale where translated, else English.

data.​ios[].​manual.​titlestringrequired
data.​ios[].​manual.​smdp_addressstring or nullrequired

The SM-DP+ address from the activation string. Null when the string is not in the standard form.

data.​ios[].​manual.​matching_idstring or nullrequired

The matching id (activation code) from the activation string. Null when the string is not in the standard form.

data.​ios[].​manual.​clip_urlstring or nullrequired
data.​ios[].​manual.​stepsArray of objects(InstallInstructionStep)required
data.​ios[].​manual.​steps[].​textstringrequired

The step, in the response locale where its section is translated; the qr_code section is English only on every phone family. Tap targets are marked with markdown bold, **Activate**, and nothing else; render or strip it.

data.​ios[].​manual.​steps[].​screenshot_urlstring or nullrequired

An image of the screen for this step, in this language. Null when none exists.

data.​ios[].​on_arrivalArray of objects(InstallInstructionSection)required

What to do on arrival: rename the line, turn it on, enable roaming, pick it for mobile data. Show the sections in the order of this list. The order can be different for each family (on Samsung and Xiaomi, mobile data comes before roaming), so find a section by its id, not by its position.

data.​ios[].​on_arrival[].​idstring or nullrequired

What the section does: rename the line, turn_on the line, enable roaming, pick the line for mobile_data. Each id comes once per family.

data.​ios[].​on_arrival[].​titlestringrequired

The section's title, in the response locale where translated, else English.

data.​ios[].​on_arrival[].​clip_urlstring or nullrequired

A short screen recording of the whole section on this phone family, in this language. Null when none exists.

data.​ios[].​on_arrival[].​stepsArray of objects(InstallInstructionStep)required

The steps, in order.

data.​ios[].​on_arrival[].​steps[].​textstringrequired

The step, in the response locale where its section is translated; the qr_code section is English only on every phone family. Tap targets are marked with markdown bold, **Activate**, and nothing else; render or strip it.

data.​ios[].​on_arrival[].​steps[].​screenshot_urlstring or nullrequired

An image of the screen for this step, in this language. Null when none exists.

data.​ios[].​apnInstallInstructionApnSetup (object) or nullrequired

Optional help for an eSIM that has signal but no internet: add the access point name (APN) by hand. Show it only then, for example on a "No internet?" screen, not in the main install flow. Many eSIMs set the APN automatically and never need it. Null when the phone family does not need it (iphone today).

Any of:

Optional help for an eSIM that has signal but no internet: add the access point name (APN) by hand. Show it only then, for example on a "No internet?" screen, not in the main install flow. Many eSIMs set the APN automatically and never need it. Null when the phone family does not need it (iphone today).

data.​ios[].​apn.​apnstringrequired

The access point name to type. The steps already name it; your app can also show it with a copy button.

data.​ios[].​apn.​titlestringrequired

The section's title, in the response locale where translated, else English.

data.​ios[].​apn.​clip_urlstring or nullrequired

A short screen recording of the APN setup on this phone family, in this language. Null when none exists.

data.​ios[].​apn.​stepsArray of objects(InstallInstructionStep)required

The steps, in order.

data.​ios[].​apn.​steps[].​textstringrequired

The step, in the response locale where its section is translated; the qr_code section is English only on every phone family. Tap targets are marked with markdown bold, **Activate**, and nothing else; render or strip it.

data.​ios[].​apn.​steps[].​screenshot_urlstring or nullrequired

An image of the screen for this step, in this language. Null when none exists.

data.​androidArray of objects(InstallInstructionSet)required

One entry per Android phone family: pixel, samsung, xiaomi.

data.​android[].​familystringrequired

The phone family: iphone on iOS; pixel, samsung or xiaomi on Android. For an Android phone you cannot place, use samsung. Find a family by this value, not by its position in the list.

data.​android[].​one_tapobjectrequired

Install on the phone itself: open install_link on that phone, then follow the steps the OS asks. On a manual-only family (Xiaomi today) there is no link, and the steps are the manual steps.

data.​android[].​one_tap.​titlestringrequired
data.​android[].​one_tap.​install_linkstring or nullrequired

The OS's own one-tap install link for this eSIM. Null for a manual-only family (Xiaomi today): that phone has no one-tap install, so show the manual section. Also null when the eSIM's activation string is not in the standard LPA:1$… form.

data.​android[].​one_tap.​clip_urlstring or nullrequired
data.​android[].​one_tap.​stepsArray of objects(InstallInstructionStep)required
data.​android[].​one_tap.​steps[].​textstringrequired

The step, in the response locale where its section is translated; the qr_code section is English only on every phone family. Tap targets are marked with markdown bold, **Activate**, and nothing else; render or strip it.

data.​android[].​one_tap.​steps[].​screenshot_urlstring or nullrequired

An image of the screen for this step, in this language. Null when none exists.

data.​android[].​qr_codeobjectrequired

Install from a second screen: render lpa as a QR code and scan it with the phone. On every phone family this section is Hubby's own English copy, whatever the locale, with clip_url null and no screenshots.

data.​android[].​qr_code.​titlestringrequired
data.​android[].​qr_code.​lpastring or nullrequired

The eSIM's activation string, to render as a QR code.

data.​android[].​qr_code.​clip_urlstring or nullrequired
data.​android[].​qr_code.​stepsArray of objects(InstallInstructionStep)required
data.​android[].​qr_code.​steps[].​textstringrequired

The step, in the response locale where its section is translated; the qr_code section is English only on every phone family. Tap targets are marked with markdown bold, **Activate**, and nothing else; render or strip it.

data.​android[].​qr_code.​steps[].​screenshot_urlstring or nullrequired

An image of the screen for this step, in this language. Null when none exists.

data.​android[].​manualobjectrequired

Install by typing the SM-DP+ address and the matching id in the phone's settings. The steps are localized on every phone family, in the locale where translated, else English.

data.​android[].​manual.​titlestringrequired
data.​android[].​manual.​smdp_addressstring or nullrequired

The SM-DP+ address from the activation string. Null when the string is not in the standard form.

data.​android[].​manual.​matching_idstring or nullrequired

The matching id (activation code) from the activation string. Null when the string is not in the standard form.

data.​android[].​manual.​clip_urlstring or nullrequired
data.​android[].​manual.​stepsArray of objects(InstallInstructionStep)required
data.​android[].​manual.​steps[].​textstringrequired

The step, in the response locale where its section is translated; the qr_code section is English only on every phone family. Tap targets are marked with markdown bold, **Activate**, and nothing else; render or strip it.

data.​android[].​manual.​steps[].​screenshot_urlstring or nullrequired

An image of the screen for this step, in this language. Null when none exists.

data.​android[].​on_arrivalArray of objects(InstallInstructionSection)required

What to do on arrival: rename the line, turn it on, enable roaming, pick it for mobile data. Show the sections in the order of this list. The order can be different for each family (on Samsung and Xiaomi, mobile data comes before roaming), so find a section by its id, not by its position.

data.​android[].​on_arrival[].​idstring or nullrequired

What the section does: rename the line, turn_on the line, enable roaming, pick the line for mobile_data. Each id comes once per family.

data.​android[].​on_arrival[].​titlestringrequired

The section's title, in the response locale where translated, else English.

data.​android[].​on_arrival[].​clip_urlstring or nullrequired

A short screen recording of the whole section on this phone family, in this language. Null when none exists.

data.​android[].​on_arrival[].​stepsArray of objects(InstallInstructionStep)required

The steps, in order.

data.​android[].​on_arrival[].​steps[].​textstringrequired

The step, in the response locale where its section is translated; the qr_code section is English only on every phone family. Tap targets are marked with markdown bold, **Activate**, and nothing else; render or strip it.

data.​android[].​on_arrival[].​steps[].​screenshot_urlstring or nullrequired

An image of the screen for this step, in this language. Null when none exists.

data.​android[].​apnInstallInstructionApnSetup (object) or nullrequired

Optional help for an eSIM that has signal but no internet: add the access point name (APN) by hand. Show it only then, for example on a "No internet?" screen, not in the main install flow. Many eSIMs set the APN automatically and never need it. Null when the phone family does not need it (iphone today).

Any of:

Optional help for an eSIM that has signal but no internet: add the access point name (APN) by hand. Show it only then, for example on a "No internet?" screen, not in the main install flow. Many eSIMs set the APN automatically and never need it. Null when the phone family does not need it (iphone today).

data.​android[].​apn.​apnstringrequired

The access point name to type. The steps already name it; your app can also show it with a copy button.

data.​android[].​apn.​titlestringrequired

The section's title, in the response locale where translated, else English.

data.​android[].​apn.​clip_urlstring or nullrequired

A short screen recording of the APN setup on this phone family, in this language. Null when none exists.

data.​android[].​apn.​stepsArray of objects(InstallInstructionStep)required

The steps, in order.

data.​android[].​apn.​steps[].​textstringrequired

The step, in the response locale where its section is translated; the qr_code section is English only on every phone family. Tap targets are marked with markdown bold, **Activate**, and nothing else; render or strip it.

data.​android[].​apn.​steps[].​screenshot_urlstring or nullrequired

An image of the screen for this step, in this language. Null when none exists.

Response
application/json
{ "data": { "iccid": "string", "locale": "de-DE", "esim_label": "Acme Travel eSIM", "ios": [ … ], "android": [ … ] } }

RedirectToken

Operations