Skip to content

Error Codes Reference ​

All API errors follow a consistent bilingual format. The message field is automatically localized based on the Accept-Language header.

Error Response Structure ​

json
{
  "error": {
    "code": "ERROR_CODE",
    "message": "English error message",
    "details": [
      { "path": "fieldName", "message": "Field-specific error" }
    ]
  }
}

Arabic (Accept-Language: ar):

json
{
  "error": {
    "code": "ERROR_CODE",
    "message": "رسالة الخطأ بالعربية",
    "details": [
      { "path": "fieldName", "message": "خطأ خاص بالحقل" }
    ]
  }
}

The details field is optional and only included for validation errors with field-level information.


Authentication Errors (401 / 403 / 502) ​

CodeStatusEnglish MessageArabic MessageCondition
AUTH_REQUIRED401Authentication requiredيلزم المصادقةMissing or invalid token
AUTH_SESSION_EXPIRED401Session has expiredانتهت صلاحية الجلسةExpired JWT
AUTH_ACCOUNT_DEACTIVATED401Account is deactivatedالحساب معطلSign-in blocked at OTP verification for a deactivated account (admin deactivation or account-deletion soft-delete)
AUTH_USE_MOBILE_APP403This account is for the Skyte mobile app. Please sign in there.هذا الحساب مخصص لتطبيق Skyte على الجوال. يرجى تسجيل الدخول من التطبيق.Customer (end_user) signing in on a web client (verify-otp, magic-link)
AUTH_USE_PORTAL403Portal accounts sign in from the Skyte web portal.حسابات البوابة تسجل الدخول من بوابة Skyte على الويب.Non-end_user role signing in on a mobile client (verify-otp, magic-link)
AUTH_INVALID_OTP401Invalid or expired codeرمز التحقق غير صالح أو منتهي الصلاحيةWrong code, unregistered phone/email, or no code to check (never issued, expired after 300s, or spent by 5 wrong attempts). One answer, so verification never reveals whether a code was issued
AUTH_NO_CREDENTIAL401At least one of phone or email is requiredيلزم توفير رقم الهاتف أو البريد الإلكترونيNeither phone nor email provided
AUTH_PHONE_EXISTS401Phone number already registeredرقم الهاتف مسجل مسبقاًDuplicate phone on registration
AUTH_EMAIL_EXISTS401Email address already registeredالبريد الإلكتروني مسجل بالفعلDuplicate email on registration. PUT /profile and PUT /auth/me return it, with the same messages, as status 409 when another account already uses the email
AUTH_LAST_IDENTIFIER409Cannot remove the only sign-in identifierلا يمكن حذف وسيلة تسجيل الدخول الوحيدةemail: null on PUT /profile or PUT /auth/me for an account with no phone
AUTH_CONSENT_REQUIRED401Required consent types missingأنواع الموافقة المطلوبة ناقصةInvalid or missing consent entries
AUTH_RATE_LIMIT_EXCEEDED401Too many OTP requests, please try again laterطلبات كثيرة للتحقق، يرجى المحاولة لاحقاًPer-recipient OTP request throttle. More than 3 OTP requests were sent to the same phone or email within the 15-minute window (register or change-phone; login sends in the background and never reports it). This is separate from verification-attempt limiting, which is reported under AUTH_INVALID_OTP instead
AUTH_PHONE_UPDATE_FAILED502Failed to update phone numberفشل تحديث رقم الهاتفThe phone-change OTP was verified, but persisting the new phone number failed. This happens if the local database update itself fails, or if it succeeds but the following Supabase Auth update fails (the local change is then rolled back)
AUTH_BLOCKLISTED403User or device is blocklistedالمستخدم أو الجهاز محظورBlocklist check
FORBIDDEN401Insufficient permissions — super_admin requiredصلاحيات غير كافية — مطلوب super_adminNon-super_admin on compliance routes
AUTH_FORBIDDEN403Insufficient permissionsصلاحيات غير كافيةRole/area guard denied the request (a role other than super_admin or admin on /admin/users, or an admin without staff-users, a user lacking the required area on an area-gated route, or a non-end_user on a customer-only route)

AUTH_INVALID_OTP is used generically for both wrong OTP and unregistered phone/email to prevent enumeration attacks.

AUTH_PHONE_EXISTS is 401 here (thrown as an AuthError on /auth/register) but 409 on /auth/change-phone and /auth/verify-change-phone (thrown there as a plain AppError) — see Authentication.

FORBIDDEN (compliance routes) is thrown as an AuthError, whose constructor only maps the exact code AUTH_FORBIDDEN to 403 — every other code, including the distinct string FORBIDDEN, resolves to 401. So despite the "super_admin required" message, this code actually returns 401, not 403.


Validation Errors (400) ​

