> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wristler.eu/llms.txt
> Use this file to discover all available pages before exploring further.

# The Wristler app

> What our own app may do that nothing else may, and why it signs in differently.

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.

> [card] [Build it](https://docs.wristler.eu/app/build.md)
>
> What to do, in the order to do it — the page an app developer works from.

> [card] [The catalogue](https://docs.wristler.eu/app/catalogue.md)
>
> What is for sale, from every seller.

> [card] [Messaging](https://docs.wristler.eu/app/messaging.md)
>
> Your threads as a buyer, or your shops' with `filter[side]=seller`; attachments, unread counts.

> [card] [Devices](https://docs.wristler.eu/app/devices.md)
>
> 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:

| | |
| - | - |
| `urn:wristler:native-login` | Email and password, with an MFA challenge when the account has it |
| `urn:wristler:social` | An Apple or Google ID token the platform SDK produced |
| `urn:wristler:guest` | Nobody yet: a token that reads only what the website shows everybody |

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

> [accordion] account:read, account:write
>
> 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.

> [accordion] account-security:write
>
> 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.

> [accordion] checkout:write
>
> 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.

> [accordion] catalogue:read
>
> 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.

> [accordion] content:read
>
> 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.

> [accordion] business:write
>
> 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.

> [accordion] messages:read, messages:write
>
> Somebody's correspondence with their buyers is a different thing to be trusted
> with than their stock list.

> [accordion] uploads:write
>
> 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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.