Skip to main content

AI / LLM Integration

Most partners now build their integration with an AI coding assistant. The prompts on this page give the assistant everything it needs — authentication, every endpoint's exact shape, the error codes — and the architecture we recommend, so the generated code is fast for your customers and stays well inside the rate limits.

eSIMfly is the source of truth for provisioning; your database is the source of truth for what you show. Sync the package catalogue into your own database on a schedule and serve your storefront from it — never call /esims/packages per customer request. Place orders with an idempotency_key, persist the whole response, and render QR codes from lpaString. The rare pending order is completed with bounded polling. Subscribe to webhooks for install, status and low-data events so you rarely need to poll usage at all. Diagnostics (live status, network events, usage reports) are support tools only.

EndpointHow it should be usedTypical requests
GET /esims/packagesScheduled sync into your DB (every 6–12 h, limit=100, 1 req/s). Never delete rows — mark inactive~70 per sync
POST /esims/orderInside the customer purchase, with idempotency_key1 per sale
GET /esims/orderOnly for pending_details orders, 15–30 s, max ~10 minrare
GET /balanceCheckout (60 s cache) + hourly low-balance alert; mirror newBalance from orders≤ 50 / day
GET /topup/packagesWhen the customer opens the top-up screen; cache 10 min per ICCID1 per screen
POST /topup/orderInside the customer top-up, with a per-ICCID lock1 per top-up
GET /esims/usage/queryWhen a customer opens one eSIM; cache 5–15 min1 per view
GET /esimsSupport search + optional nightly reconciliation of ACTIVE eSIMs≤ 50 / night
GET /ordersDaily incremental finance reconciliation (from_date)~5 / day
POST /esims/status, /network-events, /usage-reportSupport console buttons, throttledon demand
POST /esims/suspend, /cancel, /send-smsExplicit operator / customer actionson demand
PUT /webhooksOnce. Subscribe to esim.installed, esim.status.changed, esim.usage.threshold; verify signature, dedupe, answer 2xx fastonce

Rate limits are per API key and shown in the dashboard (typically 100 requests/minute, 1,000/hour, 10,000/day). The design above keeps a typical reseller at a few hundred requests per day.

Complete prompt (all endpoints)

Use this when you are building the whole integration. It is self-contained — the assistant does not need to read the rest of these docs.

AI prompt — complete integration

Open raw .txt

Paste into ChatGPT, Claude, Cursor, Copilot or any coding agent to generate this part of your integration. Built-in recommendation: Catalogue synced to your DB · orders with idempotency keys · webhooks for install / status / low-data events · on-demand usage lookups.

Show prompt text (292 lines)
# eSIMfly Business API v1 — complete integration prompt for AI coding assistants

You are implementing an integration with the eSIMfly Business API (v1) on behalf of a
reseller. Follow these instructions exactly. They already contain the recommended
architecture; do not "simplify" it by calling the API on every customer request.

Source docs: https://docs.esimfly.net  (this file: https://docs.esimfly.net/llm/esimfly-api-full-prompt.txt)

====================================================================
1. BASICS THAT APPLY TO EVERY REQUEST
====================================================================

Base URL: https://esimfly.net/api/v1/business

Authentication (HMAC-SHA256, required on every call):
  Headers:
    RT-AccessCode: <access code, starts with esf_>
    RT-RequestID:  <fresh UUID v4 for EVERY request — reuse is rejected with 400 DUPLICATE_REQUEST>
    RT-Timestamp:  <milliseconds since Unix epoch — older than 5 minutes is rejected with 401 INVALID_TIMESTAMP>
    RT-Signature:  <UPPERCASE hex of HMAC-SHA256(secretKey, timestamp + requestId + accessCode + rawBody)>
    Content-Type:  application/json   (POST/PUT only)
  rawBody is "" for GET. For POST/PUT it is the EXACT byte string sent as the body
  (serialize once, sign that string, send that same string — do not re-serialize).
  Credentials (access code + secret key sk_...) live server-side only, in env vars.

Response envelope:
  Read endpoints: { "success": true, "data": {...} }
  Order endpoints: { "success": true, ...fields at top level }
  Errors:         { "success": false, "error"|"message": "...", "code": "SOME_CODE" }
  Always branch on `success` and `code`, never on the human-readable text.

