Skip to main content

Cancel eSIM

Cancel an eSIM that has not yet been activated and refund the amount to your balance.

When an order contains multiple eSIMs (same orderReference), all eligible eSIMs in that order are cancelled together.

Endpoint

POST /api/v1/business/esims/cancel

AI prompt — Cancel eSIM

Open raw .txt

Paste into ChatGPT, Claude, Cursor, Copilot or any coding agent to generate this part of your integration. Built-in recommendation: Pre-check eligibility in your DB, warn that the whole order is cancelled, persist refund method and pending refunds.

Show prompt text (55 lines)
# eSIMfly Business API — Cancel eSIM (POST /esims/cancel) — prompt for AI coding assistants

TASK: implement cancellation + refund-to-balance for eSIMs that have NOT been activated / installed /
connected yet. Cancelling one eSIM cancels every eligible eSIM in the same order (same orderReference).
Operator or customer-initiated action only.

COMMON RULES (apply to every eSIMfly request)
- Base URL: https://esimfly.net/api/v1/business
- Headers on every call: RT-AccessCode (esf_...), RT-RequestID (fresh UUID v4 per request; reuse -> 400 DUPLICATE_REQUEST),
  RT-Timestamp (ms since epoch; >5 min old -> 401 INVALID_TIMESTAMP),
  RT-Signature = UPPERCASE hex HMAC-SHA256(secretKey, timestamp + requestId + accessCode + rawBody).
  rawBody = "" for GET; for POST/PUT sign the exact body string you send, with Content-Type: application/json.
