Skip to content

Profile API ​

User profile view, update, PDPL data portability export, and account deletion.

Per T-06-07: No endpoint accepts userId from the request body. All operations are scoped to the authenticated user's JWT.


GET /profile ​

Return the authenticated user's profile aggregate, including activated SIM count, eSIM-store currency preference, and notification locale.

Requires authentication.

Example Request ​

GET /profile
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Response ​

200 OK

json
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "phone": "+966501234567",
  "email": "ahmed@example.com",
  "name": "Ahmed Al-Rashid",
  "identityType": "citizen",
  "identityTypeLocked": false,
  "appRole": "end_user",
  "preferredCurrency": "SAR",
  "locale": "ar",
  "activatedSimCount": 3,
  "createdAt": "2026-04-30T12:00:00.000Z"
}

For email-only users, phone is null. For phone-only users, email is null. identityTypeLocked is true while an identity check for this customer is waiting, for good once one has completed, and always for a company employee whose type comes from the roster. It turns false again when a waiting check fails, is rejected, errors or expires. false means identityType can still be changed. preferredCurrency is USD, SAR, AED, EUR, GBP, EGP, or null. null means store browse uses the explicit request currency first, then the USD base default. On store checkout it is the fallback order currency when the request sends none. locale is en or ar, or null when unset. Registration fills it from the request's Accept-Language header, and the profile update changes it later. It selects the language of notification inbox copy, notification SMS text, and custom notification emails. null renders in English. See notification rendering.

Error Responses ​

CodeStatusCondition
AUTH_REQUIRED401Missing or invalid token

PUT /profile ​

Update allowed profile fields. name, email, preferredCurrency, locale, and identityType can be updated here. Phone changes require POST /auth/change-phone with OTP verification.

Requires authentication.

Per T-06-08: Zod validation and whitelist — only name, email, preferredCurrency, locale, and identityType are accepted.

The identity type and the other fields are saved in one transaction, so the update fully happens or not at all. A refused type change leaves the rest of the request untouched. It is refused with 409 IDENTITY_TYPE_LOCKED while an identity check for this customer is waiting or once one has completed, and with 409 IDENTITY_TYPE_COMPANY_MANAGED for a company employee, whose type comes from the company's roster. Sending the type already stored is never refused, locked or not, and records nothing. A change is audited as user.identity_type_changed with the customer as actor, the old and new type, and source: "profile_update".

email follows the same rules as PUT /auth/me: a new email rewrites emailHash so it becomes the login email as typed, an email another account owns is refused with 409 AUTH_EMAIL_EXISTS, null removes the email and is refused with 409 AUTH_LAST_IDENTIFIER when the account has no phone, and a company admin's new email fills the company's contact email while that is still empty and the company is not archived. A change to name or email is audited as profile.updated with the names of the changed fields only, never their values. preferredCurrency and locale are not audited, and a save that changes neither name nor email writes no audit row.

Request Body ​

FieldTypeRequiredDescription
namestringNoUpdated name (1–255 characters)
emailstring | nullNoUpdated email address. Send null to remove it; refused when the account has no phone
identityTypecitizen | resident | visitorNoNew identity type. Refused once locked
preferredCurrencyUSD | SAR | AED | EUR | GBP | EGP | nullNoPersistent eSIM-store presentment preference. Send null to clear it
localeen | ar | nullNoLanguage for notification inbox copy, notification SMS text, and custom notification emails. Send null to clear it, which renders in English

Example Request ​

PUT /profile
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
json
{
  "name": "Ahmed Mohammed Al-Rashid",
  "email": "ahmed.new@example.com",
  "preferredCurrency": "SAR",
  "locale": "ar"
}

Partial update (currency clear only):

json
{
  "preferredCurrency": null
}

Response ​

200 OK

json
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "phone": "+966501234567",
  "email": "ahmed.new@example.com",
  "name": "Ahmed Mohammed Al-Rashid",
  "identityType": "citizen",
  "identityTypeLocked": false,
  "appRole": "end_user",
  "preferredCurrency": "SAR",
  "locale": "ar",
  "activatedSimCount": 3,
  "createdAt": "2026-04-30T12:00:00.000Z"
}

email is null after a request that removed it.

Error Responses ​

CodeStatusCondition
AUTH_REQUIRED401Missing or invalid token
VALIDATION_ERROR400Invalid name, email, currency, locale, or identityType
IDENTITY_TYPE_LOCKED409An identity check is waiting or has already completed
IDENTITY_TYPE_COMPANY_MANAGED409The account is a company employee
AUTH_EMAIL_EXISTS409Another account already uses the email
AUTH_LAST_IDENTIFIER409email: null on an account with no phone, which would remove its only sign-in identifier

GET /profile/export ​

PDPL (Personal Data Protection Law) data portability export. Returns all user data in a structured format with compliance metadata.

Requires authentication.

Per T-06-07: Export is scoped to the JWT owner only. No cross-user lookup path exists.

The export includes notifications and notificationOptOuts alongside profile, activation, order, payment, and store-order data. Notification history includes archived, expired, and scrubbed rows. It returns stored parameters, not rendered inbox text.

