Skip to main content
The app is the dashboard on a phone. A seller must see the same inventory in both, so a write from the app is a write from the seller, exactly as one from the website is — there is no app-only rule and no app-only ownership. What is different is how it signs in, and what only it may reach: the catalogue, the pages people read, messaging, somebody’s own account and the devices it pushes to.

Build it

What to do, in the order to do it — the page an app developer works from.

The catalogue

What is for sale, from every seller.

Messaging

Your threads as a buyer, or your shops’ with filter[side]=seller; attachments, unread counts.

Devices

Registering an install so it can be pushed to.

Why it does not use the authorization code flow

The code flow exists so a third party can act for somebody without ever seeing their password. Our own app is not a third party: it is the place people sign in. Sending them out to a browser to approve Wristler to Wristler would be a consent screen about ourselves — more steps, and a worse answer to “is this really Wristler asking”. So the app has three grants of its own: All three are refused outright for any client that is not first-party. The lock is the client type in our database, not a scope, so it cannot be widened by asking for more. There is no client secret: a secret shipped in a binary is out within the hour. The app is a public client and the lock is on our side.

The scopes only we may ask for

Somebody’s own account and everything under /v1/me: what they put aside, the searches they want to hear about, their address book, what they bought, the devices they read us on. A partner acts for a business, and a business has none of those.
The second factor and the recovery code. Its own scope because a token that may add a favourite should not thereby be able to take somebody’s second factor away — and every one of these asks for the password again on top.
Placing an order and paying for it. Not part of account:write: adding a favourite and paying twelve thousand euros are not the same permission. It moves money between two of our customers, through our escrow account and our fees, and there is no version of it a third party performs.
What is for sale, from every seller. Its own scope because it describes something a holder may do: it is the one thing here we could reasonably open up later, to a shop window or a comparison site, and that should be a decision about this scope alone.
The pages people read rather than buy from: filter pages, Insights articles, the help centre, the personal shoppers. A guest token carries it together with catalogue:read, and nothing else.
Changing a shop’s own details: its settings, its company details, whether it takes offers. Not inventory and not orders, so a client that syncs stock has no use for it.
Somebody’s correspondence with their buyers is a different thing to be trusted with than their stock list.
Sending us a file. Its own scope because uploading is its own thing — and a partner syncing stock gives a listing imageSources instead, which is the better trade for both of us.
None of them unlocks a secret: passwords and MFA material are never serialised at any scope, which is a stronger guarantee than a scope check.

The app may look at any seller’s page

GET /v1/sellers/{id} and the listings at GET /v1/sellers/{id}/watches are open to it for any seller, not only the sellers the person acts for, because the app shows a seller’s shop the way the website does. What comes back is narrowed: somebody else’s business gives you what is for sale and nothing more — no drafts, no sold listings, no prices that were never published. Everything else under /v1/sellers/{id}/… — orders, invoices, quick replies, blocked buyers, the transaction fee — and every write answers 404 for a business the person does not act for, as if it did not exist.

Checkout, and the two things not to build

Do not draw the payment sheet the SDK offers, and do not add anything up. Every method carries its own fee, and on a watch that is the difference between nothing and a few hundred euros — so the choice is a real one, made on numbers only the server knows. POST /v1/me/orders answers with every method its seller offers, in this language, each with its fee and the total it makes — and available: false with an unavailableReason for one that cannot be used for where the order ships. Show that list; post back the code of an available one. A sheet that picks the method itself cannot show those numbers, and refuses to confirm a payment whose amount is not the one it displayed. A price never goes in a request. What somebody pays comes from the watch, or from the offer a seller sent them — relationships.offer, a message id. The offer has to still be standing and still be the newest; expired is expired, and no amount in a body would be something we could check. Three of the methods need nothing native at all. A bank transfer is confirmed by the server and answers with an IBAN and a reference, which is a screen of text — and it is the method most of this money arrives through. iDEAL, Bancontact, EPS and Przelewy24 are confirmed by the server too if you name a returnUrl on the app’s own scheme: the answer carries a URL to open, and the buyer comes back to the app. A card is never confirmed by us, because the card details are not ours to receive: with a returnUrl the answer carries the URL of Stripe’s hosted card page, and without one the client confirms it with Stripe’s SDK and the clientSecret. Apple Pay and Google Pay come with the card, at the card’s service costs: Stripe’s hosted card page shows them where the buyer’s device has one, as long as the environment has not switched them off (STRIPE_CARD_PAYMENT_METHOD_CONFIGURATION). Nothing in the API changes for them — paymentMethod: "card" either way.