Skip to content
AXOWLDevelopers
Quickstart

User account (/me)

Self-service endpoints for the signed-in User (the account, across all their orgs — see Connected ID). Base path: /api/v1/users.

Method · RoutePurpose
POST /registration/emailRegister with email. Body: email, password, username?, phoneNumber?, countryCode?. 400 with message on failure.
POST /verification/emailConfirm the emailed token and start a session. Body: userId, token.
Method · RoutePurpose
GET /meThe signed-in account: name, email, isEmailVerified, firstName, lastName, countryIso, userId, organizations[], currentOrganization, credentialIds[], isCurrentSessionSealed, profile, and signatureSpec (the user’s designed personal seal, rendered across the approval flow). The answer is cached for 5 minutes per user; add any ?_t= query value to read fresh data.
GET /me/org-count{ count } — how many organizations the account belongs to.
PUT /me/profileUpdate the profile. Body (all optional): firstName, lastName, bio, location, websiteUrl, preferredLanguage, preferredCurrency, timeZone, profileImageUrl, countryIso. For an account that belongs to an organization, the request must come from a sealed device.
PUT /me/timezoneSet only the time zone. Body { timeZone } (2–64 characters).
PUT /me/languageSet only the display language. Body { language } (2–10 letters, digits or -). Returns { language, changed }.
POST /me/profile/photoUpload an avatar as multipart/form-data, field file. PNG, JPEG, WebP or GIF, at most 5 MB. Returns { url }. For an account that belongs to an organization, the request must come from a sealed device.
GET /me/phoneThe account’s phone number and whether it is verified.
POST /me/korean-identity/initiateStart Korean real-name verification. Body { purpose? }. The owner’s own verification (no purpose, or owner-kyc) is free; any other purpose inside an organization needs the Korean Package add-on, otherwise 402 with error = "ADDON_REQUIRED".
POST /me/korean-identity/finalizeFinish it. Body { identityVerificationId, purpose? }.
POST /api/v1/identity/kyc/startStart a Stripe Identity check (document + matching selfie). Body { returnUrl }; answers { url } (send the person there). Starting again clears the previous failure reason. 409 with error = "already_verified" once the account is verified.
GET /api/v1/identity/kyc/statusThe signed-in person’s own identity check: status (None · Pending · Verified · RequiresInput · Canceled), isVerified, reason (Stripe’s reason, only for RequiresInput), verifiedAt, canRestart (true until verified).
Method · RoutePurpose
GET /my-devicesList the account’s devices. Send the current device in X-Device-Id to have it marked.
DELETE /my-devices/{deviceId}Remove a device key (account-wide). 404 if unknown; 409 if removing it would lock the account out.
POST /my-devices/{deviceId}/claimClaim a device.
POST /my-devices/{deviceId}/lockLock or unlock a device. Body { locked }. 404 if unknown; 409 if it would lock the account out.
POST /orgs/{slug}/device-requestRequest device access in an org for the device of the current session.

Removing, claiming and locking a device must be done from a sealed device. These are the account-level view; the org-scoped device view is Devices, and the conceptual model is Devices.

The signed-in user deletes their own Axowl account. Deleting an account never deletes an organization: delete (or transfer) every organization you own and leave every other organization first. Public explanation for your users: axowl.com/account/delete.

Method · RoutePurpose
GET /me/account/deletionPreconditions: canRequest, status, hasPasskey, deletionRequestedAt, purgeAt, graceDays, blockers[{ code, orgSlug, orgName }], warnings[{ code, count }]. Blocker codes: OWNER_ORG, OWNER_ORG_PENDING_PURGE, MEMBER_ORG, ACCOUNT_STATUS. Warning APP_ENTITLEMENTS: paid app subscriptions linked to the account are not canceled by deletion.
POST /me/account/deletion/email-codeSend a 6-digit confirmation code to the account’s email (for accounts without a passkey). The code is valid for 10 minutes and allows five attempts. One send per minute, five per ten minutes (429 CODE_THROTTLED); 502 CODE_SEND_FAILED if the email could not be sent.
POST /me/account/deletionRequest deletion. Body { stepUpAssertion } (passkey seal) or { emailCode }. 409 DELETION_BLOCKED with blockers; 401 VERIFICATION_REQUIRED · STEPUP_FAILED · CODE_INVALID. On success every session and access token ends, and the answer carries requested, deletionRequestedAt, purgeAt and graceDays.
POST /api/auth/account-recovery/sendPublic. Body { email }. Sends a restore code if that account is pending deletion. The answer is { accepted: true } for any email.
POST /api/auth/account-recovery/confirmPublic. Body { email, code }. Restores the account ({ restored: true }); 401 CODE_INVALID otherwise.

While deletion is pending, every sign-in answers ACCOUNT_PENDING_DELETION. After 30 days the account is anonymized (the job runs hourly, so it happens within about an hour of the 30-day mark): name, email, phone number, passkeys, linked sign-in providers, trusted devices and identity-verification records are removed. Audit records stay in anonymized form. An email is sent when deletion is requested, when it is restored, and when it is completed.