CodeStatusEnglish MessageCondition
VALIDATION_ERROR400Validation failedZod schema validation failed. details array contains field-specific errors.
VALIDATION_ICCID_INVALID400ICCID must be exactly 19 digitsICCID format validation failed
VALIDATION_SIM_LIMIT_EXCEEDED400SIM limit exceeded for your identity typeCITC SIM limit per identity type exceeded

Validation Error Details Example ​

json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": [
      { "path": "phone", "message": "Phone must be Saudi format +966XXXXXXXXX" },
      { "path": "iccid", "message": "ICCID must contain only digits" }
    ]
  }
}

Not Found Errors (404) ​

CodeStatusEnglish MessageArabic MessageCondition
NOT_FOUND404Resource not foundالمورد غير موجودGeneric not found
PACKAGE_NOT_FOUND404Package not foundالباقة غير موجودةPackage ID not found or inactive
ACTIVATION_NOT_FOUND404Activation not foundالتفعيل غير موجودActivation ID not found or not owned by user

Administration User Errors (403 / 404 / 409 / 502) ​

Returned by the /admin/users endpoints. See Admin Users.

CodeStatusEnglish MessageArabic MessageCondition
ADMIN_USER_SCOPE_EXCEEDED403This change is outside your own access.هذا التغيير خارج نطاق صلاحياتك.A grant beyond the caller's own levels, or a target the caller may not act on (itself, or outside its access); also POST /admin/companies/invites without Manage on the area the invite needs
ADMIN_USER_NOT_FOUND404Administration user not foundمستخدم الإدارة غير موجودNo manageable user with the given id (owners and end users are not manageable), or a target outside every area the caller can view
ADMIN_USER_PHONE_EXISTS409Phone number already registeredرقم الهاتف مسجل بالفعلPhone already in use on create
ADMIN_USER_EMAIL_EXISTS409Email address already registeredالبريد الإلكتروني مسجل بالفعلEmail already in use on create
ADMIN_USER_CREATE_FAILED502Failed to create user accountفشل إنشاء حساب المستخدمAuth account creation failed upstream
ADMIN_USER_PROFILE_NOT_CREATED502Failed to create user profileفشل إنشاء الملف الشخصي للمستخدمProfile row was not seeded; the orphaned auth identity is rolled back
ADMIN_USER_DELETE_FAILED502Failed to remove userفشل حذف المستخدمAuth identity removal failed upstream, after the profile row was already deleted

PATCH and DELETE on a company person can also return COMPANY_LAST_ADMIN or COMPANY_ADMIN_ROLE_LOCKED — see Company Errors.


Officer Resend Errors (409) ​

Returned by the /admin/officer delivery resend routes. See Customers.

CodeStatusEnglish MessageArabic MessageCondition
OFFICER_RESEND_NOT_ISSUED409Nothing has been issued for this record yetلم يتم إصدار أي شيء لهذا السجل بعدThe activation is not an eSIM or has no issued profile yet, or the store order has not been issued
OFFICER_RESEND_RECORD_DELETED409This record has been deletedتم حذف هذا السجلThe activation or store order is soft-deleted
OFFICER_RESEND_CREDENTIALS_CLEARED409This order no longer holds an eSIM profile to sendلم يعد هذا الطلب يحتوي على ملف eSIM لإرسالهThe store order was issued but no longer holds an eSIM profile
OFFICER_RESEND_NO_EMAIL409The customer has no email addressلا يوجد بريد إلكتروني مسجّل لهذا العميلThe delivery email cannot be sent without an address

Notification Errors ​

See Notifications API and notification callbacks. The shared catalogue declares ten notification errors. The routes add two service-unavailability errors and a payload-size error.

