openapi: 3.1.0
info:
  title: co:sim Travel eSIM API
  version: "1.0"
  description: |
    Help travelers find and buy affordable eSIM data plans for 190+ countries.

    **Workflow for AI agents:**
    1. Search destinations by country name or region
    2. List and compare packages (data, duration, price, network)
    3. Optionally calculate price with discount codes
    4. Create a checkout link — share it with the user
    5. The user clicks the link to review the order and pay via Stripe
    6. eSIM QR code is delivered to their email

    All browse endpoints require no authentication. The checkout endpoint is rate-limited by IP.

    **MCP server** — MCP-capable agents can connect to https://cosim.io/mcp
    (Model Context Protocol, Streamable HTTP, no auth) for typed tools covering
    the same catalog and checkout capabilities. Registry name: io.cosim/esim.

    **Required: User-Agent header** — All requests must include a User-Agent identifying your client and model:
    `{ClientName}/{ClientVersion} ({Model}/{ModelVersion})`
    Example: `ChatGPT-Plugins/1.2.0 (gpt-4o/2024-08-06)`

    Full documentation: https://cosim.io/llms-full.txt
  contact:
    name: co:sim Support
    url: https://cosim.io
    email: support@cosim.io

servers:
  - url: https://cosim.io/api/v1
    description: Production