Rate limits are per API key and shown in the business dashboard (typically 100 requests/minute, 1,000/hour, 10,000/day).
  Exceeding any window returns RATE_LIMIT_EXCEEDED. Response headers:
  X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. Any bulk job must
  be paced at <= 1 request/second.

Currency: read the `currency` field from every response (USD, IQD, or EUR for
  enterprise accounts). Never hard-code it. IQD amounts are whole numbers.

Package codes are OPAQUE strings ("PHAJHEAYP", "1654977", "ent_1234567", "merhaba-7days-1gb").
  Never parse, prefix, trim or reformat them. Store and send back verbatim.

Units: timestamps ISO 8601 UTC; package data in GB (data_amount_gb); eSIM usage in MB
  (total_mb/used_mb); order responses in bytes (total_volume, 1073741824 = 1 GB).

Error codes you must handle:
  INVALID_API_KEY, INVALID_SIGNATURE, INVALID_TIMESTAMP, INVALID_REQUEST_ID,
  DUPLICATE_REQUEST, INVALID_USER, RATE_LIMIT_EXCEEDED, INSUFFICIENT_BALANCE,
  INVALID_PACKAGE, PACKAGE_NOT_FOUND, PRICE_MISMATCH, ORDER_NOT_FOUND, MISSING_FIELDS,
  ESIM_NOT_FOUND, ESIM_ACCESS_DENIED, ESIM_NOT_TOPPABLE, INVALID_TOPUP_PACKAGE,
  TOPUP_NOT_SUPPORTED, NOT_ELIGIBLE, ALREADY_CANCELLED, UNSUPPORTED_PROVIDER,
  NOT_SUPPORTED, SMS_NOT_SUPPORTED, FORBIDDEN, PROVIDER_ERROR.

====================================================================
2. REQUIRED ARCHITECTURE — keep API calls out of the customer path
====================================================================

Principle: eSIMfly is the source of truth for PROVISIONING; YOUR database is the source
of truth for what you SHOW. The only eSIMfly calls allowed inside a customer-facing
request are: Create Order, Topup Packages (on demand), Topup Order, and a single-eSIM
usage lookup (on demand, cached). Everything else is a scheduled job or a support tool.

A) CATALOGUE — sync GET /esims/packages into your own database. Do NOT proxy it.
   - Job runs every 6–12 hours (plus an admin "sync now" button).
   - Loop: page=1,2,... with limit=100 until page >= data.pagination.total_pages.
     ~7,000 packages => ~70 requests per sync. Sleep ~1 s between pages.
   - Upsert by package_code. Store: name, region, type (local|regional|global|enterprise),
     data_amount_gb, validity_days, cost, currency, is_unlimited, has_voice, has_sms,
     features (JSON), countries (JSON array of ISO codes, may be absent), locationNetworkList
     (JSON), networks (JSON), provider extras if present (phone_number, network_carrier,
     has_5g, has_hotspot, can_extend, is_recurring, activation_policy, data_breakdown,
     voice_details), plus your own columns: last_seen_at, is_active, sell_price.
   - After a full successful sync, set is_active=false for rows whose last_seen_at is older
     than this run. NEVER delete rows — orders and eSIMs reference them.
   - If any page fails, abort the deactivation step (keep the previous catalogue live).
   - Serve storefront listing, search, country filters and price display from YOUR DB.
     `cost` is your buy price; apply your margin in your DB, not at request time.
   - Do not use the `search` parameter for the sync; it is for ad-hoc lookups only.
   - Pricing note: cost can change between syncs. The order endpoint charges the CURRENT
     price and returns it (`final_price`, `amount`); you only send packageCode, so there is
     no price-mismatch risk. Refresh your margin table after each sync.

