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
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.
Authentication
This endpoint requires HMAC authentication. See Authentication for details.
Request Headers
| Header | Type | Required | Description |
|---|---|---|---|
| RT-AccessCode | String | Yes | Your API access code |
| RT-RequestID | String | Yes | Unique request ID (UUID v4) |
| RT-Timestamp | String | Yes | Request timestamp in milliseconds |
| RT-Signature | String | Yes | HMAC-SHA256 signature |
| Content-Type | String | Yes | Must be "application/json" |
Request Body
Identify the eSIM by iccid (recommended) or esimId.
| Field | Type | Required | Description |
|---|---|---|---|
| iccid | String | Yes* | The ICCID of the eSIM to cancel |
| esimId | Integer | Yes* | 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 when | Not eligible when |
|---|---|
| Newly provisioned — not yet downloaded, installed, used, or connected to a network | The 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
| Field | Type | Description |
|---|---|---|
| success | Boolean | Whether at least one eSIM was cancelled |
| message | String | Human-readable summary |
| data.order_reference | String | The order the eSIM belongs to |
| data.total_esims | Integer | Number of eSIMs in the order |
| data.cancelled_esims | Integer | Number successfully cancelled |
| data.failed_esims | Integer | Number that failed to cancel |
| data.refunded_amount | Number | Amount refunded to your balance (may be 0 for some packages whose refund is handled separately) |
| data.currency | String | Currency of the refund |
| data.refund_method | String | How the refund was processed: balance, refund_request, or separate |
| data.balance_credited | Boolean | Whether your account balance was credited |
| data.partial_cancellation | Boolean | True if only some eSIMs in the order were cancelled |
| data.cancel_results | Array | Per-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_amountis0andbalance_creditedisfalse. - Cancellation is idempotent-safe: a second attempt on an already-cancelled eSIM returns
ALREADY_CANCELLEDand does not double-refund.