paths:
  /esim/locations:
    get:
      operationId: searchDestinations
      summary: Search eSIM destinations by country name or region
      description: |
        Returns destinations with package counts and starting prices.
        Supports multilingual search — try "Japan", "日本", "Japon", "일본".
        Results are sorted by relevance to the user's location.
      tags: [Browse]
      parameters:
        - name: search
          in: query
          description: Country or region name in any language.
          schema:
            type: string
          example: Japan
        - name: region
          in: query
          description: >-
            Filter by geographic region. Use `regional` to list only
            multi-country packages; `global` covers worldwide packages.
          schema:
            type: string
            enum: [asia, europe, americas, oceania, africa, middle-east, global, other, regional]
      responses:
        "200":
          description: List of matching destinations.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      items:
                        type: array
                        items:
                          type: object
                          properties:
                            code:
                              type: string
                              description: "Destination code (e.g. 'JP', 'EU-42')."
                            name:
                              type: string
                            region:
                              type: string
                            isRegional:
                              type: boolean
                            packageCount:
                              type: integer
                            startPrice:
                              type: object
                              nullable: true
                              properties:
                                amount:
                                  type: number
                                currency:
                                  type: string
                      regions:
                        type: array
                        items:
                          type: object
                          properties:
                            code:
                              type: string
                            name:
                              type: string
                            count:
                              type: integer
                  meta:
                    $ref: "#/components/schemas/Meta"

  /esim/locations/{code}/packages:
    get:
      operationId: listPackages
      summary: List all eSIM packages for a destination
      description: |
        Returns detailed plan information. Use the package `id` in calculatePrice or createCheckout.
        Key fields to present to users: dataVolume.display, duration.days, price.amount, speed, supportTopup.
      tags: [Browse]
      parameters:
        - name: code
          in: path
          required: true
          description: "Destination code (e.g. 'JP', 'TH', 'EU-42')."
          schema:
            type: string
        - name: sort
          in: query
          description: Sort order for packages.
          schema:
            type: string
            enum: [price, data, validity]
            default: price
        - name: data_type
          in: query
          description: Filter by data type.
          schema:
            type: string
            enum: [total, daily]
      responses:
        "200":
          description: Packages for the destination.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      location:
                        type: object
                        properties:
                          code:
                            type: string
                          name:
                            type: string
                          region:
                            type: string
                          isRegional:
                            type: boolean
                      packages:
                        type: array
                        items:
                          $ref: "#/components/schemas/PackageSummary"
                  meta:
                    $ref: "#/components/schemas/Meta"
        "404":
          description: Destination not found.

  /esim/packages/{id}:
    get:
      operationId: getPackage
      summary: Get detailed info about a specific eSIM package
      tags: [Browse]
      parameters:
        - name: id
          in: path
          required: true
          description: Package ID or slug.
          schema:
            type: string
      responses:
        "200":
          description: Package details.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                      slug:
                        type: string
                        nullable: true
                      name:
                        type: string
                      destination:
                        type: object
                        properties:
                          code:
                            type: string
                          name:
                            type: string
                          region:
                            type: string
                      coverage:
                        type: array
                        items:
                          type: string
                        description: Country codes covered by this plan.
                      dataVolume:
                        type: object
                        properties:
                          mb:
                            type: integer
                          display:
                            type: string
                          type:
                            type: string
                          description:
                            type: string
                      validity:
                        type: object
                        properties:
                          days:
                            type: integer
                          display:
                            type: string
                      price:
                        type: object
                        properties:
                          amount:
                            type: number
                          currency:
                            type: string
                          formatted:
                            type: string
                            description: "Formatted price string (e.g. '$3.50')."
                      network:
                        type: object
                        properties:
                          type:
                            type: string
                            nullable: true
                          speed:
                            type: string
                            nullable: true
                      features:
                        type: object
                        properties:
                          supportTopup:
                            type: boolean
                          billingStart:
                            type: string
                            nullable: true
                  meta:
                    $ref: "#/components/schemas/Meta"
        "404":
          description: Package not found.

  /esim/recommend:
    get:
      operationId: recommendPlans
      summary: Recommend up to 3 plans for a trip (cheapest / best value / most data)
      description: |
        Needs-driven plan selector. Provide trip countries, duration, and data appetite;
        the engine filters the full catalog to plans that cover all requested countries
        and have enough data for the trip, then returns up to 3 distinct slots.

        The `checkoutPath` in each slot can be shared directly with the user — just
        prepend the locale prefix (e.g. `/en${checkoutPath}`).

        Rate limit: 30 requests per minute per IP.
      tags: [Browse]
      parameters:
        - name: countries
          in: query
          required: true
          description: >-
            Comma-separated ISO-3166-1 alpha-2 country codes (e.g. "JP,KR").
            1–10 countries. Matched against plan coverage.
          schema:
            type: string
            maxLength: 40
          example: JP,KR
        - name: days
          in: query
          required: true
          description: Trip duration in days (1–90).
          schema:
            type: integer
            minimum: 1
            maximum: 90
          example: 7
        - name: usage
          in: query
          required: false
          description: >-
            Data appetite tier. light ≈ maps/messaging (~0.3 GB/day),
            medium ≈ social/photos (~0.7 GB/day, default),
            heavy ≈ video/hotspot (~1.5 GB/day).
          schema:
            type: string
            enum: [light, medium, heavy]
            default: medium
      responses:
        "200":
          description: Recommendation result. `slots` is empty when no plan covers the trip.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      slots:
                        type: array
                        description: Up to 3 distinct recommendation slots.
                        items:
                          type: object
                          properties:
                            slot:
                              type: string
                              enum: [cheapest, best_value, most_data]
                            plan:
                              type: object
                              properties:
                                id:
                                  type: string
                                slug:
                                  type: string
                                  nullable: true
                                name:
                                  type: string
                                destinationCode:
                                  type: string
                                dataDisplay:
                                  type: string
                                  description: "Human-readable data amount (e.g. '5GB', '1GB/day')."
                                dataType:
                                  type: string
                                  description: "total | daily_throttle | daily_cutoff | daily_unlimited"
                                validityDays:
                                  type: integer
                            usableGb:
                              type: number
                              description: Total usable GB for this trip (daily plans × days).
                            tripPriceUsd:
                              type: number
                              description: Effective price for this trip after pricing-program discounts.
                            pricePerGb:
                              type: number
                              description: tripPriceUsd / usableGb.
                            coverageCount:
                              type: integer
                              description: Number of countries this plan covers.
                            redundancy:
                              type: number
                              description: "0 = exact fit; >0 = plan covers more countries than requested."
                            checkoutPath:
                              type: string
                              description: >-
                                Relative path to the checkout page. Prepend locale prefix before sharing
                                (e.g. /en/data-only/jp/checkout?plan=<id>). Daily plans include &days=<n>.
                            detailsPath:
                              type: string
                              description: Relative path to the destination detail page.
                      meta:
                        type: object
                        properties:
                          countries:
                            type: array
                            items:
                              type: string
                          days:
                            type: integer
                          usage:
                            type: string
                          candidateCount:
                            type: integer
                            description: Number of plans that passed the coverage/tier/validity filter.
                  meta:
                    $ref: "#/components/schemas/Meta"
        "400":
          description: Validation error (invalid countries, days out of range, unknown usage tier).
        "429":
          description: Rate limit exceeded (30 req/min per IP).

  /orders/data-only/calculate:
    post:
      operationId: calculatePrice
      summary: Calculate order price with optional discount codes
      description: |
        Use this to show the user a price breakdown before creating a checkout.
        Supports quantity and custom days for daily plans.
        Batch mode: send packageIds (array, max 50) instead of packageId to
        get pricing for multiple plans in one call. Response wraps per-plan
        results in a packages[] array preserving input order.
      tags: [Order]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                packageId:
                  type: string
                  description: Package ID from listPackages (single mode).
                packageIds:
                  type: array
                  items:
                    type: string
                  maxItems: 50
                  description: Package IDs for batch pricing (mutually exclusive with packageId).
                quantity:
                  type: integer
                  default: 1
                customDays:
                  type: integer
                  description: Custom validity days (for daily plans only).
                discountCodes:
                  type: array
                  items:
                    type: string
                  description: Discount/coupon codes to apply.
      responses:
        "200":
          description: Price calculation result.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      package:
                        type: object
                        properties:
                          id:
                            type: string
                          name:
                            type: string
                          destination:
                            type: object
                            properties:
                              id:
                                type: string
                              name:
                                type: string
                              region:
                                type: string
                          data:
                            type: object
                            properties:
                              amount:
                                type: number
                              unit:
                                type: string
                              type:
                                type: string
                          validity:
                            type: object
                            properties:
                              days:
                                type: integer
                          quantity:
                            type: integer
                          unitPrice:
                            type: number
                          subtotal:
                            type: number
                      discounts:
                        type: object
                        properties:
                          applied:
                            type: array
                            items:
                              type: object
                              properties:
                                code:
                                  type: string
                                displayName:
                                  type: string
                                discountAmount:
                                  type: number
                          rejected:
                            type: array
                            items:
                              type: object
                              properties:
                                code:
                                  type: string
                                message:
                                  type: string
                          total:
                            type: number
                      summary:
                        type: object
                        properties:
                          subtotal:
                            type: number
                          discountAmount:
                            type: number
                          total:
                            type: number
                          currency:
                            type: string
                  meta:
                    $ref: "#/components/schemas/Meta"

  /ai/checkout:
    post:
      operationId: createCheckout
      summary: Create a checkout link for the user to pay
      description: |
        Creates an order and returns a checkout URL. Share this URL with the user.
        The user opens the link, reviews the order, selects a payment method, and pays.
        The eSIM QR code is delivered to the provided email after payment.

        Rate limit: 10 requests per 5 minutes per IP.
        Orders expire after 30 minutes if not paid.
      tags: [Checkout]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [packageId, email]
              properties:
                packageId:
                  type: string
                  description: Package ID or slug from listPackages.
                email:
                  type: string
                  format: email
                  description: Email address for eSIM QR code delivery.
                quantity:
                  type: integer
                  default: 1
                  maximum: 10
                customDays:
                  type: integer
                  description: Custom validity days for daily plans.
                discountCode:
                  type: string
                  description: Optional discount code.
                locale:
                  type: string
                  enum: [en, zh-CN, zh-TW, ja, ko, fr]
                  default: en
                  description: Language for checkout page and emails.
      responses:
        "201":
          description: Checkout created. Present checkoutUrl to the user.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      checkoutUrl:
                        type: string
                        format: uri
                        description: URL for the user to complete payment.
                      checkoutToken:
                        type: string
                        description: Token to poll status via getCheckoutStatus.
                      expiresAt:
                        type: string
                        format: date-time
                      expiresInMinutes:
                        type: integer
                        description: Minutes until the checkout link expires.
                      summary:
                        type: object
                        properties:
                          orderId:
                            type: string
                          orderNumber:
                            type: string
                          destination:
                            type: string
                          destinationCode:
                            type: string
                          plan:
                            type: string
                            description: "Data volume display (e.g. '5GB')."
                          duration:
                            type: string
                            description: "Validity period (e.g. '7 days')."
                          networkType:
                            type: string
                          billingStart:
                            type: string
                            enum: [first_connection, first_install, purchase]
                            description: |
                              When billing begins. "purchase" means the plan starts immediately
                              and is non-refundable. Check refundPolicy.eligible.
                          quantity:
                            type: integer
                          unitPrice:
                            type: string
                            description: "Formatted price (e.g. '$3.50')."
                          subtotal:
                            type: string
                          discount:
                            type: string
                            description: "Formatted discount (e.g. '-$1.00'). Only present when discount applied."
                          total:
                            type: string
                          currency:
                            type: string
                          email:
                            type: string
                      installValidity:
                        type: object
                        description: |
                          IMPORTANT: relay `note` to the user before checkout. The eSIM must be
                          installed within `days` days of purchase (coupons can shorten this);
                          uninstalled eSIMs are automatically cancelled after the deadline, which
                          also ends refund eligibility.
                        properties:
                          days:
                            type: integer
                            nullable: true
                            description: Days from payment within which the eSIM must be installed.
                          installBy:
                            type: string
                            nullable: true
                            description: Absolute deadline (YYYY-MM-DD). Estimate before payment; exact after.
                          installByIsEstimate:
                            type: boolean
                            description: true before payment (assumes purchase today), false once paid.
                          note:
                            type: string
                            description: Agent-facing sentence to relay to the user verbatim.
                      refundPolicy:
                        type: object
                        description: |
                          IMPORTANT: AI agents MUST present this refund policy to the user
                          before they click the checkout link. The mustInformUser flag is always true.
                          When eligible is false, the order is non-refundable (billing starts at
                          purchase, or the item is free).
                        properties:
                          eligible:
                            type: boolean
                            description: |
                              Whether this order is eligible for refund. false for immediate-billing
                              plans (billingStart: "purchase") and free (100%-discounted) items.
                              Always check this field.
                          reasonCode:
                            type: string
                            enum: [billed_at_purchase, free_item, refundable]
                            description: Machine-readable reason behind `eligible`.
                          reason:
                            type: string
                            description: Human-readable explanation of the refund policy for this order.
                          windowDays:
                            type: integer
                            nullable: true
                            description: |
                              Effective refund window in days from payment — already capped by the
                              install deadline (min of the 30-day guarantee and installValidity.days).
                          refundableUntil:
                            type: string
                            nullable: true
                            description: Absolute refund deadline (YYYY-MM-DD). Estimate before payment; exact after.
                          refundableUntilIsEstimate:
                            type: boolean
                          guestOrderNote:
                            type: string
                            description: |
                              How AI/guest orders request a refund. Primary path: the user signs in
                              (or creates an account) at cosim.io with the same email this order was
                              placed with — past guest orders are automatically claimed on first
                              sign-in, after which the user can refund eligible eSIMs from "My eSIMs"
                              without contacting support. Live chat at cosim.io remains a fallback
                              for users who cannot or do not want to sign in.
                          processingTime:
                            type: string
                          mustInformUser:
                            type: boolean
                            description: Always true. Agents must share this info before checkout.
                  meta:
                    $ref: "#/components/schemas/Meta"
        "400":
          description: Validation error.
        "404":
          description: Package not found.
        "429":
          description: Rate limit exceeded.

  /ai/checkout/{token}/status:
    get:
      operationId: getCheckoutStatus
      summary: Check if the user has completed payment
      description: |
        Poll this endpoint after sharing the checkout link.
        - `awaiting_payment` — waiting for user to open the link and pay
        - `paid` — payment received, eSIM being provisioned
        - `completed` — eSIM delivered to email
        - `expired` — link expired, create a new checkout
        - `cancelled` — order was cancelled
        - `failed` — payment or provisioning failed; user can retry
        - `needs_review` — payment was taken but auto-refund failed during a coupon-abuse check; ops will resolve manually within one business day. Stop polling and direct the user to contact support@cosim.io with the orderNumber.
      tags: [Checkout]
      parameters:
        - name: token
          in: path
          required: true
          description: The checkoutToken from createCheckout.
          schema:
            type: string
      responses:
        "200":
          description: Current checkout status.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      orderNumber:
                        type: string
                      status:
                        type: string
                        enum: [awaiting_payment, paid, completed, expired, cancelled, failed, needs_review]
                      total:
                        type: string
                        description: "Formatted total (e.g. '$9.99')."
                      currency:
                        type: string
                        nullable: true
                      email:
                        type: string
                        nullable: true
                      items:
                        type: array
                        items:
                          type: object
                          properties:
                            name:
                              type: string
                            description:
                              type: string
                              nullable: true
                            quantity:
                              type: integer
                            unitPrice:
                              type: string
                            total:
                              type: string
                      paidAt:
                        type: string
                        format: date-time
                        nullable: true
                      createdAt:
                        type: string
                        format: date-time
                        nullable: true
                      expiresAt:
                        type: string
                        format: date-time
                        nullable: true
                      installValidity:
                        type: object
                        description: |
                          Install-deadline info. After payment `installBy` is the exact date
                          (from the fulfilled eSIM profile) — relay it to the user, e.g.
                          "install by 2026-08-01 or the eSIM is cancelled".
                        properties:
                          days:
                            type: integer
                            nullable: true
                          installBy:
                            type: string
                            nullable: true
                          installByIsEstimate:
                            type: boolean
                          note:
                            type: string
                      refundPolicy:
                        type: object
                        description: |
                          Same shape as createCheckout's refundPolicy. After payment
                          `refundableUntil` is exact (min of 30-day guarantee and install deadline).
                  meta:
                    $ref: "#/components/schemas/Meta"
        "404":
          description: Checkout token not found.

  /ai/checkout/{token}/pay:
    post:
      operationId: payCheckout
      summary: Initiate payment for an AI checkout order
      description: |
        Called from the AI checkout page when the user clicks "Pay".
        This endpoint is NOT typically called by AI agents directly — the user
        interacts with the checkout page UI instead.
        Supports multiple payment methods: card, alipay, wechat, amazon_pay.
      tags: [Checkout]
      parameters:
        - name: token
          in: path
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                locale:
                  type: string
                  enum: [en, zh-CN, zh-TW, ja, ko, fr]
                  default: en
                paymentMethod:
                  type: string
                  enum: [card, alipay, wechat, amazon_pay]
                  default: card
                  description: |
                    Payment method. card uses Stripe Checkout (includes Apple Pay/Google Pay).
                    alipay and amazon_pay redirect to the provider.
                    wechat returns a QR code for inline display.
      responses:
        "200":
          description: Payment session created.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      paymentType:
                        type: string
                        enum: [redirect, qr_code]
                      checkoutUrl:
                        type: string
                        format: uri
                        description: Redirect URL (card, alipay, amazon_pay).
                      sessionId:
                        type: string
                        description: Stripe session ID (card only).
                      qrCodeUrl:
                        type: string
                        description: QR code image URL (wechat only).
                      qrCodeData:
                        type: string
                        description: Raw weixin:// payment URL (wechat only).
                      qrCodeExpiresAt:
                        type: integer
                        description: Unix timestamp when QR expires (wechat only).
                      orderId:
                        type: string
                        description: Order ID (wechat only, for polling).
        "400":
          description: Invalid request or payment creation failed.
        "404":
          description: Order not found.
        "410":
          description: Order expired.
        "429":
          description: Rate limit exceeded.