- Keep access code + secret key server-side in env vars. Never ship them to a browser or mobile app.
- Responses: { success: true, ... } or { success: false, error|message, code }. Branch on `success` and `code`.
- Rate limits are per API key, shown in the business dashboard (typically 100/minute, 1,000/hour, 10,000/day) -> RATE_LIMIT_EXCEEDED. Pace bulk work at <= 1 req/s.
- Read `currency` from responses (USD | IQD | EUR for enterprise). Never hard-code it. IQD amounts are integers.
- Package codes are opaque strings: store and send back verbatim, never parse or prefix them.
- Node.js/TypeScript: use the official SDK instead of raw HTTP — `npm install @esimfly/sdk`
  (https://github.com/eSimfly-Official/esimfly-sdk-nodejs); it implements these rules. Other languages: implement the contract below.
- Full multi-endpoint prompt: https://docs.esimfly.net/llm/esimfly-api-full-prompt.txt

ENDPOINT
  POST https://esimfly.net/api/v1/business/esims/cancel
  Body: { "iccid": "8948010010036785060" }   or   { "esimId": 15757 }

ELIGIBILITY: only eSIMs never downloaded / installed / used / attached to a network. Anything with an
activation date is NOT eligible. Some packages process the refund immediately, others create a refund
request confirmed shortly after.

RESPONSE 200
  { success: true, message: "All 1 eSIM cancelled successfully.", data: {
      order_reference, total_esims, cancelled_esims, failed_esims,
      refunded_amount, currency, refund_method: "balance"|"refund_request"|"separate",
      balance_credited: boolean, partial_cancellation: boolean,
      cancel_results: [{ esimId, iccid, success, refundAmount }] } }
  refunded_amount may be 0 with balance_credited false when the refund is handled separately.

ERRORS: 400 MISSING_IDENTIFIER; 400 ALREADY_CANCELLED (safe: no double refund); 400 NOT_ELIGIBLE with
        details.ineligibleEsims[{ id, iccid, reason }]; 403 FORBIDDEN; 404 ESIM_NOT_FOUND.

INTEGRATION PATTERN
1. Before calling, check OUR esims row: if status is not NEW or we recorded an activation, show
   "not refundable" without calling the API. The API is the final judge — surface NOT_ELIGIBLE reasons.
2. Warn the operator that ALL eligible eSIMs in the order will be cancelled (partial_cancellation tells you
   whether some were skipped).
3. Persist the result: set our esims rows to CANCELLED, store refunded_amount / refund_method / currency on
   the order, update the balance mirror only when balance_credited === true; if refund_method is
   "refund_request"/"separate", mark the refund pending and reconcile later via GET /balance.
4. Cancellation is idempotent-safe: a repeat returns ALREADY_CANCELLED. Retry once on timeout with a new
   RT-RequestID.
5. Never cancel in bulk from a cron; each cancellation should be an explicit human or customer action.

DELIVERABLE: cancelEsim(iccid) in the shared client, pre-checks against our DB, and result persistence
including pending refunds.

Building the whole integration? Use the complete prompt for all endpoints instead of combining the per-endpoint ones.

Authentication

This endpoint requires HMAC authentication. See Authentication for details.

Request Headers

HeaderTypeRequiredDescription
RT-AccessCodeStringYesYour API access code
RT-RequestIDStringYesUnique request ID (UUID v4)
RT-TimestampStringYesRequest timestamp in milliseconds
RT-SignatureStringYesHMAC-SHA256 signature
Content-TypeStringYesMust be "application/json"

Request Body

Identify the eSIM by iccid (recommended) or esimId.

FieldTypeRequiredDescription
iccidStringYes*The ICCID of the eSIM to cancel
esimIdIntegerYes*The eSIM id (from the eSIM list/order response). Used if iccid is not provided.

* Provide either iccid or esimId.

{
"iccid": "8948010010036785060"
}

Cancellation Eligibility

An eSIM can only be cancelled (and refunded) before it has been activated or connected to a network.

Eligible whenNot eligible when
Newly provisioned — not yet downloaded, installed, used, or connected to a networkThe eSIM has been activated, installed, used, or has connected to any network

In all cases, an eSIM that has an activation date (QR code scanned and installed) cannot be cancelled. Depending on the package, a cancellation is either processed immediately or submitted as a refund request that is confirmed shortly afterwards.

Response

Success Response (200 OK)

{
"success": true,
"message": "All 1 eSIM cancelled successfully.",
"data": {
"order_reference": "order_1692123456_ab7cd",
"total_esims": 1,
"cancelled_esims": 1,
"failed_esims": 0,
"refunded_amount": 2.72,
"currency": "USD",
"refund_method": "balance",
"balance_credited": true,
"partial_cancellation": false,
"cancel_results": [
{
"esimId": 15757,
"iccid": "8948010010036785060",
"success": true,
"refundAmount": 2.72
}
]
}
}

Response Fields

FieldTypeDescription
successBooleanWhether at least one eSIM was cancelled
messageStringHuman-readable summary
data.order_referenceStringThe order the eSIM belongs to
data.total_esimsIntegerNumber of eSIMs in the order
data.cancelled_esimsIntegerNumber successfully cancelled
data.failed_esimsIntegerNumber that failed to cancel
data.refunded_amountNumberAmount refunded to your balance (may be 0 for some packages whose refund is handled separately)
data.currencyStringCurrency of the refund
data.refund_methodStringHow the refund was processed: balance, refund_request, or separate
data.balance_creditedBooleanWhether your account balance was credited
data.partial_cancellationBooleanTrue if only some eSIMs in the order were cancelled
data.cancel_resultsArrayPer-eSIM cancellation result

Error Responses

400 Bad Request

Missing identifier:

{ "success": false, "message": "Provide either iccid or esimId", "code": "MISSING_IDENTIFIER" }

Already cancelled:

{ "success": false, "message": "This eSIM has already been cancelled and cannot be cancelled again", "code": "ALREADY_CANCELLED" }

Not eligible (e.g. already activated):

{
"success": false,
"message": "No eSIMs are eligible for cancellation",
"code": "NOT_ELIGIBLE",
"details": {
"totalEsims": 1,
"ineligibleEsims": [
{
"id": 15757,
"iccid": "8948010010036785060",
"reason": "This eSIM has already been activated and cannot be cancelled or refunded"
}
]
}
}

403 Forbidden

eSIM does not belong to your account:

{ "success": false, "message": "You do not have permission to cancel this eSIM", "code": "FORBIDDEN" }

404 Not Found

{ "success": false, "message": "eSIM not found", "code": "ESIM_NOT_FOUND" }

Example

const crypto = require('crypto');
const { v4: uuidv4 } = require('uuid');

async function cancelEsim(iccid) {
const accessCode = 'esf_your_access_code';
const secretKey = 'sk_your_secret_key';

const body = JSON.stringify({ iccid });
const timestamp = Date.now().toString();
const requestId = uuidv4();
const signData = timestamp + requestId + accessCode + body;
const signature = crypto.createHmac('sha256', secretKey)
.update(signData)
.digest('hex')
.toUpperCase();

const response = await fetch('https://esimfly.net/api/v1/business/esims/cancel', {
method: 'POST',
headers: {
'RT-AccessCode': accessCode,
'RT-RequestID': requestId,
'RT-Timestamp': timestamp,
'RT-Signature': signature,
'Content-Type': 'application/json'
},
body
});

const data = await response.json();
if (data.success) {
console.log(`Cancelled ${data.data.cancelled_esims} eSIM(s), refunded ${data.data.refunded_amount} ${data.data.currency}`);
} else {
console.error(`Cancel failed: ${data.message}`);
}
return data;
}

Notes

  • Refunds are credited to your account balance (not the original payment method).
  • For some packages the refund is handled separately, so refunded_amount is 0 and balance_credited is false.
  • Cancellation is idempotent-safe: a second attempt on an already-cancelled eSIM returns ALREADY_CANCELLED and does not double-refund.