B) ORDERS — POST /esims/order is the only way to buy.
   - Body: { "packageCode": "<from your DB>", "quantity": 1-10, "idempotency_key": "<your unique order id>" }
   - Always send `idempotency_key` (your internal order/cart id, <=200 chars). A retry with
     the same key returns the ORIGINAL order (HTTP 200 with "duplicate": true) instead of
     charging again. Two concurrent requests with the same key => 409 DUPLICATE_REQUEST.
     If the first attempt FAILED, the same key may be reused.
   - Timeout handling: on a network timeout, retry ONCE with the same idempotency_key and a
     NEW RT-RequestID. Never retry without the key.
   - Persist the whole response: orderReference, esimId, packageName, amount, final_price,
     profit, newBalance, currency, status, and for each esims[] item: iccid, lpaString,
     directAppleInstallUrl, directAndroidInstallUrl (when present), status, imsi, msisdn,
     total_volume, total_duration, expired_time, isPending.
   - Store lpaString and render the QR code yourself (any QR library) — do not store the
     base64 `qrCodeUrl` image (large, redundant).
   - status "pending_details" (rare; a few packages are provisioned asynchronously and take a
     few minutes): esims[] is empty. Poll GET /esims/order?orderReference=… every 15–30 s for
     at most ~10 minutes; ready when order.esim.isPending === false and order.esim.iccid is
     set. If it is still pending after that, mark the order for manual follow-up.
   - Check balance BEFORE the order only if you display it; the order itself returns
     INSUFFICIENT_BALANCE with currentBalance/requiredBalance/needToLoad if funds are short.

C) eSIM STATE — keep your own `esims` table populated from order responses and WEBHOOKS.
   - Never list GET /esims to find eSIMs you sold; you already have them.
   - Subscribe to webhooks (section G) so installs, status changes and low-data thresholds
     arrive in real time; then usage lookups are only needed when a customer opens the eSIM.
   - Customer opens "my eSIM": GET /esims/usage/query?iccid=… (one call, cache 5–15 min).
   - Support lookup by ICCID: GET /esims?search=<iccid>&limit=1.
   - Optional nightly reconciliation of ACTIVE eSIMs: GET /esims?status=ACTIVE&limit=100,
     paginated, paced at 1 req/s — only if you need fleet-wide usage in your DB.
   - Do NOT run per-eSIM usage/status loops in a cron; that burns the daily quota.

D) BALANCE — maintain a local mirror.
   - Update the mirror from `newBalance` in every order/top-up response.
   - GET /balance: at checkout (cache 60 s) and in an hourly low-balance alert job.
   - Enterprise accounts: data.source === "enterprise", currency EUR, balance held on the
     network; top-ups happen in the enterprise portal, not via API.

E) TOP-UPS — only on customer action.
   - GET /topup/packages?iccid=<iccid>&limit=100 when the customer opens the top-up screen
     (packages are eSIM-specific; cache per ICCID for ~10 min).
   - POST /topup/order { "iccid", "packageCode" } — then update your esims row from
     esimData (newTotalVolumeGB, newRemainingVolumeGB, expiredTime) and balance from newBalance.
   - Eligible statuses: ACTIVE, DEPLETED, USED_EXPIRED (and NEW for eSIMfly-provided eSIMs).
     Otherwise ESIM_NOT_TOPPABLE.
   - No idempotency key on top-ups: on a timeout do NOT blindly retry — first call
     GET /esims/usage/query?iccid=… and compare total_mb with your stored value.

F) DIAGNOSTICS — support tools only, never in crons, never on page load.
   - POST /esims/status         (live status from the network, incl. last network + device)
   - POST /esims/network-events (last 7 days of attach/data-session events, wrong-network flag)
   - POST /esims/usage-report   (daily usage, by country/operator, up to 90 days; cache 1 h)
   - POST /esims/suspend { action: "suspend"|"activate" }  (eSIMfly eSIMs only)
   - POST /esims/cancel  (only before activation; refunds to balance; idempotent-safe)
   - POST /esims/send-sms { message <=500 chars }

G) WEBHOOKS — configure once, verify always.
   - PUT /webhooks { "webhook_url": "https://…", "events": ["esim.installed", "esim.status.changed", "esim.usage.threshold"] }
     Save the returned `secret` (whsec_…) — shown once. GET /webhooks lists config + last deliveries.
   - Events: esim.installed (profile enabled on a phone, seconds), esim.profile.updated (every SM-DP+
     state change — chatty, usually skip), esim.usage.threshold (500/200/100/50 MB remaining, seconds,
     not unlimited plans), esim.status.changed (NEW→ACTIVE→DEPLETED/EXPIRED, ≤30 min, all providers),
     esim.provisioned (async packages). Real-time events cover eSIMfly-network eSIMs (packages that
     expose `countries`/`networks`).
   - Verify: expected = "sha256=" + hex(HMAC-SHA256(secret, rawBody)); constant-time compare with
     X-Webhook-Signature. Reject on mismatch (401). Dedupe on X-Webhook-Id. Respond 2xx within 10 s;
     process asynchronously. Retries: 10 s, 30 s, 2 min, 10 min, then marked failed.
   - Payload: { event, timestamp, data: { iccid, esim_id, package_code, order_reference, source, …event fields } }
     Apply each event to your own esims row (installed_at, status/expiry, used/remaining GB).