Export EntryFields
notifications[]id, category, eventKey, templateKey, params, severity, createdAt, readAt, seenAt, archivedAt, expiresAt, deliveries
notifications[].deliveries[]channel, outcome, provider, attempts, sentAt, deliveredAt, failedAt, suppressionReason
notificationOptOuts[]channel, createdAt

Scrubbed notification parameters are {}. The export excludes batch requests, callback receipts, provider message IDs, error text, dedupe keys, and idempotency keys. See notification retention and export.

Query Parameters ​

ParameterTypeDefaultDescription
formatstringjsonExport format (currently only json supported)

Example Request ​

GET /profile/export?format=json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Response ​

200 OK

json
{
  "format": "json",
  "exportedAt": "2026-05-05T08:30:00.000Z",
  "data": {
    "profile": {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "phone": "+966501234567",
      "email": "ahmed@example.com",
      "name": "Ahmed Al-Rashid",
      "identityType": "citizen",
      "appRole": "end_user",
      "preferredCurrency": "SAR",
      "locale": "ar",
      "createdAt": "2026-04-30T12:00:00.000Z"
    },
    "activations": [
      {
        "id": "c3d4e5f6-a7b8-9012-cdef-345678901234",
        "iccid": "LPA:1$SMDP.DEV.SKYTE.SA$***",
        "simType": "esim",
        "smdpAddress": "SMDP.DEV.SKYTE.SA",
        "matchingId": "SKYTE-DEV-0001",
        "activationCodeRaw": "LPA:1$SMDP.DEV.SKYTE.SA$SKYTE-DEV-0001",
        "status": "activated",
        "createdAt": "2026-05-05T08:30:00.000Z"
      }
    ],
    "orders": [],
    "payments": [],
    "storeOrders": [
      {
        "orderId": "550e8400-e29b-41d4-a716-44665544e401",
        "orderStatus": "completed",
        "product": {
          "id": "550e8400-e29b-41d4-a716-44665544e101",
          "nameEn": "Saudi Arabia 3 GB",
          "nameAr": "السعودية 3 جيجابايت"
        },
        "presentmentCurrency": "SAR",
        "presentmentAmount": "18.71",
        "fxRateUsed": "3.75",
        "retailUsdSnapshot": "4.99",
        "smdpAddress": "SMDP.STORE.DEV.SKYTE.SA",
        "matchingId": "STORE-8F3A1C2B",
        "activationCodeRaw": "1$SMDP.STORE.DEV.SKYTE.SA$STORE-8F3A1C2B",
        "iccid": "8900000000000000001",
        "issuedAt": "2026-05-05T08:32:00.000Z",
        "createdAt": "2026-05-05T08:30:00.000Z"
      }
    ],
    "notifications": [],
    "notificationOptOuts": []
  },
  "metadata": {
    "pdplCompliant": true,
    "requestId": "export-a1b2c3d4-20260505",
    "retentionPolicyDays": 365
  }
}

Error Responses ​

CodeStatusCondition
AUTH_REQUIRED401Missing or invalid token

DELETE /profile ​

Request account deletion. This is a soft delete — the user record is marked inactive with a deletionRequestedAt timestamp. Data is retained during the mandatory PDPL retention period, then purged.

Requires authentication. Customers (end_user) only; other roles get 403 AUTH_FORBIDDEN.

The request also stamps the retention lifecycle on the caller's records. Activations and store orders both get deletionRequestedAt, and store orders additionally get retainUntil set to the request time plus the 365-day retention policy. Once that date passes, POST /compliance/retention/sweep scrubs the store order's eSIM payload — smdpAddress, matchingId, activationCodeRaw and iccid are nulled and deletedAt is stamped. The order row itself is kept because the purchase and its payment are a financial record.

Existing notifications also receive deletionRequestedAt and the 365-day retainUntil deadline, and notifications recorded for the account later receive the same two stamps from the request's audit event. The flag, the stamps and the audit event are written in one transaction. After that deadline, the retention sweep clears their params and stamps deletedAt. Notification rows, dedupe keys, and delivery history remain. Notification expiry or archiving alone does not trigger scrubbing. Batch requests, callback receipts, and channel opt-outs are not cleared by this sweep.

Per T-06-09: Auditable with PROFILE_DELETION_REQUESTED event. Per T-06-07: Uses JWT context userId only.

Request Body (Optional) ​

FieldTypeRequiredDescription
reasonstringNoOptional reason for deletion

Example Request ​

DELETE /profile
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
json
{
  "reason": "No longer need the service"
}

Without body:

DELETE /profile
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Response ​

200 OK

json
{
  "deleted": true,
  "purgedAt": null,
  "message": "Account deletion requested. Your data will be retained during the mandatory retention period, then permanently deleted."
}

Arabic response (Accept-Language: ar):

json
{
  "deleted": true,
  "purgedAt": null,
  "message": "تم طلب حذف الحساب. ستُحتفظ بياناتك خلال فترة الاحتفاظ الإلزامية ثم تُحذف نهائيًا."
}

purgedAt is null while data retention is pending. Once fully purged, purgedAt contains the timestamp.

Error Responses ​

CodeStatusCondition
AUTH_REQUIRED401Missing or invalid token

Internal documentation - Activation System