components:
  schemas:
    Meta:
      type: object
      properties:
        requestId:
          type: string
        timestamp:
          type: string
          format: date-time

    PackageSummary:
      type: object
      properties:
        id:
          type: string
          description: Package ID — use this in calculatePrice and createCheckout.
        name:
          type: string
        slug:
          type: string
        dataVolume:
          type: object
          properties:
            mb:
              type: integer
            display:
              type: string
              description: "Human-readable (e.g. '1GB', '500MB/day')."
            type:
              type: string
              enum: [total, daily_throttle, daily_cutoff, daily_unlimited]
              description: "total = fixed pool; daily_* = resets each day."
        duration:
          type: object
          properties:
            days:
              type: integer
            display:
              type: string
        price:
          type: object
          properties:
            amount:
              type: number
            currency:
              type: string
        networkType:
          type: string
        speed:
          type: string
          description: "Network speed tier (e.g. '4G', '3G/4G/5G')."
        supportTopup:
          type: boolean
        billingStart:
          type: string
          enum: [first_connection, first_install]
          description: When the validity timer starts.
        isFavorite:
          type: boolean
          description: Recommended/popular plan.
        preInstallDays:
          type: integer
          description: >
            Number of days from purchase within which the eSIM must be installed
            (QR code scanned). If this deadline passes without installation the eSIM
            expires permanently. Typical values range from 30 to 180 days depending
            on the plan. Always surface this to users at checkout.