H) ORDER HISTORY / RECONCILIATION — GET /orders
   - For finance reconciliation, run daily with from_date = last run, limit=100, paginated.
   - Never poll /orders to learn whether an order succeeded; the order response tells you.

Request budget with this design (per day, typical reseller): catalogue sync 70–140,
orders = number of sales, top-ups = number of top-ups, usage lookups = customer views
(cached), balance <= 24+checkouts. Comfortably inside 10,000/day and 100/minute.

====================================================================
3. ENDPOINT REFERENCE (compact but complete)
====================================================================

3.1 GET /balance
  -> { success, data: { balance: number, currency: "USD"|"IQD"|"EUR",
       // enterprise only:
       source: "enterprise", account: string, live: boolean, as_of: ISO } }

3.2 GET /esims/packages?page=1&limit=100[&type=local|regional|global][&search=Turkey]
  -> { success, data: { packages: Package[], pagination: { page, limit, total, total_pages } } }
  Package: { package_code, name, region, type, data_amount_gb, validity_days, cost, currency,
    features: { voice_minutes, sms_count, is_rechargeable }, is_unlimited, has_voice, has_sms,
    countries?: ["US",...], locationNetworkList: [{ locationName, locationLogo,
    operatorList: [{ operatorName, networkType }] }], networks?: [{ network_id, network_name,
    network_type, country_code, country_name, country_iso2, continent, mcc_code, mnc_code }],
    // O2 / Vodafone / Bouygues extras (optional):
    phone_number, network_carrier, has_5g, has_hotspot, can_extend, is_recurring,
    activation_policy: "immediate"|"first_use", data_breakdown, voice_details }
  Notes: only >=1 GB packages are returned; sorted local -> regional -> global; enterprise
  self-service accounts get type "enterprise" and ent_ codes.

3.3 POST /esims/order
  Body: { packageCode: string, quantity?: 1..10, idempotency_key?: string,
          recurring?: boolean (O2/Vodafone subscription packages only) }
  -> { success, message, orderReference, esimId, packageName, newBalance, currency,
       lpaString, qrCodeUrl (data:image/png;base64,...), directAppleInstallUrl,
       paymentMethod: "balance", status: "completed"|"pending_details", amount, profit,
       final_price, processing_time_ms, isPending?, duplicate?,
       esims: [{ iccid, lpaString, qrCodeUrl, directAppleInstallUrl, directAndroidInstallUrl?,
                 status: "New"|"PENDING", imsi, msisdn, sim_status, esim_status, profile_status,
                 unlimited, total_volume (bytes), total_duration (days), expired_time, isPending }] }
  Errors: 400 MISSING_PACKAGE_CODE | INVALID_PACKAGE | INSUFFICIENT_BALANCE
          { currentBalance, requiredBalance, needToLoad } | 404 PACKAGE_NOT_FOUND (enterprise)
          | 409 DUPLICATE_REQUEST (concurrent duplicate) | 500 ORDER_PROCESSING_ERROR.

3.4 GET /esims/order?orderReference=<ref>
  -> { success, order: { id, packageName, packageCode, status, amount, finalPrice, orderDate,
       orderReference, paymentMethod, paymentStatus, esim: { iccid|null, status, imsi, msisdn,
       sim_status, esim_status, profile_status, qrCodeUrl, directAppleInstallUrl,
       directAndroidInstallUrl, lpaString, isPending, unlimited, totalVolume, totalDuration,
       expiredTime } } }

3.5 GET /topup/packages?iccid=<iccid>[&page][&limit<=100]
  -> { success, data: { packages: [{ package_code, name, data_amount_gb, validity_days, cost,
       currency, features: { is_rechargeable: true }, is_unlimited }], pagination } }
  Errors: 400 MISSING_ICCID | 400 ESIM_NOT_TOPPABLE | 403 ESIM_ACCESS_DENIED.

3.6 POST /topup/order   Body: { iccid, packageCode, quantity? }
  -> { success, message, orderReference, iccid, packageName, newBalance, currency,
       status: "completed", amount, profit, processing_time_ms,
       esimData: { newTotalVolumeGB, newRemainingVolumeGB, expiredTime } }
  Errors: MISSING_FIELDS | ESIM_NOT_FOUND | ESIM_NOT_TOPPABLE | INSUFFICIENT_BALANCE |
          INVALID_TOPUP_PACKAGE | TOPUP_NOT_SUPPORTED.

