OAuth 2.0
OAuth is how you build an app many Noé workspaces can connect to. Instead of asking every customer to paste an API key, you send them to Noé, they approve what you may access, and you receive tokens for their workspace.
Use an API key instead if you are integrating a single workspace you control — see Authentication.
| API key | OAuth | |
|---|---|---|
| Who creates the credential | The workspace owner, by hand | Your app, automatically |
| Scales to many customers | No | Yes |
| Credential lifetime | Until revoked | 10-minute access token + refresh token |
| Appears in the Noé Marketplace | No | Optional |
Before you start
You need an approved app. Register one in the developer console, request the scopes it needs, and submit it for review — the full sequence is in Registering an app.
From the console you will have:
- a client id (
noe_client_…), public - a client secret, shown once when you create it
- the redirect URIs you registered, matched exactly
The flow
Code
PKCE is mandatory, with S256 only. A stolen authorization code is useless
without the verifier, and there is no reason to support the weaker flow.
1. Send the customer to Noé
Generate a code_verifier (43–128 random characters), derive the challenge, and
redirect the customer's browser:
Code
<workspace> is your customer's Noé subdomain. Ask them for it before starting
the flow — the same way an integration asks for a Shopify shop or a Slack
workspace. A customer on a white-label domain uses that domain instead.
Deriving the challenge, in Node:
Code
Always send state, and check it when the customer comes back. It is what stops
an attacker from having their own authorization code delivered into your
customer's session.
2. They approve
Noé validates your request before rendering anything, so a wrong redirect_uri
or an unapproved scope shows the customer an error rather than a consent screen
for a request that cannot complete.
They see your app's name and logo, tick the scopes to grant, and may restrict the grant to a subset of their properties. Anything they untick, you do not get.
3. Noé redirects back
Code
If they refuse:
Code
4. Exchange the code
Within 5 minutes, and only once:
Code
Code
Store the refresh token encrypted, against the customer's workspace. Compare the
returned scope with what you asked for: the customer may have granted less.
Presenting the same code twice revokes every token for that grant. A replayed code is evidence it leaked, so the safe response is to invalidate everything it produced rather than to refuse just the second attempt. Exchange each code once.
5. Call the API
The access token is a bearer credential like any other:
Code
Everything else in these docs — pagination, filtering, idempotency, rate limits, webhooks — behaves identically whether you authenticate with a key or a token.
6. Refresh
Access tokens last 10 minutes. Refresh tokens last 60 days and rotate: each refresh returns a new one and invalidates the old.
Code
Persist the new refresh token before you use the new access token. Losing it means the customer has to re-authorise.
Reusing a rotated refresh token revokes every token for that grant, for the same reason as a replayed code. If two of your workers can refresh concurrently, take a lock per workspace — otherwise one will present a token the other has already rotated and disconnect the customer.
Disconnecting
Revoke a refresh token when a customer disconnects your app:
Code
This always returns 200, so it cannot be used to probe whether a token existed.
A customer can also revoke you from their side, at any time, in
Mon compte → API. When they do, your next call fails with grant_revoked —
treat that as a disconnection and stop polling.
Errors
Authorization-server errors use OAuth's own codes, not the data-API error format:
| Code | Meaning |
|---|---|
invalid_client | Wrong client id or secret, or the app is not approved |
unauthorized_client | The app is suspended or not approved |
invalid_redirect_uri | Does not exactly match a registered URI |
invalid_scope | None of the requested scopes are approved for your app |
invalid_grant | Code or refresh token unknown, expired, already used, or the workspace revoked you |
unsupported_grant_type | Only authorization_code and refresh_token |
On the data API, two token-specific codes join the usual set:
| Code | What to do |
|---|---|
token_expired | Refresh and retry |
token_revoked | Stop; the customer or Noé revoked this token |
Security expectations
These are the rules we hold apps to, and what a review checks:
- Never put the client secret in a browser, mobile binary or public repo. The exchange happens on your server.
- Register exact
redirect_urivalues, over HTTPS. Cleartext HTTP on a public host is rejected at review. - Store refresh tokens encrypted at rest.
- Request the narrowest scopes you need. Asking for
reservations:guest_readwithout a reason that survives review will slow your approval down. - Treat
grant_revokedas final rather than retrying it.