CodeStatusEnglish MessageArabic MessageCondition
NOTIFICATION_NOT_FOUND404Notification not foundالإشعار غير موجودInbox item is missing, foreign, or scrubbed
NOTIFICATION_BATCH_NOT_FOUND404Notification batch not foundدفعة الإشعارات غير موجودةUnknown batch ID
NOTIFICATION_BATCH_NOT_CANCELLABLE409This notification batch can no longer be cancelledلم يعد بالإمكان إلغاء دفعة الإشعارات هذهBatch is running or finished
NOTIFICATION_INVALID_CUSTOM_CONTENT400Notification content is invalidمحتوى الإشعار غير صالحDeclared catalogue code. Current HTTP content validation returns VALIDATION_ERROR instead
NOTIFICATION_UNKNOWN_EVENT400Unknown notification eventحدث إشعار غير معروفTrigger event is unknown. Current HTTP template validation returns VALIDATION_ERROR instead
NOTIFICATION_CHANNEL_DISABLED400This notification channel is not availableقناة الإشعارات هذه غير متاحةRequested channel is disabled or unsupported by the template
NOTIFICATION_OPT_OUT_NOT_ALLOWED400This notification channel cannot be turned offلا يمكن إيقاف قناة الإشعارات هذهDeclared catalogue code. Current HTTP opt-out validation returns VALIDATION_ERROR for in_app instead
NOTIFICATION_WEBHOOK_SIGNATURE_INVALID401Notification callback signature is invalidتوقيع استدعاء الإشعارات غير صالحCallback signature is missing or invalid
NOTIFICATION_WEBHOOK_PROVIDER_UNKNOWN404Unknown notification callback providerمزود استدعاء الإشعارات غير معروفChannel or provider is unknown, lacks callback capabilities, or is a mock callback in production
PAYLOAD_TOO_LARGE413Notification callback payload is too largeحجم بيانات استدعاء الإشعارات أكبر من المسموحRaw callback body exceeds the 64 KiB cap. Rejected before reading, parsing or signature handling
NOTIFICATION_IDEMPOTENCY_CONFLICT409This idempotency key was already used with a different requestمفتاح منع التكرار هذا مستخدم بالفعل مع طلب مختلفSame sending administrator and unexpired key with a different validated request
SERVICE_UNAVAILABLE503Service unavailableالخدمة غير متاحةAdministrative send acceptance or replay recovery failed. Retry-After: 1
NOTIFICATION_WEBHOOK_UNAVAILABLE503Notification callback could not be queued. Retry the same event.تعذّر وضع استدعاء الإشعار في قائمة الانتظار. أعد إرسال الحدث نفسه.Callback receipt, enqueue, or handoff-stamp write failed. Retry-After: 60

Protected routes also use AUTH_REQUIRED and administrative routes use AUTH_FORBIDDEN. Invalid fields use VALIDATION_ERROR. Applicable HTTP throttling returns RATE_LIMIT_EXCEEDED, including on notification callbacks.

Delivery Failure Codes ​

These values are durable delivery bookkeeping, not HTTP error envelopes. Batch failure lists expose them through errorCode.

CodeCondition
NOTIFICATION_PROVIDER_RETRYABLEProvider rejected the attempt transiently
NOTIFICATION_PROVIDER_TERMINALProvider rejected the delivery permanently
NOTIFICATION_ATTEMPT_FAILEDClassified fallback for rendering, dispatch, or worker failure
NOTIFICATION_STALLEDStalled recovery exhausted its allowance
NOTIFICATION_START_LIMITWorker exceeded the allowed number of starts

A verified terminal callback can instead record its provider-supplied reason code. Suppression reasons are account_inactive, channel_opted_out, no_address, and provider_unsupported. They are not HTTP errors. NOTIFICATION_CALLBACK_UNMATCHED is a retry signal for callback correlation, not a response code or durable delivery failure.


Company Errors (403 / 404 / 409 / 502) ​

Returned by the B2B company registration, portal, and admin-oversight endpoints. See B2B Portal and B2B Admin.