3.7 GET /esims?page&limit<=100&status=all|NEW|ACTIVE|EXPIRED|CANCELLED|DEPLETED|DELETED&search=&include_base64=false
  -> { success, data: { esims: [{ id, iccid, package_name, package_code, countries: [names],
       status, data: { total_mb, used_mb, remaining_mb, usage_percentage, is_unlimited },
       validity: { days, activated_at, expires_at, is_expired }, qr_code (URL),
       manual_installation: { smdp_address, activation_code, lpa_format },
       direct_apple_installation_url, direct_android_installation_url, flag_url, created_at,
       is_pending, phone_number, imsi, sim_status, esim_status, profile_status }], pagination } }
  Unlimited plans: total_mb = 0, remaining_mb = 0, is_unlimited = true.

3.8 GET /esims/usage/query?iccid=<iccid>   (or ?order_id=<orderReference>)
  -> { success, data: { esim: { iccid, order_id, package_name, status },
       data: { total_mb, used_mb, remaining_mb, usage_percentage, is_unlimited },
       validity: { days, activated_at, expires_at, is_expired } } }
  404 when not found / not yours.

3.9 POST /esims/status   Body: { iccid } | { esimId }   (LIVE from the network)
  -> { success, data: { iccid, status, esim_status, smdp_status, profile, unlimited,
       last_network: { operator, mcc, mnc, country, country_iso2, connection_type },
       device: { model, imei }, activation_date, last_usage_date, expiry_date,
       data_usage: { used_gb, total_gb|null, unlimited } } }
  Errors: MISSING_IDENTIFIER | FORBIDDEN | ESIM_NOT_FOUND | 502 PROVIDER_ERROR.

3.10 POST /esims/network-events   Body: { iccid } | { esimId }
  -> { success, data: { iccid, total_events, wrong_network_count,
       events: [{ time, event_type: "attach"|"data_session"|"location_update", req_type,
       operator, mcc, mnc, country, country_iso2, msisdn, apn, connection_type,
       data_response, is_allowed }] } }   (last 7 days, newest first)

3.11 POST /esims/usage-report   Body: { iccid|esimId, days?: 7 (max 90) }
  -> { success, data: { iccid, period_days, start_date, end_date,
       summary: { total_data_mb, total_data_gb, avg_daily_mb, avg_daily_gb },
       daily_usage: [{ date, data_mb, data_gb }],
       by_country: [{ country, mcc, data_mb, data_gb, operators: [{ operator, mnc, data_mb, data_gb }] }] } }

3.12 POST /esims/suspend   Body: { iccid|esimId, action: "suspend"|"activate" }   (eSIMfly eSIMs only)
  -> { success, message, data: { iccid, action, esim_status: "Disconnected"|"Active" } }
  Errors: INVALID_ACTION | MISSING_IDENTIFIER | UNSUPPORTED_PROVIDER | ESIM_NOT_FOUND.

3.13 POST /esims/cancel   Body: { iccid|esimId }   (before activation only; whole order)
  -> { success, message, data: { order_reference, total_esims, cancelled_esims, failed_esims,
       refunded_amount, currency, refund_method: "balance"|"refund_request"|"separate",
       balance_credited, partial_cancellation, cancel_results: [{ esimId, iccid, success, refundAmount }] } }
  Errors: MISSING_IDENTIFIER | ALREADY_CANCELLED | NOT_ELIGIBLE { details.ineligibleEsims } | FORBIDDEN | ESIM_NOT_FOUND.

3.14 POST /esims/send-sms   Body: { iccid|esimId, message (<=500 chars) }
  -> { success, message }   Errors: MISSING_MESSAGE | MESSAGE_TOO_LONG | SMS_NOT_SUPPORTED | SMS_FAILED.

