Account linking and merging API (token exchange, /me/identities, /account/merge)
This guide covers the token exchange that signs an app in with an ID token from LINE, Apple or Google, the /api/v1/me/identities endpoints for adding and removing sign-in methods, and /api/v1/account/merge for merging two accounts. These are the same endpoints the ReceiptRoller iOS and Android apps and the LINE mini app use.
Token exchange (sign in with an ID token)
An RFC 8693 token exchange turns the ID token your platform SDK returned into a ReceiptRoller access token, without a browser round trip through /oauth/authorize. Only client IDs allowed to use token exchange can call it — contact support if you need it.
POST https://receiptroller.io/api/v1/auth/token
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&client_id={client_id}
&subject_token={ID token}
&subject_token_type=urn:ietf:params:oauth:token-type:id_token
&subject_issuer=line # line | apple | google
&organization_id={business account id} # only for a store's own LINE channel
&nonce={nonce} # if you gave the SDK a nonce
&create_if_missing=true # create an account when none uses this identity
A successful response is the usual token plus issued_token_type and account_created (true when this call created the account).
| Status | error | Meaning |
|---|---|---|
| 400 | invalid_request | subject_token, subject_token_type or subject_issuer missing or wrong |
| 401 | invalid_client | The client isn't allowed to use token exchange |
| 400 | invalid_grant | The ID token couldn't be verified (expired, another channel, nonce mismatch…) |
| 404 | identity_not_found | No account uses this identity. When an account with the same verified email exists, candidate_account (masked_email, sign_in_methods) is included — ask the person to sign in to it and link from there |
| 429 | rate_limit_exceeded | Too many accounts created from the same address |
Proof
Adding or removing a sign-in method and merging also need proof that the caller owns the account, on top of the token.
{ "type": "password", "password": "..." }
{ "type": "id_token", "issuer": "line", "idToken": "...", "nonce": null, "organizationId": null }
- Use one of the account's own sign-in methods: its password, or a LINE, Apple or Google identity connected to it
- An account with no sign-in method yet (started anonymously in the phone app) can omit
proof - Missing proof answers 403
reauth_required, wrong proof 403proof_rejected; both list what's accepted inacceptedMethods
Sign-in methods: /api/v1/me/identities
GET /api/v1/me/identities:email,hasPasswordandidentities(id,provider,scope,linkedAt,lastUsedAt,canRemove)POST /api/v1/me/identities:{ issuer, idToken, nonce?, organizationId?, proof }adds a LINE, Apple or Google identity. If another account already uses it, the answer is 409identity_belongs_to_other_accountwith amergeTicketDELETE /api/v1/me/identities/{id}: body{ proof }, using a method other than the one being removed. The last method answers 409last_sign_in_methodPOST /api/v1/me/identities/password/code:{ email, proof }sends a code to the email (202). Codes per hour are limited (429rate_limit_exceeded)POST /api/v1/me/identities/password:{ email, password, code, proof }. If another account uses that email, 409identity_belongs_to_other_accountwith amergeTicket; a password that fails the rules is 400password_rejected
Merging accounts: /api/v1/account/merge
A merge needs a mergeTicket (valid 10 minutes) that carries the proof of the other account. A ticket only works with the token of the account that obtained it.
- Prove the other account and get a ticket:
POST /api/v1/account/merge/tickets
Send the code with{ "proof": {...}, "other": { "type": "email_code", "email": "...", "code": "..." } } // type is id_token, password or email_codePOST /api/v1/account/merge/email-code({ email, proof }). It answers 202 whether or not an account uses the address. - Preview:
POST /api/v1/account/merge/preview({ mergeTicket, keep? }).keepiscurrent(keep the account signed in) orother; omitted,suggestedKeepis used — the account the person made themselves, or the older of two of a kind - Merge:
POST /api/v1/account/merge({ mergeTicket, keep, confirm: true }). 202 returns themergeIdandtokensfor the account kept — switch to them. Sending the same ticket again returns the same merge - Progress:
GET /api/v1/account/merge/{mergeId}.statusisrunning,completed,failedorundone. WhencanResumeis true,POST /api/v1/account/merge/{mergeId}/resumepicks it up again
POST /api/v1/account/merge/suggestions ({ proof }) returns another account that is probably the person's. same_line is an account with the same LINE id; LINE proves both, so it comes with a mergeTicket. same_email is an account whose email matches the one in the LINE ID token; it has no ticket and must be proven as usual.
| Status | error | Meaning |
|---|---|---|
| 400 | invalid_credentials / invalid_code / invalid_grant | The other account couldn't be proven (an unknown email answers the same way) |
| 400 | same_account | The other account is the one signed in |
| 400 | invalid_ticket / confirm_required | Ticket expired or invalid / confirm isn't true |
| 403 | ticket_not_yours | Another account obtained the ticket |
| 404 | other_account_not_found / merge_not_found | The other account or the merge doesn't exist |
| 409 | already_merged / not_resumable | Already merged / can't be resumed |
| 429 | locked_out / rate_limit_exceeded | Too many wrong passwords / too many codes |
Implementation notes
- Branch on the
errorcode. The wording oferrorDescriptioncan change - Adding or removing a sign-in method and merging are notified to the person by email and LINE; your app doesn't need to send its own notice
- A merge can't be undone from an app. Within 30 days, support may be able to separate the accounts again
Related
-
Using the Store Information APIA guide to the REST API for fetching and updating a store's basic information (store name, store type, contact details, and address). Lets you implement a store information editing screen from token-authenticated clients such as staff apps.
-
Setting the redirect URLExplains the role of the redirect URI (callback URL) you set in the ReceiptRoller developer portal, the registration rules, using development vs. production environments, and common errors and how to handle them.
-
Using the Orders / OMS APIA guide to CRUD operations on the orders under a business account using ReceiptRoller's Orders / OMS API (/api/v1/orders). Covers creating, updating, transitioning status (confirm, process, cancel), and deleting orders, plus the flow for Android / iOS apps and server integrations.
-
Using the Business Hours APIA guide to ReceiptRoller's Business Hours API (/api/v1/stores/{storeId}/business-hours). Covers retrieving and updating per-day business hours, registering special business days (temporary closures and hour changes), configuring a store's workable hours (the upper bound for shift creation), and determining whether the store is currently open.
-
Using the Products APIA guide to CRUD operations on products under a business account using the ReceiptRoller Products API (/api/v1/products). An introductory article for reading and writing the PIM (product master) from an Android/iOS app or server integration.