CodeStatusEnglish MessageArabic MessageCondition
COMPANY_NOT_FOUND404Company not foundالشركة غير موجودةNo company with the given id
COMPANY_ALREADY_REGISTERED409You already belong to a companyأنت مرتبط بشركة بالفعلCaller already has a company membership
COMPANY_CR_EXISTS409CR number already registeredرقم السجل التجاري مسجل بالفعلCommercial registration number already in use by another company, on registration or on PATCH /admin/companies/:id
COMPANY_CREATE_FAILED502Failed to register companyفشل تسجيل الشركةCompany registration failed
COMPANY_INVALID_TRANSITION409Cannot {action} a {status} companyإجراء غير صالح لحالة الشركة الحاليةThe status action (approve, reject, suspend, reactivate, archive, unarchive) is not valid for the company's current status
COMPANY_ARCHIVED409An archived company is read-onlyلا يمكن تعديل شركة مؤرشفةAn internal-admin write on an archived company: editing it, inviting into it, starting review on, countering or rejecting its quote, confirming its contract, or recording its payment
COMPANY_LAST_ADMIN409The company's last active admin cannot be deactivated or removedلا يمكن تعطيل أو حذف آخر مسؤول نشط في الشركةPATCH /admin/users/:id with isActive: false, or DELETE /admin/users/:id, on the last active admin of a company
COMPANY_ADMIN_ROLE_LOCKED409A company admin's role cannot be changedلا يمكن تغيير دور مسؤول الشركةPATCH /admin/users/:id sends role for a b2b_admin target
COMPANY_NOT_APPROVED403Company is pending approvalالشركة في انتظار الموافقةPortal route accessed by a member of a not-yet-approved company (also returned, with a different message, when a magic link is redeemed for — or POST /activations is attempted against — a company that isn't approved)
COMPANY_SUSPENDED403Company access is suspendedتم تعليق وصول الشركةPortal route accessed by a member of a suspended company
COMPANY_REJECTED403Company registration was rejectedتم رفض تسجيل الشركةPortal route accessed by a member of a rejected company
COMPANY_NOT_READY409This company isn't ready to send activation links yet — the contract and payment must be confirmed firstهذه الشركة غير جاهزة لإرسال روابط التفعيل بعد — يجب تأكيد العقد والدفع أولاًMagic links minted before the contract + payment readiness gate is satisfied
COMPANY_BALANCE_EXCEEDED409This activation would exceed your company's available balanceسيؤدي هذا التفعيل إلى تجاوز الرصيد المتاح لشركتكFlexible balance gate: drawn funds plus this seat's charge would pass the company's funded amount (also appears as a per-row code in the link-send failed array)

Quote Errors (400 / 404 / 409 / 502) ​

Returned by the B2B quote lifecycle endpoints. See B2B Portal and B2B Admin.

CodeStatusEnglish MessageArabic MessageCondition
QUOTE_NOT_FOUND404Quote not foundالطلب غير موجودNo quote with the given id (or, on the admin read, the quote is still a private draft)
QUOTE_INVALID_STATE409Varies with the action (e.g. "Quote has no items to confirm")يختلف حسب الإجراءThe quote's status doesn't allow the requested action
QUOTE_CREATE_FAILED502Failed to create quoteفشل إنشاء الطلبQuote draft creation failed
QUOTE_VERSION_SUPERSEDED409A newer version has superseded this oneاستُبدلت هذه النسخة بنسخة أحدثAction attempted on a quote version that a counter has since superseded
QUOTE_COUNTER_FAILED502Failed to create the countered versionفشل إنشاء النسخة المعدَّلةCountering a quote failed
QUOTE_PACKAGE_NOT_FOUND400One or more packages do not exist or are not available for business ordersباقة واحدة أو أكثر غير موجودة أو غير متاحة لطلبات الأعمالA quote line references a package id that doesn't exist or is not B2B-orderable (inactive, or no B2B price — the same surface the B2B catalog exposes). The offending ids are in error.details.packageIds

Contract Errors (400 / 404 / 409 / 502) ​

Returned by the B2B contract confirmation and payment endpoints. See B2B Portal and B2B Admin.

CodeStatusEnglish MessageArabic MessageCondition
CONTRACT_NOT_FOUND404Contract not foundالعقد غير موجودNo contract with the given id
CONTRACT_ALREADY_EXISTS409This company already has an active contractيوجد لدى هذه الشركة عقد نشط بالفعلConfirming a quote for a company that already has an active contract
CONTRACT_ITEM_NOT_FOUND404Contract line not found on this company's active contractبند العقد غير موجود ضمن العقد النشط لهذه الشركةA roster package assignment references a contract line that doesn't belong to the company's active contract
CONTRACT_PACKAGE_PRICE_MISSING400One or more quoted packages have no B2B price set — cannot confirm this contractباقة واحدة أو أكثر في طلب تحديد الباقات لا تملك سعر أعمال محدد — لا يمكن تأكيد هذا العقدConfirming a quote where a quoted package has no priceSarB2b
CONTRACT_CREATE_FAILED502Failed to create contractفشل إنشاء العقدContract creation failed
CONTRACT_PAYMENT_MODE_INVALID409Payments cannot be recorded for this contractلا يمكن تسجيل الدفعات لهذا العقدRecording a payment on a contract whose payment mode is neither prepaid nor flexible (legacy/invalid data — every contract is one or the other today)
CONTRACT_PAYMENT_BELOW_TOTAL409Payment amount is below the contract totalقيمة الدفعة أقل من إجمالي العقدRecorded payment amount is less than the contract's totalSar
CONTRACT_PAYMENT_IDEMPOTENCY_CONFLICT409This idempotency key was already used for a different paymentمفتاح العملية مستخدم بالفعل لدفعة مختلفةThe same idempotencyKey was replayed with a different method or amount — replaying it with identical values returns the original record instead
CONTRACT_PAYMENT_DOCUMENT_INVALID_MIME400Unsupported document typeنوع المستند غير مدعومThe uploaded payment document's MIME type isn't accepted
CONTRACT_PAYMENT_DOCUMENT_TOO_LARGE400Document must be 5MB or lessيجب ألا يتجاوز حجم المستند 5 ميغابايتThe uploaded payment document exceeds the 5MB size cap
CONTRACT_PAYMENT_RECORD_FAILED502Failed to record paymentفشل تسجيل الدفعةRecording the payment failed
CONTRACT_PAYMENT_DOCUMENT_NOT_FOUND404Payment document not foundمستند الدفع غير موجودThe payment record has no supporting document to download

Roster Errors (404 / 409) ​

The B2B CSV roster staging endpoint returns no error envelope of its own. Row-level problems — invalid phone or email, a duplicate contact, or a contact a concurrent upload claimed first — come back as data in the rejected array of a 200 response. The same holds for the batch link routes, whose per-entry skips and failures are data too.

The two single-entry roster-control routes are the exception. PATCH /b2b/roster/:rosterId and DELETE /b2b/roster/:rosterId write one row, so they have no partial-accept response to carry a reason and throw instead. See B2B Portal.

CodeStatusEnglish MessageArabic MessageCondition
ROSTER_ENTRY_NOT_FOUND404Roster entry not foundصف القائمة غير موجودNo roster entry with the given id on the caller's own company roster (also appears as a per-entry skip code on the batch link routes)
ROSTER_LINK_OPEN409This person has an open invitation — revoke it firstلدى هذا الشخص دعوة مفتوحة — يجب إلغاؤها أولاًA live, unexpired issued/sent/exported link still holds this seat, so the assignment cannot change and the person cannot be removed. Cleared with POST /b2b/roster/links/revoke.
ROSTER_SEAT_CONSUMED409This person already used their invitation — their seat is permanently theirsاستخدم هذا الشخص دعوته بالفعل — أصبح مقعده دائمًاThe beneficiary redeemed a link at some point. Redemption is permanent, even once that link is past its own expiry. The removal route also returns this code, with the message "This person already has activation history — their record cannot be removed", when the underlying account is no longer a bare staged end user.

Returned by POST /auth/magic-link on redemption. See Authentication.

CodeStatusEnglish MessageArabic MessageCondition
MAGIC_LINK_INVALID401This link is not validهذا الرابط غير صالحToken doesn't match any issued magic link
MAGIC_LINK_ALREADY_USED401This link has already been usedتم استخدام هذا الرابط بالفعلThe link's status is already used (or it lost the race to consume it)
MAGIC_LINK_REVOKED401This link has been revokedتم إلغاء هذا الرابطThe link was revoked before redemption
MAGIC_LINK_EXPIRED401This link has expiredانتهت صلاحية هذا الرابطThe link's 24h TTL has passed
MAGIC_LINK_USER_ALREADY_ACTIVE409This account already has an active SIMيوجد لدى هذا الحساب شريحة مفعّلة بالفعلRedemption succeeded but the user's latest activation is already activated. The link send/export routes pre-empt this at mint time with the per-entry skip LINK_USER_ALREADY_ACTIVE (same predicate), so a company-minted link should no longer reach this state

COMPANY_NOT_APPROVED (403) is also returned here — see Company Errors.


Document Errors (404 / 500) ​

Returned by GET /documents/:type/:entityId.pdf. See Documents.

CodeStatusEnglish MessageArabic MessageCondition
DOCUMENT_TYPE_NOT_FOUND404Document type not foundنوع المستند غير موجود:type isn't a registered document type
DOCUMENT_NOT_FOUND404Document not foundالمستند غير موجود:entityId isn't a valid UUID, or no matching entity exists
DOCUMENT_RENDER_FAILED500Failed to render documentفشل إنشاء المستندPDF rendering threw after the access policy and entity lookup both passed

Support and Officer Errors (403 / 404 / 409 / 422 / 503) ​

These codes are returned by /tickets, /admin/support, and /admin/officer. See Support Tickets and Activation Officer for route scope and state rules.

CodeStatusMeaning
TICKET_NOT_FOUND404Ticket is missing or outside the caller's ownership/assignment scope
TICKET_CLOSED409Closed ticket cannot receive comments or change status
TICKET_LINK_INVALID422Linked activation/store order is invalid or outside customer scope
TICKET_ASSIGNEE_INVALID422Assignee is not an active activation officer
TICKET_ACTION_NOT_ALLOWED409Ticket type, status, assignment, or transition refuses the action
REFUND_ORDER_NOT_PAID422Refund link is not on a paid order
REFUND_NOT_DECIDABLE409Activation approval requires failed or cancelled
REFUND_ALREADY_DECIDED409Refund decision already exists
REFUND_SELF_DECISION403Request opener cannot decide their own request
REFUND_DECIDER_CANNOT_OPEN403Caller holds the refund decider role, which never opens a refund request
REFUND_STORE_APPROVAL_DEFERRED409Store-linked approval is not enabled yet
OFFICER_CLAIM_TAKEN409Another officer claimed the row or it no longer needs attention
OFFICER_CLAIM_NOT_HELD409Caller does not hold the row claim
OFFICER_ACTION_NOT_ALLOWED409Officer action is invalid for the current state or scope
OFFICER_JOB_IN_FLIGHT409Completion job is still live
OFFICER_COMPANY_READ_ONLY409Company activation is visible but immutable in the officer console, and cannot get a refund request from any staff route
OFFICER_LOOKUP_FAILED502The officer console's direct operator lookup call failed or timed out
IDENTITY_START_UNAVAILABLE503Identity-start lease could not be acquired safely

Activation Errors (409 / 422) ​

CodeStatusEnglish MessageArabic MessageCondition
ACTIVATION_FAILED422Activation failedفشل التفعيلGeneral activation failure
ACTIVATION_ICCID_INVALID422ICCID validation failedفشل التحقق من رقم ICCIDOperator rejected ICCID
ACTIVATION_ESIM_DISABLED422eSIM activation is disabledتفعيل eSIM غير مفعّل حاليًاFEATURE_ESIM is off for an eSIM request, or a corporate B2B user attempted eSIM activation before that lane is enabled
ACTIVATION_CODE_INVALID422Invalid eSIM activation codeرمز تفعيل eSIM غير صالحeSIM activation create reached the operator validation path and the LPA activation code was rejected
ACTIVATION_ESIM_UNSUPPORTED_OPERATOR422This operator does not support eSIM activationهذا المشغل لا يدعم تفعيل eSIMsimType: esim was requested for an operator plugin that did not explicitly declare supportsEsim: true
ACTIVATION_PACKAGE_NOT_ON_CONTRACT409This package is not on your company's contractهذه الباقة غير مدرجة في عقد شركتكB2B activation: no roster entry, no assigned contract line, the requested package doesn't match the assigned line, or the contract is not active
ACTIVATION_LINK_NOT_REDEEMED409Open your company invite link before activatingيجب فتح رابط دعوة شركتك قبل التفعيلB2B activation: the roster entry has no redeemed invite link — OTP login alone cannot activate a staged seat
ACTIVATION_SEAT_ALREADY_USED409This account already has an active or pending SIMيوجد لدى هذا الحساب شريحة نشطة أو قيد التفعيل بالفعلB2B activation: the seat already has a pending/in-progress/activated SIM (failed/cancelled allow retry)
IDENTITY_ERROR422Identity verification failedفشل التحقق من الهويةIdentity verification initiation/retrieval failure
ACTIVATION_SERIAL_IN_USE409This SIM serial is already in useرقم الشريحة مستخدم بالفعلCustomer activation creation found another paid activation already holding the ICCID

serial_in_use and officer_verified_failed are durable activation errorCode values, not HTTP envelope codes. serial_in_use is written when completion's first-claim check finds another physical activation already holding the serial. officer_verified_failed is written when an officer records evidence that the operator confirmed the line was not activated. The latter uses a fixed customer-facing reason and keeps typed evidence in audit data.


Identity Type and Number Errors (400 / 409) ​

Returned by the customer identity check (POST /activations/:id/verify-identity) and PUT /profile. See Activations and Profile.

CodeStatusEnglish MessageArabic MessageCondition
ID_NUMBER_INVALID400The identity number is not valid for this identity typeرقم الهوية غير صالح لنوع الهوية هذاThe number does not fit the identity type's format, or fails the check digit
ID_NUMBER_TYPE_MISMATCH400This identity number belongs to the other identity type. A national ID starts with 1 and an iqama with 2رقم الهوية هذا يخص نوع الهوية الآخر. تبدأ الهوية الوطنية بالرقم 1 والإقامة بالرقم 2A well-formed citizen number was checked as resident, or the reverse
IDENTITY_TYPE_LOCKED409The identity type cannot be changed while an identity check is waiting or after one has completedلا يمكن تغيير نوع الهوية أثناء انتظار التحقق من الهوية أو بعد اكتمالهAn identity type change was requested while an identity check for the customer is waiting, or after one has completed
IDENTITY_TYPE_COMPANY_MANAGED409A company employee's identity type comes from the company's rosterنوع هوية موظف الشركة يأتي من قائمة موظفي الشركةAn identity type change was requested for a company employee

Sending the identity type already on the record is never refused, locked or not. A citizen number is 10 digits starting 1, a resident number is 10 digits starting 2, and both must pass the check digit: the digits in places 1, 3, 5, 7 and 9 are doubled, a doubled value of 10 or more counts as the sum of its digits, and the total of all ten digits must be a multiple of 10. A visitor number keeps the looser 5 to 20 character rule.


eSIM Store Errors (400 / 404 / 409 / 422 / 503) ​

Returned by the public eSIM store, store-order, admin catalog, admin order recovery, FX settings, and manual sync endpoints. See eSIM Store.

CodeStatusEnglish MessageArabic MessageCondition
STORE_DISABLED422eSIM store is disabledمتجر eSIM غير مفعّل حاليًاFEATURE_ESIM_STORE is off — gates browse (GET /store/destinations, GET /store/products, GET /store/products/:id) and store writes (POST /store/orders, POST /store/device-acks); GET /store/status stays public, and owned-order reads and the QR endpoint stay available so delivered codes are never locked out
STORE_PRODUCT_NOT_FOUND404Store product not foundمنتج المتجر غير موجودProduct is missing, or public detail hides it because it is inactive
STORE_PRODUCT_INACTIVE422Store product is inactiveمنتج المتجر غير متاح حاليًاBuy request targets an inactive product
STORE_CURRENCY_UNSUPPORTED400Unsupported store currencyعملة المتجر غير مدعومةCurrency value is outside the supported USD / SAR / AED / EUR / GBP / EGP set, or a supported currency's FX rate has not been fetched yet (details.currency + details.detail). On list and buy routes, unsupported currencies are rejected as standard validation errors; on the detail route, the explicit currency param fires this code
STORE_KYC_REQUIRED422This eSIM requires KYC and cannot be purchased yetتتطلب هذه الشريحة تحققًا من الهوية ولا يمكن شراؤها حاليًاBuy request targets a KYC provider SKU whose provider does not support the async verification flow (or is not registered). With a supporting provider the purchase proceeds and issuance answers with a verification URL instead
STORE_ORDER_NOT_FOUND404Store order not foundطلب المتجر غير موجودStore order is missing, foreign to the caller, or not issued yet for QR delivery; also returned by the admin order detail and requeue routes for unknown ids
STORE_SYNC_UNAVAILABLE503eSIM store sync is temporarily unavailableمزامنة متجر eSIM غير متاحة مؤقتًاManual admin catalog sync could not be enqueued because Redis/queueing is unavailable
STORE_ORDER_NOT_REQUEUEABLE409Store order is not in a requeueable stateطلب المتجر ليس في حالة تسمح بإعادة الإدراجAdmin requeue targeted an order that is not paid-without-an-issued-profile (already issued, completed, cancelled, or still pending payment)
STORE_REQUEUE_UNAVAILABLE503Store fulfillment requeue is temporarily unavailableإعادة إدراج تلبية طلب المتجر غير متاحة مؤقتًاAdmin requeue could not enqueue a store fulfillment job because Redis/queueing is unavailable
STORE_CHARGE_BELOW_MINIMUM422Charge amount is below the minimum allowed by payment processorsمبلغ الدفع أقل من الحد الأدنى المسموح به لمعالجات الدفعThe calculated store order charge amount is below the mandatory minimum threshold
STORE_PROVIDER_PAYMENT_EXTERNAL422External provider checkout is not supported yetالدفع عبر مزوّد الخدمة غير مدعوم بعدThe store order's provider has payment_mode = 'external'. Fail-closed — order creation and card/Apple Pay payment initiation both block before any insert or PSP call. Missing linkage or unrecognised mode values throw the same code.
STORE_PRICE_CHANGED409Store product price has changed; re-fetch the product and retry with the current priceتغيّر سعر منتج المتجر؛ يرجى إعادة جلب المنتج والمحاولة بالسعر الحاليOptional expectedPrice guard on POST /store/orders: the request carried expectedPrice and it was not strictly equal to the freshly computed presentment price.amount; details.currentPrice: { amount, currency } carries the current price so the client can re-render without a refetch
STORE_PROVIDER_NOT_FOUND404Store provider not foundمزوّد المتجر غير موجودAdmin provider toggle (PATCH /admin/store/providers/:id) targeted an id with no eSIM operator row — telecom operators are invisible to this route by design
STORE_PROVIDER_UNAVAILABLE422Store provider is temporarily unavailableمزوّد المتجر غير متاح مؤقتًاCheckout circuit breaker on POST /store/orders: the product's provider plugin is not registered in this process, its latest health reading is unhealthy, or the health check itself failed (fail closed)

Payment Errors (402 / 404) ​

CodeStatusEnglish MessageArabic MessageCondition
PAYMENT_INTENT_FAILED402Payment intent creation failedفشل إنشاء نية الدفعPayment checkout creation error
PAYMENT_METHOD_NOT_SUPPORTED_FOR_ORDER422This payment method is not available for eSIM store ordersطريقة الدفع هذه غير متاحة لطلبات متجر الشرائح الإلكترونيةmada or STC Pay attempted on an eSIM store order; cards and Apple Pay are the only supported store rails
PAYMENT_APPLE_PAY_FAILED402Apple Pay payment failedفشل دفع Apple PayApple Pay confirmation failed
PAYMENT_STCPAY_FAILED402STC Pay initiation failedفشل بدء عملية STC PaySTC Pay initiation error
PAYMENT_RECEIPT_FAILED402Receipt retrieval failedفشل استرجاع الإيصالReceipt generation error
PAYMENT_ORDER_INVALID_STATUS402Order is not in pending_payment statusالطلب ليس في حالة انتظار الدفعThe order is not in pending_payment when a payment create call is attempted
PAYMENT_IDEMPOTENCY_CONFLICT402Idempotency conflictتعارض مفتاح الوحدةSame idempotency key with different parameters
PAYMENT_MISMATCH_RECORD_FAILED500Failed to record the payment mismatchفشل تسجيل عدم تطابق الدفعةWriting the reconciliation record and the failed-payment transition both failed on a mismatched webhook. Returned to the provider so it redelivers the event
ORDER_NOT_FOUND404Order not foundلم يتم العثور على الطلبThe payment request referenced an order that does not exist or is not owned by the caller

Idempotency conflict response:

json
{
  "error": {
    "code": "PAYMENT_IDEMPOTENCY_CONFLICT",
    "message": "A payment with this idempotency key already exists with different parameters",
    "details": {
      "existingPaymentId": "d5e6f7a8-b9c0-1234-defa-567890123456"
    }
  }
}

Operator Errors (502 / 504) ​

CodeStatusEnglish MessageArabic MessageCondition
OPERATOR_UNAVAILABLE502Operator API unavailableخدمات المشغل غير متاحةOperator API returned error
OPERATOR_TIMEOUT504Operator API timed outانتهت مهلة خدمات المشغلOperator API did not respond in time

Server Errors (5xx) ​

CodeStatusEnglish MessageCondition
INTERNAL_ERROR500An unexpected error occurredUnhandled server error
NOT_IMPLEMENTED501This endpoint is not yet implementedEndpoint stub (not yet built)

Rate Limiting (429) ​

When the rate limit (100 requests / 15 minutes / IP) is exceeded, or the separate auth-surface limit (AUTH_RATE_LIMIT_*, keyed per IP on the anonymous auth routes and per signed-in user on /auth/change-phone and /auth/verify-change-phone):

json
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests, please try again later"
  }
}