3.15 Webhooks
  PUT /webhooks { webhook_url, events[] } -> { success, webhook: { url, secret, events } }
  GET /webhooks -> { success, webhook: { url, events, api_key_name }, available_events[], recent_deliveries[] }
  Delivery headers: X-Webhook-Event, X-Webhook-Signature ("sha256=<hex>"), X-Webhook-Timestamp, X-Webhook-Id.
  Event fields: esim.installed { eid, imsi, installed_at } · esim.profile.updated { profile_status, profile,
  previous_profile_status, eid, imsi, changed_at } · esim.status.changed { old_status, new_status, changed_at,
  expiry_date } · esim.usage.threshold { threshold_remaining_mb, used_percent, used_gb, total_gb, remaining_gb,
  package_id, reported_at } · esim.provisioned { iccid, qr_code_url, lpa_string, direct_apple_install_url,
  direct_android_install_url, package_name }

3.16 GET /orders?page&limit<=100&status=all|pending|completed|failed|cancelled&from_date&to_date&search&sort_by=created_at|amount|status&sort_order=asc|desc
  -> { success, data: { orders: [{ id, order_reference, package_name, package_code, amount,
       currency, status, flag_url, created_at, esim: { iccid, imsi, msisdn, sim_status,
       esim_status, profile_status, unlimited, total_volume, total_duration, expired_time } | null }],
       summary: { total_orders, total_revenue }, pagination } }

====================================================================
4. IMPLEMENTATION CHECKLIST (do all of these)
====================================================================
[ ] One HTTP client module: signs requests, injects headers, parses the envelope, maps
    `code` to typed errors, reads X-RateLimit-Remaining, retries ONLY idempotent GETs
    (max 2, exponential backoff) and order POSTs that carry idempotency_key.
[ ] Tables: packages (synced), orders, esims, webhook_deliveries (dedupe on X-Webhook-Id).
[ ] Scheduled jobs: package sync (every 6–12 h, paced 1 req/s, never deletes), low-balance
    alert (hourly), optional nightly reconciliation of ACTIVE eSIMs.
[ ] Customer flows call eSIMfly only for: order, top-up packages, top-up order, one-eSIM usage.
[ ] Webhook receiver: raw-body signature check + dedupe + 2xx within 10 s + async apply.
[ ] Pending-order handling (bounded polling, then manual follow-up).
[ ] Never log the secret key or webhook secret; never send them to a browser/mobile app.
[ ] Read `currency` from responses; format IQD as integers.

How to use the prompts

  1. Copy the prompt (button above, or fetch the raw file at /llm/esimfly-api-full-prompt.txt).

  2. Add your context below it: language/framework, database, how orders are created in your system, where credentials live. For example:

    Stack: Node.js 20 + NestJS + PostgreSQL (Prisma). Credentials in ESIMFLY_ACCESS_CODE and
    ESIMFLY_SECRET_KEY. Our orders table already exists (id uuid, status, customer_id).
    Start with the shared HTTP client and the package sync job.
  3. Ask for one deliverable at a time — the shared signed client first, then the catalogue sync, then ordering, then top-ups — and run the checklist at the end of the prompt.

  4. Give agents the raw URLs. Coding agents that can browse can read /llms.txt (an index of all prompts) or any /llm/<endpoint>.txt directly.

Per-endpoint prompts

Every endpoint page has its own prompt with the same rules, scoped to that endpoint. Use these when you only need one capability (for example adding top-ups to an existing integration). Do not paste all fifteen to build a full integration — use the complete prompt above instead.

EndpointPrompt
Balance Querypage · raw
Get All Packagespage · raw
Create Orderpage · raw
Get Topup Packagespage · raw
Process Topup Orderpage · raw
List All eSIMspage · raw
Query eSIM Usagepage · raw
eSIM Status (live)page · raw
Network Eventspage · raw
Usage Reportpage · raw
Suspend / Activate eSIMpage · raw
Cancel eSIMpage · raw
Send SMSpage · raw
Orderspage · raw
Webhookspage · raw

Why we ask you to sync the catalogue

GET /esims/packages assembles and prices the complete multi-provider catalogue (7,000+ packages) on every call and paginates it in memory. Calling it per customer search or page view is slow for your users, quickly exhausts your hourly quota, and is expensive for everyone. A scheduled sync into your own database is roughly 70 requests every 6–12 hours, gives your storefront millisecond responses, lets you add your own margin and search index, and keeps order history intact because you mark packages inactive instead of deleting them. The order endpoint charges the current price and only needs packageCode, so a slightly stale catalogue never causes a wrong charge.

Keeping the prompts current

The prompts are generated from the same source as these docs and change when the API changes. Re-copy them when you add a capability, and check the changelog for breaking changes such as renamed fields.