{"openapi":"3.1.0","info":{"title":"Roam eSIMs API","version":"1.0.0","description":"Find travel eSIM options for one or more destinations, take payment, and provision an eSIM — designed for AI assistants and other authorised clients acting on behalf of a traveller. All requests require a connector Bearer key. Installation secrets such as QR codes and activation codes are never returned through this API — after successful payment, Stripe redirects the traveller to their own private Roam installation page.\n"},"servers":[{"url":"https://api.roamesims.com"}],"security":[{"bearerAuth":[]}],"paths":{"/v1/quotes":{"post":{"operationId":"getTravelEsimQuote","summary":"Get travel eSIM plan options for a trip","description":"Find travel eSIM data plans for an upcoming or current international trip. Use this when the traveller asks for an eSIM, mobile data, internet access while travelling, connectivity abroad, or ways to avoid international roaming charges. Returns 1-3 data plan options for the given destination(s), returned in Light, Standard, Heavy order where available.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteRequest"},"examples":{"japanTrip":{"summary":"11-day trip to Japan for 3 travellers","value":{"destinations":["JP"],"start_date":"2026-10-08","end_date":"2026-10-18","travellers":3,"usage":"normal"}}}}}},"responses":{"200":{"description":"Quote created (may be available or unavailable)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteResponse"},"examples":{"available":{"summary":"A typical available quote — normal usage recommends Standard","value":{"status":"available","quote_id":"qt_f3016m0cj928vfesgkzp","trip":{"destinations":["JP"],"start_date":"2026-10-08","end_date":"2026-10-18","duration_days":11,"travellers":3,"usage":"normal"},"currency":"USD","expires_at":"2026-10-01T03:31:42.401Z","plans":[{"offer_id":"of_light","tier":"light","name":"Light","data_gb":3,"validity_days":30,"coverage":["JP"],"network_type":["4G"],"hotspot":false,"top_up_supported":false,"activation_policy":"FIRST_NETWORK_CONNECTION","price":{"per_traveller":8.99,"total":26.97,"currency":"USD"},"recommended":false,"caveats":[]},{"offer_id":"of_standard","tier":"standard","name":"Standard","data_gb":10,"validity_days":30,"coverage":["JP"],"network_type":["4G"],"hotspot":false,"top_up_supported":false,"activation_policy":"FIRST_NETWORK_CONNECTION","price":{"per_traveller":15.99,"total":47.97,"currency":"USD"},"recommended":true,"caveats":[]},{"offer_id":"of_heavy","tier":"heavy","name":"Heavy","data_gb":20,"validity_days":30,"coverage":["JP"],"network_type":["4G"],"hotspot":false,"top_up_supported":false,"activation_policy":"ON_INSTALL","price":{"per_traveller":25.99,"total":77.97,"currency":"USD"},"recommended":false,"caveats":[]}]}},"unavailable":{"summary":"No package covers every requested destination","value":{"status":"unavailable","quote_id":"qt_g7dk2m0cj928vfesgkzq","trip":{"destinations":["JP","GB"],"start_date":"2026-10-08","end_date":"2026-10-18","duration_days":11,"travellers":1,"usage":"normal"},"currency":"USD","reason":"NO_COMPLETE_COVERAGE","plans":[]}}}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"INVALID_REQUEST","message":"destinations must be ISO-3166 alpha-2 codes"}}}},"401":{"description":"Missing or invalid connector key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"UNAUTHORIZED"}}}},"503":{"description":"Live eSIM inventory is temporarily unavailable — distinct from a normal 200 \"unavailable\" result, which means no plan exists for that destination.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"SUPPLIER_UNAVAILABLE","message":"Live eSIM availability could not be checked. Try again shortly."}}}}}}},"/v1/checkouts":{"post":{"operationId":"createEsimCheckout","summary":"Start a checkout for a chosen eSIM plan","description":"Create a hosted payment checkout for an offer previously returned by getTravelEsimQuote. Call only after the traveller has explicitly selected or approved the plan — do not call this simply because international travel was mentioned. This operation does not itself charge the traveller. Return checkout_url to the traveller for payment; do not fetch, scrape, or parse the checkout URL. Safe to retry with the same quote_id/offer_id (e.g. after a network timeout) — the server always returns the original order instead of creating a second one, whether or not an Idempotency-Key is sent.\n","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string"},"description":"Optional extra protection. Retrying with the same key returns the original result immediately, without even re-checking the offer. Not required — retries are safe by default because an offer can only ever back one order.\n"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["quote_id","offer_id"],"properties":{"quote_id":{"type":"string"},"offer_id":{"type":"string"}}},"examples":{"default":{"value":{"quote_id":"qt_f3016m0cj928vfesgkzp","offer_id":"of_standard"}}}}}},"responses":{"201":{"description":"Checkout created (or returned unchanged, if replayed with the same Idempotency-Key)","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["order_id","checkout_url"],"properties":{"order_id":{"type":"string","description":"Opaque order id. Use this (never a token) for all subsequent GET /v1/orders/{id} calls.\n"},"checkout_url":{"type":"string","format":"uri","description":"Hosted Stripe Checkout page. Hand this URL to the traveller directly — do not fetch or parse it. After payment it redirects to the traveller's own private install page.\n"}}},"examples":{"default":{"value":{"order_id":"ord_9f2a1c7e4b8d3f6a2c10","checkout_url":"https://checkout.stripe.com/c/pay/cs_test_..."}}}}}},"400":{"description":"The quote or offer referenced doesn't exist or has expired. Note: a repeated quote_id/offer_id that already has an order does NOT return this — it returns 201 with the existing order (see the operation description above).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"quoteExpired":{"value":{"error":"QUOTE_EXPIRED","message":"Request a new quote."}},"offerNotFound":{"value":{"error":"OFFER_NOT_FOUND"}},"offerUnavailable":{"summary":"Consumed by another checkout attempt with no recoverable order (rare)","value":{"error":"OFFER_UNAVAILABLE","message":"Request a new quote."}}}}}},"409":{"description":"The selected offer is no longer available at the quoted terms","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"OFFER_CHANGED","message":"The selected plan has changed. Request a new quote."}}}},"503":{"description":"Checkout temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/orders/{id}":{"get":{"operationId":"getEsimOrder","summary":"Get the status of an eSIM order","description":"Returns order state plus a next_action hint so the caller doesn't have to infer behaviour from the raw state name. One eSIM is provisioned per traveller — for a multi-traveller order, state is only READY once esims_ready equals esims_expected; until then check esims_ready for partial progress.\n","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The order_id returned by POST /v1/checkouts"}],"responses":{"200":{"description":"Order status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderStatus"},"examples":{"ready":{"summary":"Single-traveller order, fully ready","value":{"order_id":"ord_9f2a1c7e4b8d3f6a2c10","state":"READY","travellers":1,"esims_expected":1,"esims_ready":1,"next_action":"OPEN_INSTALL_PAGE","message":"The eSIM is ready. The traveller can use the private installation page received after checkout.","plan":{"name":"Standard","data_gb":10,"validity_days":30,"coverage":["JP"]},"price":{"total":18.99,"currency":"USD"},"created_at":"2026-10-01T03:21:10.000Z"}},"provisioning":{"summary":"Single-traveller order, still preparing","value":{"order_id":"ord_9f2a1c7e4b8d3f6a2c10","state":"PROVISIONING","travellers":1,"esims_expected":1,"esims_ready":0,"next_action":"WAIT_FOR_PROVISIONING","message":"The eSIM is being prepared. Check again shortly.","plan":{"name":"Standard","data_gb":10,"validity_days":30,"coverage":["JP"]},"price":{"total":18.99,"currency":"USD"},"created_at":"2026-10-01T03:21:10.000Z"}},"groupOrderPartiallyReady":{"summary":"3-traveller order, 2 of 3 eSIMs ready","value":{"order_id":"ord_3g7ouppartialabc123","state":"PROVISIONING","travellers":3,"esims_expected":3,"esims_ready":2,"next_action":"WAIT_FOR_PROVISIONING","message":"2 of 3 eSIMs are ready. The remaining eSIM is still being prepared.","plan":{"name":"Standard","data_gb":10,"validity_days":30,"coverage":["JP"]},"price":{"total":47.97,"currency":"USD"},"created_at":"2026-10-01T03:21:10.000Z"}},"groupOrderReady":{"summary":"3-traveller order, all eSIMs ready","value":{"order_id":"ord_3g7ouppartialabc123","state":"READY","travellers":3,"esims_expected":3,"esims_ready":3,"next_action":"OPEN_INSTALL_PAGE","message":"All 3 eSIMs are ready. The travellers can use the private installation page received after checkout.","plan":{"name":"Standard","data_gb":10,"validity_days":30,"coverage":["JP"]},"price":{"total":47.97,"currency":"USD"},"created_at":"2026-10-01T03:21:10.000Z"}}}}}},"404":{"description":"Order not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/orders/{id}/installation":{"get":{"operationId":"getEsimInstallationStatus","summary":"Check whether the traveller's private eSIM installation page is ready","description":"Check whether the traveller's private eSIM installation page is ready. This endpoint never returns QR codes, LPA strings, or activation credentials.\n\nOnce state is \"ready\", tell the traveller to use the private Roam installation page they were redirected to after payment or received by email.\n","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"The order_id returned by POST /v1/checkouts"}],"responses":{"200":{"description":"Installation status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InstallationStatus"},"examples":{"ready":{"value":{"state":"ready","activation_message":"You can install this now. Your plan begins when the eSIM first connects to a supported network at your destination."}},"preparing":{"value":{"state":"preparing"}}}}}},"404":{"description":"Order not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/health":{"get":{"operationId":"getHealth","summary":"Liveness check","description":"Unauthenticated liveness probe. Not a connector action — infra/monitoring only.","security":[],"responses":{"200":{"description":"Service is up","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["status"],"properties":{"status":{"type":"string","enum":["ok"]}}},"example":{"status":"ok"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Currency":{"type":"string","description":"ISO-4217 presentment currency. The quoted amount is persisted and charged in this exact currency at Stripe Checkout. Omit it to use USD.\n","enum":["USD","EUR","GBP","AUD","CAD","JPY","NZD","SGD","HKD","CNY","KRW","INR","IDR","THB","MYR","PHP","TWD","CHF","SEK","NOK","DKK","PLN","CZK","HUF","RON","TRY","BRL","MXN","ZAR","ILS","AED","SAR","QAR"],"default":"USD"},"Error":{"type":"object","additionalProperties":false,"required":["error"],"description":"Every non-2xx response returns this shape. `error` is a stable machine-readable code; `message` (when present) is a human-readable detail safe to relay to an end user. Never includes internal commercial data such as wholesale cost.\n","properties":{"error":{"type":"string"},"message":{"type":"string"}}},"Money":{"type":"object","additionalProperties":false,"required":["total","currency"],"description":"Used for order-level totals, where there's a single final amount.","properties":{"total":{"type":"number","minimum":0},"currency":{"$ref":"#/components/schemas/Currency"}}},"PlanPrice":{"type":"object","additionalProperties":false,"required":["per_traveller","total","currency"],"description":"Used for quoted plans, where the request may cover multiple travellers — per_traveller times the traveller count equals total.\n","properties":{"per_traveller":{"type":"number","minimum":0},"total":{"type":"number","minimum":0},"currency":{"$ref":"#/components/schemas/Currency"}}},"Trip":{"type":"object","additionalProperties":false,"required":["destinations","start_date","end_date","duration_days","travellers","usage"],"properties":{"destinations":{"type":"array","minItems":1,"maxItems":10,"items":{"type":"string","pattern":"^[A-Z]{2}$"}},"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"},"duration_days":{"type":"integer","minimum":1},"travellers":{"type":"integer","minimum":1,"maximum":5},"usage":{"type":"string","enum":["light","normal","heavy"]}}},"QuoteRequest":{"type":"object","additionalProperties":false,"required":["destinations","start_date","end_date"],"properties":{"destinations":{"type":"array","items":{"type":"string","minLength":2,"maxLength":2},"minItems":1,"maxItems":10,"description":"ISO-3166 alpha-2 country codes"},"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"},"travellers":{"type":"integer","minimum":1,"maximum":5,"default":1},"usage":{"type":"string","enum":["light","normal","heavy"],"default":"normal"},"currency":{"$ref":"#/components/schemas/Currency"}}},"Plan":{"type":"object","additionalProperties":false,"required":["offer_id","tier","name","data_gb","validity_days","coverage","network_type","hotspot","top_up_supported","activation_policy","price","recommended","caveats"],"properties":{"offer_id":{"type":"string"},"tier":{"type":"string","enum":["light","standard","heavy"]},"name":{"type":"string","enum":["Light","Standard","Heavy"]},"data_gb":{"type":"number","exclusiveMinimum":0},"validity_days":{"type":"integer","minimum":1},"coverage":{"type":"array","minItems":1,"items":{"type":"string","pattern":"^[A-Z]{2}$"}},"network_type":{"type":"array","minItems":1,"items":{"type":"string","enum":["3G","4G","5G"]}},"hotspot":{"type":["boolean","null"],"description":"null means unknown — do not tell the traveller hotspot is unsupported."},"top_up_supported":{"type":"boolean"},"activation_policy":{"type":"string","enum":["FIRST_NETWORK_CONNECTION","ON_INSTALL"]},"price":{"allOf":[{"$ref":"#/components/schemas/PlanPrice"}]},"recommended":{"type":"boolean"},"caveats":{"type":"array","items":{"type":"string"}}}},"AvailableQuote":{"type":"object","additionalProperties":false,"required":["status","quote_id","trip","currency","expires_at","plans"],"properties":{"status":{"type":"string","enum":["available"]},"quote_id":{"type":"string"},"trip":{"$ref":"#/components/schemas/Trip"},"currency":{"$ref":"#/components/schemas/Currency"},"expires_at":{"type":"string","format":"date-time"},"plans":{"type":"array","minItems":1,"maxItems":3,"items":{"$ref":"#/components/schemas/Plan"}}}},"UnavailableQuote":{"type":"object","additionalProperties":false,"required":["status","quote_id","trip","currency","reason","plans"],"properties":{"status":{"type":"string","enum":["unavailable"]},"quote_id":{"type":"string","description":"Traceable even though it can never be checked out — useful for logs/support."},"trip":{"$ref":"#/components/schemas/Trip"},"currency":{"$ref":"#/components/schemas/Currency"},"reason":{"type":"string","enum":["NO_COMPLETE_COVERAGE","NO_ELIGIBLE_PACKAGES"]},"plans":{"type":"array","maxItems":0}}},"QuoteResponse":{"oneOf":[{"$ref":"#/components/schemas/AvailableQuote"},{"$ref":"#/components/schemas/UnavailableQuote"}],"discriminator":{"propertyName":"status","mapping":{"available":"#/components/schemas/AvailableQuote","unavailable":"#/components/schemas/UnavailableQuote"}}},"OrderPlan":{"type":"object","additionalProperties":false,"required":["name","data_gb","validity_days","coverage"],"properties":{"name":{"type":"string"},"data_gb":{"type":"number","exclusiveMinimum":0},"validity_days":{"type":"integer","minimum":1},"coverage":{"type":"array","minItems":1,"items":{"type":"string","pattern":"^[A-Z]{2}$"}}}},"OrderStatus":{"type":"object","additionalProperties":false,"required":["order_id","state","travellers","esims_expected","esims_ready","next_action","message","plan","price","created_at"],"properties":{"order_id":{"type":"string"},"state":{"type":"string","enum":["AWAITING_PAYMENT","PAID","PROVISIONING","READY","FULFILMENT_FAILED","NEEDS_RECONCILIATION","REFUND_PENDING","REFUNDED","CANCELLED"]},"travellers":{"type":"integer","minimum":1,"description":"Number of travellers this order covers — one eSIM is provisioned per traveller."},"esims_expected":{"type":"integer","minimum":1,"description":"Always equal to travellers."},"esims_ready":{"type":"integer","minimum":0,"description":"How many of the expected eSIMs have finished provisioning. state is only READY once esims_ready equals esims_expected — READY never means \"at least one is ready\".\n"},"next_action":{"type":"string","enum":["COMPLETE_PAYMENT","WAIT_FOR_PROVISIONING","OPEN_INSTALL_PAGE","NONE"],"description":"What the caller should do next, so behaviour need not be inferred from state."},"message":{"type":"string","description":"Human-readable, safe to relay to the traveller as-is."},"plan":{"$ref":"#/components/schemas/OrderPlan"},"price":{"allOf":[{"$ref":"#/components/schemas/Money"}]},"created_at":{"type":"string","format":"date-time"}}},"InstallationStatus":{"type":"object","additionalProperties":false,"required":["state"],"description":"Status only. QR/LPA/activation codes are never exposed via the API.","properties":{"state":{"type":"string","enum":["confirming","preparing","ready","failed"]},"activation_message":{"type":"string"}}}}}}