Response headers include:

HeaderDescription
X-RateLimit-LimitMaximum requests per window
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetWindow reset timestamp (Unix epoch)

Payment gateway and webhook errors ​

Moyasar and Paymob errors. All messages are bilingual.

CodeHTTPMeaning
PAYMENT_GATEWAY_DISABLED503New payments disabled
MOYASAR_NOT_CONFIGURED503Selected profile missing
MOYASAR_CREDENTIALS_REQUIRED422First setup requires all keys
MOYASAR_CREDENTIALS_UNREADABLE503Stored credentials cannot be decrypted
PAYMENT_RETURN_NOT_CONFIGURED503Moyasar only: the API could not tell its public address; set a custom HTTPS return URL or PUBLIC_API_URL
MOYASAR_UNAVAILABLE503Provider transport failure; retry
MOYASAR_REQUEST_FAILED404/502Provider lookup or credential failure
MOYASAR_RESPONSE_INVALID502Invalid provider response
MOYASAR_WEBHOOK_UNAUTHORIZED401Webhook secret mismatch
PAYMOB_NOT_CONFIGURED503Selected Paymob profile missing
PAYMOB_CREDENTIALS_REQUIRED422First setup requires public key, secret key and card integration ID, plus the API key for production
PAYMOB_CREDENTIAL_VERSION_UNKNOWN503Saved Paymob credential generation is unavailable
PAYMOB_CREDENTIALS_UNREADABLE503Stored credentials cannot be decrypted
PAYMOB_UNAVAILABLE503Provider transport failure; retry
PAYMOB_REQUEST_FAILED404/502Provider lookup or credential failure
PAYMOB_RESPONSE_INVALID502Invalid provider response
PAYMOB_WEBHOOK_INVALID400Malformed hmac or callback body
PAYMOB_WEBHOOK_UNAUTHORIZED401HMAC signature missing or mismatched while an HMAC secret is saved
PAYMOB_WEBHOOK_QUEUE_UNAVAILABLE503Queue handoff failed; Retry-After: 1
PAYMENT_CURRENCY_UNSUPPORTED422Gateway does not charge this currency (Paymob: SAR only)
PAYMENT_METHOD_UNAVAILABLE422Requested method has no saved Paymob integration
PAYMENT_ENVIRONMENT_MISMATCH409Event mode differs from saved payment
PAYMENT_ALREADY_STARTED409error.details contains owned paymentId and original idempotencyKey to resume
PAYMENT_ORDER_CHANGED409Refresh changed order total
PAYMENT_AMOUNT_UNSUPPORTED422Minimum charge is 100 minor units (1.00)
PAYMENT_ID_INVALID400Payment ID is not UUID
PAYMENT_NOT_FOUND404Owned gateway payment not found

SIM Limits (CITC) ​

Per CITC regulations, users are limited by identity type:

Identity TypeMax SIMsidentityType Value
Citizen10citizen
Resident2resident
Visitor1visitor

Internal documentation - Activation System