All v2 endpoints use Bearer token authentication and return a standard response envelope.
Header required in all requests:
Authorization: Bearer <your_token>
Only client-visible API changes published from this point forward appear here. Internal operational, supplier, deployment, and security implementation details are not published.
Candidate contract, available upon deployment: added opt-in EUR wallet-alert preferences, notification history, private Telegram linking and separate notification-email verification. Alerts are off by default. Fixed EUR 500 and EUR 200 thresholds are observed approximately every 60 seconds, without an instant-delivery guarantee or retroactive low-balance alerts. These informational operations do not change wallet balances or financial transactions.
Client action: Use your existing Bearer token for its own account only; child-account and administrator targeting are not supported. Read settings and channel-availability flags first. Verify a separate notification email or complete the private one-time Telegram Start link before selecting that channel, then PUT all four required preferences. Do not send query parameters, extra fields, manual chat IDs or Idempotency-Key. Codes are never returned, email destinations are masked, and uncertain delivery outcomes are identified in history.
Affected operations: GET /api/v2/wallet/alerts · PUT /api/v2/wallet/alerts · GET /api/v2/wallet/alerts/history · POST /api/v2/wallet/alerts/telegram-link · DELETE /api/v2/wallet/alerts/telegram-link · POST /api/v2/wallet/alerts/email-verification · POST /api/v2/wallet/alerts/email-verification/confirm · DELETE /api/v2/wallet/alerts/email
Added Movistar eSIM catalog, delivery, delivery-PDF, and resend-email operations. Availability is returned from central inventory, while each delivery charges the authenticated account wallet once at its current commercial price. Each delivery requires a deliveryEmail and an Idempotency-Key; a retry with the same key returns the original delivery without a duplicate charge or email.
Client action: Use your existing Bearer token. Read the catalog before delivery, send productId and deliveryEmail with a unique Idempotency-Key, and treat MOVISTAR_ESIM_STOCK_UNAVAILABLE as out of stock. Use the resend-email operation for an existing order instead of creating another delivery request.
Affected operations: GET /api/v2/movistar-esim/catalog · POST /api/v2/movistar-esim/activate · POST /api/v2/movistar-esim/orders/:orderId/resend-email · GET /api/v2/movistar-esim/orders/:orderId/pdf
All v2 endpoints wrap responses in a consistent envelope:
// Success (2xx)
{
"data": { },
"meta": { "requestId": "..." },
"message": "Additional message" // Optional
}
// Error (4xx / 5xx)
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable description",
"details": { }
},
"meta": { "requestId": "..." }
}
Candidate contract, available upon deployment. These optional informational alerts are off by default. Use your existing Authorization: Bearer $API_TOKEN for all eight operations below. The token can read and configure only its own account, including when its account is a subadmin. It cannot target child accounts, other accounts or an administrator's commercial scope. Alerts require an active client or subadmin account with a EUR wallet; platform administrator wallets are not eligible.
These endpoints do not charge, top up, reserve or change wallet balances, prices, credit, orders or financial operations. Wallet activity is monitored independently of whether you visit the website: eligible API-originated wallet changes are monitored too. Notification availability does not change the outcome of financial operations.
>= EUR 525 for the EUR 500 threshold and >= EUR 225 for the EUR 200 threshold. There is also a 24-hour cooldown per account, threshold and channel; rearming does not bypass it. A new notification may remain pending until the cooldown ends.GET /api/v2/wallet/alerts. Check globallyEnabled, mode, emailAvailable and telegramAvailable. Saving preferences alone does not guarantee delivery while the module or channel is unavailable.telegramAvailable: true, create a link, privately open the returned URL in Telegram and press Start. Read settings again until telegramLinked: true. Linking alone does not enable alerts.PUT /api/v2/wallet/alerts, sending all four fields and selecting at least one available, valid channel. Supported notification locales are en and es. Subsequent crossings are evaluated from the new baseline.enabled: false. To disconnect Telegram, DELETE its link. To remove a separate notification email and restore the valid account email where available, DELETE the email override. If you also want email alerts off, PUT emailEnabled: false; removing the override alone is not a global opt-out.Strict input and privacy: no endpoint accepts query parameters, including id_client, limit or a manual chat identifier. GET requests must have no body. Empty-body POST/DELETE operations accept no body or exactly {}. Other bodies accept only the documented keys, without extra fields. Idempotency-Key is not supported here and is rejected; do not copy the header from financial endpoints. Settings and history never return raw email destinations, Telegram chat IDs or verification codes. Treat the one-time Telegram link as private and do not log or share it.
Every successful operation returns HTTP 200 with {"data": ..., "meta": {"requestId": "..."}}. Errors use the error envelope below. All alert responses use Cache-Control: no-store. The following curl examples use a local $API_TOKEN variable; no real token or recipient is included. Set API_BASE to your API origin and supply your own token securely.
API_BASE="https://api.activationsim.es"
# API_TOKEN must already be supplied securely by your application.
curl --request GET "$API_BASE/api/v2/wallet/alerts" \
--header "Authorization: Bearer $API_TOKEN"
/api/v2/wallet/alertsReturns the authenticated account's settings. No query or body. The exact settings object is shown below; values are illustrative. The same settings shape is returned by PUT, both DELETE operations and successful email verification confirmation.
{
"data": {
"enabled": false,
"emailEnabled": false,
"telegramEnabled": false,
"locale": "es",
"telegramLinked": false,
"emailAvailable": true,
"emailMasked": "s***@e***",
"emailVerified": true,
"emailSource": "account",
"telegramAvailable": false,
"globallyEnabled": false,
"mode": "disabled",
"thresholds": [500, 200],
"pollIntervalSeconds": 60
},
"meta": { "requestId": "..." }
}
| Settings fields | Meaning |
|---|---|
enabled, emailEnabled, telegramEnabled, locale | Saved consent, channel selections and notification language. They default to false, false, false and es. |
emailAvailable, emailMasked, emailVerified, emailSource | Current usable email, shown only as a mask or null. Source is account or notification. emailVerified indicates a usable destination, including the valid account-email fallback; it does not mean this API verified the account email. |
telegramAvailable, telegramLinked | Channel availability and whether this account has a linked private chat. Consult availability at runtime; a returned example or saved preference does not promise the channel is enabled. |
globallyEnabled, mode | Overall availability and disabled, simulation or live mode. Simulation does not deliver a real message. |
thresholds, pollIntervalSeconds | Read-only constants [500, 200] in euros and 60 seconds. |
/api/v2/wallet/alertsReplaces the four writable preferences. All four fields are required, even when disabling. Booleans must be JSON booleans; locale must be en or es. If enabled is true, at least one valid channel must be selected. Email selection requires a valid usable email; Telegram selection requires a linked private account and an available channel. Unknown fields, recipients, thresholds and partial bodies are rejected. Returns the settings object above.
curl --request PUT "$API_BASE/api/v2/wallet/alerts" \
--header "Authorization: Bearer $API_TOKEN" \
--header "Content-Type: application/json" \
--data '{"enabled":true,"emailEnabled":true,"telegramEnabled":false,"locale":"en"}'
# Full opt-out:
curl --request PUT "$API_BASE/api/v2/wallet/alerts" \
--header "Authorization: Bearer $API_TOKEN" \
--header "Content-Type: application/json" \
--data '{"enabled":false,"emailEnabled":false,"telegramEnabled":false,"locale":"en"}'
/api/v2/wallet/alerts/historyReturns up to the latest 50 notification records for this account, newest first. No query parameters, pagination or body are accepted. This is notification history, not a financial ledger or a verification-code history. An empty array means no recorded alerts.
curl --request GET "$API_BASE/api/v2/wallet/alerts/history" \
--header "Authorization: Bearer $API_TOKEN"
{
"data": [{
"id": "1",
"thresholdEuros": 200,
"channel": "email",
"status": "unknown",
"createdAt": "2026-11-02T12:00:00.000Z",
"sentAt": null,
"attempts": 1
}],
"meta": { "requestId": "..." }
}
| Status | Interpretation |
|---|---|
pending | Queued, waiting for delivery, retry or cooldown. |
sending | A delivery attempt is in progress. |
sent | The transport accepted the message. This does not prove it reached an inbox or was read. |
simulated | Recorded without a real delivery. A timestamp in this mode is not proof of receipt. |
failed | The delivery was rejected or its safe retries were exhausted. |
unknown | Delivery is uncertain, for example after an ambiguous transport result. It may or may not have arrived; it is not automatically resent as if certainly failed. |
cancelled | The queued notification was cancelled, for example after a preference or destination change. |
channel is email or telegram; thresholdEuros is 500 or 200; attempts is the recorded attempt count. sentAt can be null. No recipient address, chat ID, code or private link is present in history.
/api/v2/wallet/alerts/telegram-linkCreates a private, opaque Telegram Start link for the token's own account. Send no body or {}. First check telegramAvailable. The link is single-use, expires after 10 minutes and replaces a previously issued unused link. Treat its URL as a credential. Open it in your own Telegram private chat and press Start; groups, manual chatId/chat_id values and arbitrary recipients are not supported. Do not construct or depend on the opaque Start parameter format.
The HTTP 200 data object is exactly {"url":"<private Telegram Start URL>","expiresAt":"<ISO 8601 timestamp>"}. Read settings afterwards and explicitly enable Telegram using PUT only once telegramLinked is true. This wallet-alert API provides no access to administrator order notifications, administrative chats or other accounts' messages, regardless of the bot used.
curl --request POST "$API_BASE/api/v2/wallet/alerts/telegram-link" \
--header "Authorization: Bearer $API_TOKEN"
/api/v2/wallet/alerts/telegram-linkDisconnects this account's private Telegram destination, disables its Telegram selection, invalidates outstanding links and cancels pending Telegram alerts. Send no body or {}. Returns the settings object. Email preferences are not an implied opt-out. The private bot's /stop command also disconnects the wallet-alert link.
curl --request DELETE "$API_BASE/api/v2/wallet/alerts/telegram-link" \
--header "Authorization: Bearer $API_TOKEN"
/api/v2/wallet/alerts/email-verificationRequests a verification message for a separate notification email on this account. Body: exactly {"email":"synthetic@example.invalid"}, replaced with your own inbox. It does not change the login/account email or opt you in. A new request invalidates the previous unused code. The six-digit code expires after 10 minutes and permits at most five incorrect attempts. Request limits are three requests per 15 minutes per account with a minimum one-minute gap; HTTP route limits also apply. The current notification recipient stays in effect until confirmation succeeds.
The HTTP 200 data object is exactly {"expiresAt":"<ISO 8601 timestamp>","mode":"live"}, with mode live or simulation. The code is sent asynchronously to the requested inbox and is never returned by any API response. In simulation mode no real email is sent.
curl --request POST "$API_BASE/api/v2/wallet/alerts/email-verification" \
--header "Authorization: Bearer $API_TOKEN" \
--header "Content-Type: application/json" \
--data '{"email":"synthetic@example.invalid"}'
/api/v2/wallet/alerts/email-verification/confirmConfirms the current unused code for this account. Body: exactly {"code":"123456"}; use the actual six-digit code from your inbox as a string, preserving leading zeroes. Success installs the verified notification email and returns settings with a masked email and emailSource: "notification". It does not automatically opt in or enable a channel. Expired, exhausted or invalidated codes require a fresh verification request; invalid attempts count toward the five-attempt limit.
curl --request POST "$API_BASE/api/v2/wallet/alerts/email-verification/confirm" \
--header "Authorization: Bearer $API_TOKEN" \
--header "Content-Type: application/json" \
--data '{"code":"123456"}'
/api/v2/wallet/alerts/emailClears the separate notification-email override, invalidates pending verification requests, cancels pending notifications for the previous destination and restores the valid account-email fallback, where available. It does not modify the account email. If no valid fallback exists, email selection is disabled. Send no body or {}. Returns settings with emailSource: "account" and only a masked destination or null.
curl --request DELETE "$API_BASE/api/v2/wallet/alerts/email" \
--header "Authorization: Bearer $API_TOKEN"
Error messages are English, contain no raw provider/database details and use only the following envelope:
{
"error": { "code": "WALLET_ALERT_NOT_CONFIGURED", "message": "Wallet alerts are temporarily unavailable. Wallet operations are unaffected." },
"meta": { "requestId": "..." }
}
| HTTP | Code / action |
|---|---|
| 400 | invalid_request: unsupported queries, extra or missing fields, wrong types, invalid email/code format, enabled with no selected channel, malformed JSON, a GET body or unsupported Idempotency-Key. Correct the request before retrying. |
| 401 | unauthorized: missing, invalid or inactive Bearer-token account. Authenticate with the account's own valid token; a web session is not a substitute. |
| 409 | WALLET_ALERT_NOT_ELIGIBLE, WALLET_ALERT_EMAIL_UNAVAILABLE, WALLET_ALERT_EMAIL_INVALID, WALLET_ALERT_TELEGRAM_NOT_LINKED, WALLET_ALERT_TELEGRAM_UNAVAILABLE, WALLET_ALERT_VERIFICATION_INVALID or WALLET_ALERT_VERIFICATION_EXPIRED (also their WALLET_ALERT_EMAIL_VERIFICATION_INVALID/WALLET_ALERT_EMAIL_VERIFICATION_EXPIRED aliases): resolve eligibility, channel state or verification before retrying. |
| 429 | RATE_LIMITED or WALLET_ALERT_RATE_LIMITED. Responses include Retry-After: 60. Wait at least 60 seconds; the three-per-15-minute verification quota may require a longer wait. Do not retry with an Idempotency-Key to evade throttling. |
| 503 | WALLET_ALERT_NOT_CONFIGURED or WALLET_ALERTS_UNAVAILABLE: the optional alert module is unavailable or an unexpected failure occurred. Back off and retry later. Do not infer that a wallet operation failed or submit a duplicate purchase. |
Low route budgets protect notification operations: settings GET 20/minute, settings PUT 6/minute, history GET 10/minute, Telegram link POST 3/minute, Telegram link DELETE 6/minute, verification POST 3/minute, confirmation POST 5/minute and email DELETE 6/minute. Shared-path and network throttling can apply across requests; these are ceilings, not guaranteed capacity. The durable email verification limits above apply independently.
User profile, permissions and wallet endpoints. Prefix: /api/v2/user
/api/v2/user/permissions
Returns the list of permissions assigned to the authenticated user.
Response:
{
"data": {
"permissions": ["acceso_msmvl"]
},
"meta": { "requestId": "..." }
}
/api/v2/user/wallet
Returns wallet information (balance, currency) for the authenticated user.
Response:
{
"data": {
"balance": 50000,
"currency": "EUR"
},
"meta": { "requestId": "..." }
}
Possible errors:
| Code | HTTP | Description |
|---|---|---|
| UNAUTHORIZED | 401 | Missing or invalid authentication token |
| WALLET_NOT_FOUND | 404 | No wallet associated to the authenticated user |
/api/v2/user/wallet/history
Returns the wallet balance change history for the authenticated user.
Query params:
| Name | Type | Description |
|---|---|---|
| limit | number | Items per page (default: 10) |
| offset | number | Offset for pagination, max 100 (default: 0) |
Response:
{
"data": [
{
"name": "John Doe",
"amount": 1500,
"type": "debit",
"description": "eSIM activation",
"createdAt": "2026-03-11T10:00:00.000Z"
}
],
"meta": { "requestId": "..." }
}
Possible errors:
| Code | HTTP | Description |
|---|---|---|
| UNAUTHORIZED | 401 | Missing or invalid authentication token |
| INVALID_QUERY | 400 | Invalid limit or offset query parameters |
| WALLET_NOT_FOUND | 404 | No wallet associated to the authenticated user |
| internal_error | 500 | Failed to retrieve wallet history |
Pre-provisioned Movistar eSIM products from central inventory. Prefix: /api/v2/movistar-esim.
/api/v2/movistar-esim/catalogReturns active Movistar eSIM products with an effective client price and availability flag.
{
"data": [{
"id": 123,
"name": "Movistar eSIM Solo España · Unlimited data · 28 days · No KYC",
"description": "...",
"priceCents": 1990,
"available": true
}],
"meta": { "requestId": "..." }
}
/api/v2/movistar-esim/activateDelivers one eSIM for the requested catalog product, sends the installation PDF and LPA to deliveryEmail, and charges the authenticated account wallet once at its effective commercial price. A unique Idempotency-Key header is required; retrying the same request returns the original delivery without a second charge or duplicate email.
Headers: Idempotency-Key: <16–100 character unique key>
// Request
{ "productId": 123, "deliveryEmail": "customer@example.com" }
// Response
{
"data": {
"orderId": 456,
"productId": 123,
"productName": "Movistar eSIM Solo España · 28 días",
"lpa": "1$...",
"qrDataUrl": "data:image/png;base64,...",
"replayed": false,
"emailDelivery": { "status": "sent", "retryAvailable": true }
},
"meta": { "requestId": "..." }
}
Possible errors: MOVISTAR_ESIM_STOCK_UNAVAILABLE (409), MOVISTAR_ESIM_INSUFFICIENT_BALANCE (402), MOVISTAR_ESIM_PRODUCT_SETUP_REQUIRED (409), MOVISTAR_ESIM_IDEMPOTENCY_CONFLICT (409).
/api/v2/movistar-esim/orders/:orderId/resend-emailResends the delivery PDF and LPA for an existing Movistar eSIM order. It never creates another delivery or charge.
{ "deliveryEmail": "customer@example.com" }
/api/v2/movistar-esim/orders/:orderId/pdfDownloads the protected PDF for a Movistar eSIM delivery belonging to the authenticated account. The response is application/pdf.
SIM/eSIM activation via MSMVL. Prefix: /api/v2/msmvl/activation. Requires
acceso_msmvl permission.
Compatibility note: for compatibility, the brand value "Lyca" also covers
Llamaya. The API parameter must still be sent as "Lyca".
/api/v2/msmvl/activation
Registers and activates a SIM or eSIM number. Performs ICC validation (SIM only), document/age validation, and the activation call. Checks wallet balance before activation, but does not charge the wallet nor trigger recharge or bundle in this step.
After a successful activation, follow the corresponding flow depending on the SIM type:
| SIM type | Required steps (in order) |
|---|---|
| eSIM | /bundlecash |
| Physical SIM — 'product price > facial value' | /recharge → /bundle |
| Physical SIM — 'product price ≤ facial value' or 'facial value = 0' | /bundlecash |
Orange brand is not available for this endpoint and will return
BRAND_NOT_AVAILABLE.
Body (JSON):
{
"productId": "PO_Mali_M",
"brand": "Lebara",
"name": "Juan",
"surname": "Pérez",
"surname2": "García",
"documentNumber": "12345678A",
"dateOfBirth": "1990-05-15",
"nationalityId": "ES",
"iccId": "89340712341234567890",
"simType": "sim",
"identificationType": "personal-identity-code",
"email": "user@example.com",
"language": "eng"
}
Body fields:
| Field | Type | Description |
|---|---|---|
| productId | string | Product/plan identifier (required) |
| brand | "Lebara" | "Lyca" | "Orange" |
Brand (required). Use "Lyca" when referring to Llamaya. |
| name | string | Customer first name (required) |
| surname | string | Customer first surname (required) |
| surname2 | string | Customer second surname (optional) |
| documentNumber | string | Document number (required) |
| dateOfBirth | string | Date of birth (required) |
| nationalityId | string | Nationality code, 2 chars ISO (required) |
| iccId | string | ICC ID, 19–20 chars (required when simType is sim) |
| simType | "sim" | "esim" |
SIM type (required) |
| identificationType | "business-identity-code" | "nie" | "passport" | "personal-identity-code" |
Document type (required) |
| string | Customer email (required) | |
| language | string | Contact language, 3 chars ISO 639-2/T (required, e.g. "eng") |
Success response:
{
"data": {
"msisdnId": "612345678",
"qrCode": "LPA:1$sm-..."
},
"message": "Activation completed successfully.",
"meta": { "requestId": "..." }
}
Success data fields:
| Field | Type | Description |
|---|---|---|
| msisdnId | string | Assigned phone number |
| qrCode | string | null | QR code for eSIM activation (null for physical SIM) |
⏱️ The generated QR code (qrCode) is valid for 24 hours
from the moment of activation. After that period, the QR expires and cannot be used to install the eSIM.
Possible errors:
| Code | HTTP | Description |
|---|---|---|
| BRAND_NOT_AVAILABLE | 400 | Orange brand is not available for this endpoint |
| WALLET_NOT_FOUND | 500 | No wallet associated to user |
| PRODUCT_NOT_FOUND | 400 | Requested product not found |
| ICC_ID_REQUIRED | 400 | iccId missing when simType is "sim" |
| ACTIVATION_COST_UNAVAILABLE | 400 | Could not determine the activation cost for the SIM |
| ICC_ID_NOT_AVAILABLE | 400 | The provided ICC ID is not available for activation |
| INSUFFICIENT_BALANCE | 400 | Wallet balance too low — retry after topping up the wallet |
| ACTIVATION_SAVE_FAILED | 500 |
SIM activated but DB record failed — contact support with the msisdnId in
error.details
|
/api/v2/msmvl/activation/recharge
Step 2 — Charge the user's wallet and trigger the external recharge for the number. Call this after a
successful
POST /activation.
For physical SIM: charges only the difference between the product price and the SIM facial value. If the
facial value already covers the full product price, the wallet is not charged and the operation succeeds
immediately. In that case, you may also skip this endpoint entirely and call
/bundlecash directly instead of invoking /recharge +
/bundle separately.
Body (JSON):
{
"mobileNumber": "612345678"
}
Body fields:
| Field | Type | Description |
|---|---|---|
| mobileNumber | string | Mobile number from the activation step (required) |
Success response:
{
"data": {
"msisdnId": "612345678",
"valueCharged": 5.00
},
"message": "Recharge completed successfully. 5 EUR have been deducted from the wallet.",
"meta": { "requestId": "..." }
}
Possible errors:
| Code | HTTP | Description |
|---|---|---|
| ACTIVATION_NOT_FOUND | 404 | No activation registered for this mobile number |
| FORBIDDEN | 403 | The activation belongs to a different user |
| ACTIVATION_PRODUCT_MISSING | 500 | Activation has no product associated |
| PRODUCT_NOT_FOUND | 400 | Requested product not found |
| ICC_ID_MISSING | 500 | Physical SIM activation has no ICCID associated |
| ACTIVATION_COST_UNAVAILABLE | 400 | Could not determine the recharge cost for this SIM |
| WALLET_NOT_FOUND | 500 | No wallet associated to user |
| INSUFFICIENT_BALANCE | 400 | Wallet balance too low — retry after topping up the wallet |
| WALLET_UPDATE_FAILED | 500 | Failed to deduct from wallet |
| BRAND_NOT_MAPPED | 400 | Brand could not be mapped to the configured recharge product |
| RECHARGE_FAILED | 500 | External recharge call failed |
/api/v2/msmvl/activation/bundle
Step 3a (physical SIM) — Purchases the bundle to apply the product tariff to the number.
Body (JSON):
{
"mobileNumber": "612345678"
}
Body fields:
| Field | Type | Description |
|---|---|---|
| mobileNumber | string | Mobile number from the activation step (required) |
Success response:
{
"data": {
"msisdnId": "612345678",
"provisionId": "PROV-123456"
},
"message": "Bundle purchased successfully.",
"meta": { "requestId": "..." }
}
Possible errors:
| Code | HTTP | Description |
|---|---|---|
| ACTIVATION_NOT_FOUND | 404 | No activation registered for this mobile number |
| FORBIDDEN | 403 | The activation belongs to a different user |
| ACTIVATION_PRODUCT_MISSING | 500 | Activation has no product associated |
| PRODUCT_NOT_FOUND | 400 | Requested product not found |
| BUNDLE_FAILED | 500 | Bundle purchase failed — contact support |
/api/v2/msmvl/activation/bundlecash
Combines the recharge (/recharge) and bundle purchase (/bundle) into a single
call. Can be used in two scenarios:
/recharge and
/bundle.
/recharge and /bundle and call this endpoint directly
to apply the bundle in one step.
Body (JSON):
{
"mobileNumber": "612345678"
}
Body fields:
| Field | Type | Description |
|---|---|---|
| mobileNumber | string | Mobile number from the activation step (required) |
Success response:
{
"data": {
"msisdnId": "612345678",
"basketReference": "REF-123",
"basketId": "BKT-456",
"localReference": "LOC-789"
},
"message": "BundleCash applied successfully.",
"meta": { "requestId": "..." }
}
Possible errors:
| Code | HTTP | Description |
|---|---|---|
| ACTIVATION_NOT_FOUND | 404 | No activation registered for this mobile number |
| FORBIDDEN | 403 | The activation belongs to a different user |
| BUNDLECASH_REQUIRES_ESIM | 400 | BundleCash is only valid for eSIM activations or physical SIM activations where the product price ≤ facial value |
| ACTIVATION_PRODUCT_MISSING | 500 | Activation has no product associated |
| PRODUCT_NOT_FOUND | 400 | Requested product not found |
| BUNDLECASH_FAILED | 500 | BundleCash application failed — contact support |
/api/v2/msmvl/activation/deactivation-roaming
Step 4 — Deactivates roaming control for the activated number. Call this after bundle/bundlecash succeeds. For Lyca Globe products, the operation is skipped automatically and success is returned.
Body (JSON):
{
"mobileNumber": "612345678"
}
Body fields:
| Field | Type | Description |
|---|---|---|
| mobileNumber | string | Mobile number from the activation step (required) |
Success response:
{
"data": { "msisdnId": "612345678" },
"message": "Roaming deactivated successfully.",
"meta": { "requestId": "..." }
}
Possible errors:
| Code | HTTP | Description |
|---|---|---|
| ACTIVATION_NOT_FOUND | 404 | No activation registered for this mobile number |
| FORBIDDEN | 403 | The activation belongs to a different user |
/api/v2/msmvl/activation/facial/:iccId
Get the facial value (activation cost) for a given ICC ID. Used to determine how much balance the SIM already has before activation.
Path params:
| Name | Type | Description |
|---|---|---|
| iccId | string | ICC ID, 19–20 chars (required) |
Query params:
| Name | Type | Description |
|---|---|---|
| brand | "Lebara" | "Lyca" | "Orange" |
Brand name (required) |
| language | string | Language code, 2 chars ISO 639-1 (required) |
Response:
{
"data": {
"value": 10.00
},
"meta": { "requestId": "..." }
}
/api/v2/msmvl/activation/history
Get MSMVL activation history for the authenticated user.
Query params:
| Name | Type | Description |
|---|---|---|
| page | number | Page number, min 1 (default: 1) |
| limit | number | Items per page, 1–100 (default: 10) |
| dateFrom | date | Filter from date (optional, ISO format) |
| dateTo | date | Filter to date (optional, ISO format) |
Response:
{
"data": {
"activationHistory": [
{
"id": 1,
"msisdnId": "612345678",
"qrCode": "LPA:1$sm-...",
"createdAt": "2026-03-11T10:00:00.000Z"
}
],
"total": 42
},
"message": "Activation history retrieved successfully.",
"meta": { "requestId": "..." }
}
Balance top-ups for Orange and Llamaya. Prefix: /api/v2/msmvl/topup. Requires the permission acceso_msmvl.
Commercial security: each sale requires a Idempotency-Key unique. If the supplier's response is uncertain, the platform blocks the automatic retry and returns the identifier for reconciliation.
/api/v2/msmvl/topup/readinessChecks the availability of top-ups and the supported brands. It does not query the number nor execute any sale to the provider.
{
"data": {
"readyForCommercialSubmission": false,
"commercialSubmissionLocked": true,
"providerEnvironment": "staging",
"supportedBrands": ["Orange", "Llamaya"]
}
}
/api/v2/msmvl/topupValidates that the number belongs to the selected brand, sells the top-up, and deducts from the wallet only after confirmation from the supplier.
Required header: Idempotency-Key: <unique-key-16-to-100-characters>
{
"brand": "Orange",
"msisdnId": "+34612345678",
"amount": 10
}
Successful response: includes rechargeId, amount in cents, brand, and provider reference when provided.
| Code | HTTP | Meaning |
|---|---|---|
| LIVE_RECHARGES_LOCKED | 423 | The real sales switch is locked; the supplier is not contacted. |
| OPERATOR_MISMATCH | 400 | The number does not belong to the requested brand; it is neither sold nor charged. |
| RECHARGE_REQUIRES_REVIEW | 409 | The previous result is uncertain; do not retry the same operation. |
/api/v2/msmvl/topup/llamaya-bonusesReturns only Llamaya vouchers assigned and active for the client's token. Does not query the number or the provider. Use the catalogProductId returned in the next step; priceCents is the commercial rate that will be discounted, not the PVP.
{
"data": [{
"catalogProductId": 65,
"providerProductId": "PO_Travel_80",
"name": "Travel 80",
"priceCents": 4000,
"currency": "EUR",
"simType": "sim"
}]
}
/api/v2/msmvl/topup/llamaya-bonusesApplies a Llamaya bonus to an existing line via POST /bundle/cash of MASORANGE. Validates that the line is Llamaya and that the rate remains available. Debits the wallet only once and only after all provider confirmations.
Required header: Idempotency-Key: <unique-key-16-to-100-characters>. Keep the same key to retrieve a completed response; do not reuse it with another number or rate.
{
"msisdnId": "+34612345678",
"catalogProductId": 65
}
Sequence: call /readiness; obtain the catalog with GET /llamaya-bonuses; select catalogProductId; send the POST; save rechargeId and providerReferences for reconciliation.
| Displayed commercial rate | MASORANGE BundleCash product | Components |
|---|---|---|
| National 5, 10, 15 or 20 | PO_Lyca_Globe5/10/15/20 | 1 |
| Travel 15, 30 or 40 | PO_Travel_15/30/40 | 1 |
| Travel 80 | PO_Travel_40 | 2 |
| Travel 120 | PO_Travel_40 | 3 |
| Travel 160 / Unlimited | PO_Travel_40 | 4 |
Important: Travel 80/120/160 is a single commercial recharge, with a single customer rate and an idempotent key. The platform generates a distinct reference per component. If a result is uncertain, it blocks the request for review and does not automatically retry any component.
| Code | HTTP | Meaning |
|---|---|---|
| LIVE_LLAMAYA_BONUSES_LOCKED | 423 | Blocked vouchers; MASORANGE is not queried or contacted. |
| LLAMAYA_BONUS_NOT_ASSIGNED | 404 | The rate is not assigned or is no longer valid for the customer. |
| BONUS_RESULT_UNKNOWN | 502 | Uncertain result; remains under review and should not be retried automatically. |
/api/v2/msmvl/topup/llamaya-lines/readinessIndicates whether the Llamaya tariff and consumption reading query is available. Check readyForProviderQuery, simulationMode, realProviderQueryLocked and dataSource before querying a line. In staging it remains locked by default; an authorized test uses exclusively the internal simulator.
/api/v2/msmvl/topup/llamaya-lines/:msisdnRetrieve the rate, active bonuses, and usage of a Llamaya line that has been activated by the same token. This is a read-only operation: it does not activate, renew, top up, or deduct balance. Check first /readiness"; in an authorized staging simulation the response contains " simulated: true and dataSource: internal_staging_simulator. MASORANGE reports consumption per voucher. The platform only adds remainingAmount = totalAmount − consumedAmount and never estimates GB if the supplier does not provide them.
Invocation: replace :msisdn by the nine-digit mobile number or by its international form +34"; send the owner token in " Authorization: Bearer. There is no parameter to query numbers of other clients.
curl --request GET \
--url 'https://api-staging.activationsim.es/api/v2/msmvl/topup/llamaya-lines/612345678' \
--header 'Authorization: Bearer TU_TOKEN_API' \
--header 'Accept: application/json'
{
"data": {
"msisdn": "612345678",
"lineStatus": "ACTIVE",
"dataSource": "masorange_readonly",
"simulated": false,
"activeTariffs": [{ "providerProductId": "PO_Travel_40", "bundleType": "BundleCash", "activationDate": "..." }],
"bonuses": [{
"name": "Travel 80",
"consumptionSummary": [{
"consumptionType": "DATA",
"totalAmount": 171798691840,
"consumedAmount": 21474836480,
"remainingAmount": 150323855360,
"unit": "BYTE"
}]
}]
}
}
Data usage: Respect the unit returned by the supplier. If consumptionSummary arrives empty, the consumption is not available and must not be calculated from the tariff name.
| Code | HTTP | Meaning |
|---|---|---|
| LLAMAYA_LINE_QUERY_LOCKED | 423 | Query blocked; MASORANGE was not contacted. |
| NUMBER_NOT_OWNED | 403 | The number does not belong to the customer's activation history. |
/api/v2/msmvl/topup/llamaya-renewalsSchedule renewals after the first voucher. Does not apply or charge the first bonus: it must already have been completed with an activation or POST /llamaya-bonuses. A plan of totalMonths: 8 creates seven future cycles, separated by 28 days.
{
"msisdnId": "+34612345678",
"catalogProductId": 65,
"totalMonths": 8,
"maxPriceCents": 4000
}
| Field | Rule |
|---|---|
msisdnId | Llamaya line completed and activated by the same token. |
catalogProductId | Commercial identifier returned by GET /llamaya-bonuses; do not use MASORANGE's technical ID. |
totalMonths | Between 1 and 24, including the initial bonus. With 1, no future cycle is scheduled. |
maxPriceCents | Optional. Renewal cap; by default the current rate is applied. If the rate increases above the cap, the plan remains at requires_review and does not charge. |
Response: returns id, nextRenewalAt, completedRenewals and remainingRenewals. Does not require Idempotency-Key because it does not complete a sale.
/api/v2/msmvl/topup/llamaya-renewalsLists the token's plans, with the current price, the authorized limit, wallet balance, sufficiency for the next cycle, dates and last error. The token never receives lines from another client.
/api/v2/msmvl/topup/llamaya-renewals/:renewalIdManage a self-renewal without repeating any BundleCash. Send exactly one of the following bodies:
{ "action": "pause" }
{ "action": "resume" }
{ "action": "cancel" }
pause retains the calendar; resume reactivate a paused or under review plan; cancel it closes it irreversibly for future cycles, without altering recharges already completed.
/api/v2/msmvl/topup/llamaya-renewals/:renewalId/executionsReturns the auditable ledger per cycle: scheduled date and attempt, status, amount actually charged, rechargeId and MASORANGE references. If there is insufficient balance, price higher than authorized, tariff not assigned or uncertain result, the cycle and the plan remain in requires_review; they are not automatically retried.
Validation utilities. Prefix: /api/v2/msmvl/validate. Requires
acceso_msmvl permission.
/api/v2/msmvl/validate/:iccid
Validate if an ICC ID (SIM card identifier) is available for activation.
Query params:
| Name | Type | Description |
|---|---|---|
| brand | "Lebara" | "Lyca" | "Orange" |
Brand (required) |
/api/v2/msmvl/validate/age
Validate minimum age requirement (must be at least 18 years old).
Query params:
| Name | Type | Description |
|---|---|---|
| dateOfBirth | string | Date of birth in YYYY-MM-DD format (required) |
/api/v2/msmvl/validate/document
Validate an identification document number.
Query params:
| Name | Type | Description |
|---|---|---|
| documentNumber | string | Document number (required) |
| identificationType | "business-identity-code" | "nie" | "passport" | "personal-identity-code" |
Document type (required) |
Commercial catalog associated with the token. Prefix: /api/v2/msmvl/products. Requires acceso_msmvl permission.
/api/v2/msmvl/products
Returns only the products with a commercial tariff in effect for the token. Each element includes catalogProductId for reference, productId to send during activation, simType, priceCents and priceEur. The price is the one that will be deducted from the customer's wallet; it must never be replaced by the supplier's technical rate.
Query params:
| Name | Type | Description |
|---|---|---|
| brand | "Lebara" | "Lyca" | "Orange" |
Brand required. Use "Lyca" for Llamaya. |
| language | "en" | "es" |
Optional language for the labels. The default value is es. |
Phone number management. Prefix: /api/v2/msmvl/msisdn-op. Requires
acceso_msmvl permission.
/api/v2/msmvl/msisdn-op/operator/:msisdn
Get the operator info for a phone number (current operator, origin, portability status).
/api/v2/msmvl/msisdn-op/balance/:msisdn
Get balance for a phone number.
Query params:
| Name | Type | Description |
|---|---|---|
| brand | "Lebara" | "Lyca" | "Orange" |
Brand (required) |
/api/v2/msmvl/msisdn-op/msisdn/:msisdn
Get comprehensive MSISDN info (ICC ID, IMSI, PINs, PUKs, status, product, balance, bonuses, QR code).
Query params:
| Name | Type | Description |
|---|---|---|
| brand | "Lebara" | "Lyca" | "Orange" |
Brand (required) |
/api/v2/msmvl/msisdn-op/msisdn/:mobileNumber/services
Get active services for a phone number.
Query params:
| Name | Type | Description |
|---|---|---|
| brand | "Lebara" | "Lyca" | "Orange" |
Brand (required) |
/api/v2/msmvl/msisdn-op/msisdn/:mobileNumber/esimQr
Get eSIM activation QR info (URL and request date) for a phone number.
Query params:
| Name | Type | Description |
|---|---|---|
| brand | "Lebara" | "Lyca" | "Orange" |
Brand (required) |
Inventory lookup. Prefix: /api/v2/msmvl/inventory. Requires
acceso_msmvl permission.
/api/v2/msmvl/inventory/:mobileNumber
Get inventory info (ID and brand) for a mobile number.
Path params:
| Name | Type | Description |
|---|---|---|
| mobileNumber | string | Mobile number, exactly 9 digits (required) |
Response:
{
"data": {
"id": "INV_12345",
"brand": "Lebara"
},
"meta": { "requestId": "..." }
}