---
name: xero-oauth-app
description: Creates or signs in to a Xero developer account and registers a Xero OAuth2 app to obtain a client ID and client secret — with redirect URIs, the accounting/payroll/files scope families, the tenant (organisation) indirection, the tiered connection ceiling and certification gates, and a safe credential handoff. Use when asked to get Xero OAuth credentials, set up a Xero developer account, create a Xero app or custom connection, rotate a Xero client secret, or fix a Xero error like `invalid_grant`, `AuthenticationUnsuccessful`, an invalid-scope authorize screen, or a customer who cannot connect because the app hit its connection limit. For any other vendor's developer portal, use that vendor's skill instead.
---

# Xero OAuth2 App Registration

Get a working Xero OAuth2 client — a Xero account, an app in **My Apps**, redirect URIs, scopes, a client ID and secret —
for a platform that connects many customers' Xero organisations.

Two things about Xero decide whether this works, and neither is on the registration form.

**A Xero access token is not bound to an organisation.** One token can reach several organisations, and every API call
must carry a tenant id that you fetch from a separate endpoint after the flow completes. A connector that assumes "one
connection, one organisation" silently syncs the wrong org, or one of several. That is §7.

**Your app has a hard connection ceiling, and it is small.** Since 2 March 2026 Xero prices apps in tiers, and a new app
starts on the free Starter tier with **5 connected organisations**. Customer number six cannot connect until someone
pays, and getting past 1,000 requires certification plus a security self-assessment. That is §8.

## Inputs to collect before you start

Ask in one batch. Never invent.

| Input | Notes |
| --- | --- |
| **App name**, company URL, privacy policy URL | The name cannot contain the word "Xero" (§3) |
| **Xero account email** | A free Xero account owns the app; the demo company is the test org (§2) |
| **Redirect URIs** | Every callback host your platform serves (§4) |
| **Scope set** | Exactly what the connector calls, plus `offline_access` (§5) |
| **How many customer organisations** this app must serve | Decides the tier, the cost and whether certification is in scope (§8) |
| **Who owns the tier/billing decision** | Moving off Starter needs a card on file — a commercial call, not yours (§8) |
| **Does the client store the rotated refresh token?** | Blocking — a client that does not will fail on the second refresh (§9) |
| **New app or an edit to an existing one?** | New credentials orphan every existing customer connection (§1) |

## Quick Start

1. Confirm a **new app** is needed — existing connections are bound to the current client ID (§1).
2. Sign in to, or create, a free Xero account and enable the demo company (§2).
3. Create the app in **My Apps**, choosing the right integration type (§3).
4. Add **every** redirect URI, exactly (§4).
5. Select the scopes the connector calls, plus `offline_access` (§5).
6. Capture the client ID and generate the client secret (§6).
7. Work through the tenant indirection before writing any client code (§7).
8. Establish the connection ceiling and who pays for the tier the roadmap needs (§8).
9. Confirm the client handles 30-minute access tokens and rotating 60-day refresh tokens (§9).
10. Verify with the demo company and a second organisation, then hand the credentials over (§11, §12).

## Platform state (verified 2026-09-20 — re-verify before trusting)

Xero changed its commercial model and its scope model in the same six months. Both land on this task.

- **Tiered pricing took effect 2 March 2026**, replacing the old revenue-share model. Five tiers, priced on connections
  and API egress: **Starter 5 connections (free)**, **Core 50 ($35 AUD/month)**, **Plus 1,000 ($245)**,
  **Advanced 10,000 ($1,445)**, **Enterprise unlimited (price on application)**. Plus and above require **app
  certification**; Advanced and above also require a **security assessment**, initial and annual. App Store listing
  needs Plus or higher. See §8.
- **The docs contradict themselves on the connection limit.** The limits, tenants and pricing pages say Starter starts
  at 5 and Core lifts it to 50. The OAuth overview, the auth-flow page and the getting-started guide still say
  "25 tenants". **Treat 5 as the planning number**, and confirm against the tier shown on the app's own page in the
  developer portal — that is the only authoritative figure for a given app.
- **Granular scopes replaced the broad ones from 2 March 2026.** `accounting.transactions`, `accounting.transactions.read`
  and `accounting.reports.read` are marked Deprecated and split into per-resource scopes. Apps created **on or after
  2 March 2026 use granular scopes**; older apps can keep broad scopes **until September 2027**. A new app registered
  today is a granular-scope app, so an authorize URL still carrying the deprecated broad scopes is the first thing to
  check when consent fails (§5).
- **Premium endpoints are tier-gated.** The **Journals** endpoint (general ledger) and the **Xero Practice Manager
  (XPM)** API are Advanced-tier features requiring a security assessment and use-case approval. **Bulk Connections**
  (one user connecting many organisations in a single flow) is likewise Advanced and approval-gated. Manual journals
  are *not* affected — a different endpoint, available in every tier.
- **Rate limits now vary by tier**: 1,000 calls/day per organisation on Starter, 5,000 on Core and above. The rate-limit
  FAQ still prints a flat 5,000; the limits page is the current one (§10).
- **The developer terms prohibit using Xero API data to train AI/ML models** (from 4 December 2025 for developers who
  registered on or after that date, from 2 March 2026 for everyone else). If the product does anything of the kind,
  stop and escalate — that is a legal question, not a registration step.

If the portal does not look like this, stop and report what you actually see rather than clicking on.

## 1. Decide: reuse the existing app, or register a new one

A new app means a **new client ID, and every existing customer connection is bound to the old one** — every customer
would have to reconnect. Reuse the existing app for: adding a redirect URI, adding or migrating a scope, rotating a
compromised secret, or diagnosing a failure.

Register a **new** app only when the user explicitly wants one: a replacement for a compromised app, a separate app for
a different product, or a deliberate test app. Xero itself recommends a throwaway test app for trying granular scopes
against the demo company. Note that connections and API volume are metered **per app** and cannot be shared between
apps you own — a second app starts again on Starter with its own 5 connections. Say which path you are taking first.

## 2. Account: sign in or sign up

- **Xero account** — free, at `https://www.xero.com/signup/developers/`. Any Xero account can own apps; there is no
  separate developer registration.
- **Demo company** (recommended test organisation) — enable it from `https://my.xero.com/`. Pre-populated with data,
  resettable at any time, can switch country (which resets it), **auto-resets 28 days after creation**, and you must
  reconnect your app after each reset. Payroll is enabled where the chosen country supports it.
- **Trial organisation** — 30 days before billing details are required, no sample data, cannot be reset or switch
  country, but other users can be invited.
- **Who may authorize** — the connecting user must be **Standard, Adviser or Administrator with the Connected Apps
  permission**. Reporting endpoints additionally need the user to have **Reports** access; Payroll endpoints need
  **Payroll Admin**. Scopes never exceed what the user may do.

Hand control back to the user for anything only they can do: email verification, two-step authentication, accepting
terms, adding a payment method, or requesting a tier upgrade. Do not retry a blocked step in a loop.

**If this session has no browser automation** (the usual case for a CLI or cloud run), do not pretend to click. Hand the
user an exact, ordered click path with the literal values to paste (§4 redirect URIs, §5 scope list), then continue once
they report back with the client ID.

## 3. Create the app

Go to **My Apps** — `https://developer.xero.com/app/manage` — and click **New App**. The modal asks for a name, a
company or application URL, and an **integration type**. The type is the choice that matters:

| Integration type | Grant | Use it when |
| --- | --- | --- |
| **Web app** | Authorization code | A server that can keep a client secret — **this is what a multi-tenant connector needs** |
| **Mobile or desktop app** | Authorization code with PKCE | Native clients that cannot hold a secret; no client secret is issued. Single-page apps are not supported |
| **Custom connection** | Client credentials | A machine-to-machine integration with **exactly one** organisation, paid for by that organisation, AU/NZ/UK/US only |

Notes that cost a review cycle:

- The app name must be the go-to-market name of the product and **cannot contain the word "Xero"**.
- A Custom Connection can never serve a second organisation, so it is the wrong shape for a connector — but it is also
  the right answer when a customer wants one bespoke org wired up without a public app. Custom Connections do not count
  against the "two uncertified apps per organisation" limit (§8).
- Xero's certification checkpoints expect the connect experience to be a **full-page redirect**, not a pop-up, and
  expect your UI to show the connected organisation's name and a working disconnect button. Build for that now if
  listing is anywhere on the roadmap (§8).
- Never ask a user for their Xero login, and never expose the client ID or secret in client-side code.

## 4. Redirect URIs

Add **every** callback host your platform serves. Xero allows **up to 50 redirect URIs per app**, matched exactly.
For Unified.to these are one per data center; confirm the current list with the platform owner rather than assuming:

```
https://api.unified.to/oauth/code          # us (default)
https://api-eu.unified.to/oauth/code       # eu
https://api-au.unified.to/oauth/code       # au
https://api-dev.unified.to/oauth/code      # dev
```

Rules:

- **HTTPS only**, with one exception: `http://localhost` is allowed for testing. **`http://127.0.0.1` is not accepted.**
- **No wildcards.** Xero follows RFC 6749 §3.1.2 — an absolute URI, matched exactly.
- A `redirect_uri` that is not registered does not fail politely at the callback: the user sees a Xero **500 "Sorry,
  something went wrong" screen** at the *start* of the flow. Same screen for an invalid scope or a wrong client ID, so
  read the message on it before guessing.
- The `redirect_uri` sent at the token exchange must match the one used to get the code, or the exchange returns
  `unauthorized_client`.

## 5. Scopes

Scopes are **space-delimited** in the authorize URL. The families:

| Family | Examples | Notes |
| --- | --- | --- |
| Identity | `openid`, `profile`, `email` | Sign-in only. No organisation data, and no connection is created |
| Offline | `offline_access` | **Mandatory** for a refresh token, and required for certification |
| Accounting (granular) | `accounting.invoices[.read]`, `accounting.payments[.read]`, `accounting.banktransactions[.read]`, `accounting.manualjournals[.read]` | Replaced the broad `accounting.transactions[.read]` |
| Accounting (other) | `accounting.settings[.read]`, `accounting.contacts[.read]`, `accounting.attachments[.read]`, `accounting.budgets.read`, `accounting.journals.read` | `accounting.journals.read` is the Advanced-tier general ledger (§8) |
| Reports | `accounting.reports.balancesheet.read`, `.profitandloss.read`, `.trialbalance.read`, `.aged.read`, `.banksummary.read`, `.budgetsummary.read`, `.executivesummary.read`, `.taxreports.read` | Replaced the broad `accounting.reports.read`; the user also needs Reports access |
| Payroll | `payroll.employees[.read]`, `payroll.settings[.read]`, `payroll.payruns[.read]`, `payroll.payslip[.read]`, `payroll.timesheets[.read]` | Same strings across the AU, UK and NZ payroll APIs; the user must be a Payroll Admin |
| Files / Assets / Projects | `files[.read]`, `assets[.read]`, `projects[.read]` | Note that invoice and contact *attachments* are `accounting.attachments`, not `files` |
| Certification-gated | `paymentservices`, `bankfeeds`, `finance.*.read`, `practicemanager.*` | Only after additional certification, and sometimes a commercial agreement |
| Non-tenanted | `app.connections`, `marketplace.billing` | Client-credentials grant only (§7) |

Four ways scopes fail that look like the portal being broken:

1. **Scopes are additive and cannot be narrowed.** Each authorization adds to what the user already consented to; the
   only way to reduce a token's scopes is to revoke it and start again.
2. **One tenant type per flow.** Requesting an accounting scope alongside a Practice Manager scope errors for the user.
   Multiple tenant types means multiple separate authorization flows.
3. **A missing granular scope returns 401 with `WWW-Authenticate: insufficient_scope`**, not a 403. Catch it and prompt
   the user to reconnect rather than logging it as a server error.
4. **Migrating scopes forces re-consent.** Existing tokens do not gain granular scopes; users must go through the flow
   again. Xero's suggested migration is to change the authorize link now and let re-authorizations drift users over
   before September 2027, then force the stragglers by deleting their connection.

Request the narrow set, and say in your summary what you chose and why. Unexplained or ambiguous scopes are a
certification finding.

**Product fact.** As of 2026-09-20, Unified.to's Xero connector requests a per-object scope set rather than one blanket
list: the identity leg asks for `openid`, `profile` and `email`, and each unified object maps to the narrowest scopes
it needs — drawn from `accounting.settings` / `accounting.settings.read`, `accounting.invoices` / `.read`,
`accounting.contacts` / `.read`, `accounting.payments` / `.read`, `accounting.banktransactions` / `.read`,
`accounting.manualjournals` / `.read`, `accounting.attachments` / `.read`, the three report scopes
`accounting.reports.balancesheet.read`, `accounting.reports.profitandloss.read` and
`accounting.reports.trialbalance.read`, `projects` / `projects.read`, `payroll.employees.read` and
`payroll.settings.read` — with **`offline_access` appended to every one**. Two objects still request the **deprecated
broad `accounting.transactions` / `accounting.transactions.read`**, and aged-payable/receivable reports are computed
from invoice and transaction reads instead of `accounting.reports.aged.read`. The connector does **not** use PKCE for
Xero, exchanges the code with HTTP Basic auth over a form POST, sends the client id on refresh, treats a connection as
valid for about 60 days (tracking the refresh token, not the 30-minute access token), and has the revocation endpoint
wired up. It is also configured to use Unified.to's shared OAuth credentials by default. Confirm all of this with the
connector's owner before you register anything — those broad scopes in particular are the detail most likely to have
moved.

## 6. Capture the credentials

From **My Apps** → your app → **Configuration**:

- **Client ID** — visible on the app page.
- **Client secret** — click **Generate a secret**. It is shown **once**; if it is lost, generate a new one. Web apps get
  a secret, PKCE apps never do.
- **Endpoints** — authorize `https://login.xero.com/identity/connect/authorize`, token
  `https://identity.xero.com/connect/token`, revocation `https://identity.xero.com/connect/revocation`, connections
  `https://api.xero.com/connections`, API base `https://api.xero.com/api.xro/2.0/`. The OpenID Connect discovery
  document is at `https://identity.xero.com/.well-known/openid-configuration` and the JWKS at that path plus `/jwks`.
- **Environment** — there is no separate sandbox host. Production and test both run against the same endpoints; the
  demo company is the "sandbox" (§2).

Both the token exchange and the refresh authenticate with `Authorization: Basic base64(client_id + ":" + client_secret)`
over `application/x-www-form-urlencoded`. PKCE apps send the same header with an empty secret — the trailing colon still
matters.

Rotating the secret breaks every token exchange and refresh until the new value is deployed; issued access tokens keep
working until they expire. Never rotate without explicit go-ahead and a cutover plan.

Report the secret once so the user can paste it into their secret store, say plainly that it is now in the transcript
and can be regenerated, then move on.

## 7. The tenant indirection — one token, many organisations

This is the part that a connector gets wrong, and nothing in the registration form warns you about it.

A Xero access token represents **a user**, not an organisation. Immediately after the exchange you must call the
connections endpoint and pick a tenant:

```
GET https://api.xero.com/connections
Authorization: Bearer <access_token>
```

Each element carries `id` (the connection id), `tenantId`, `tenantType` (`ORGANISATION`, `PRACTICEMANAGER` or
`PRACTICE`), `tenantName`, `authEventId`, `createdDateUtc` and `updatedDateUtc`. Every subsequent API call then needs
both headers:

```
Authorization: Bearer <access_token>
xero-tenant-id: <tenantId>
```

What follows from that:

- **One authorization can return several organisations**, and the list also includes organisations the user connected
  in *earlier* flows — all reachable with the newest token. Filter with `?authEventId=<authentication_event_id from the
  decoded access token JWT>` to see only what was just authorized. The docs note the last-connected tenant's
  `authEventId` matches the token's claim.
- **`tenantId` is not `connectionId`.** `DELETE https://api.xero.com/connections/{connectionId}` (204, empty body)
  removes a connection and takes the *connection* id. Deleting a connection does not invalidate the token; revoking the
  token does not tidy your own records.
- **A connection is a unique user-tenant pair.** Three users connecting the same organisation is one connection against
  your ceiling; one user connecting three organisations is three.
- **Connections never expire on their own.** They persist regardless of token expiry or API inactivity, and disappear
  only when the user disconnects from `https://apps.xero.com/connected`, loses permission in the tenant, you DELETE the
  connection, the token is revoked, the tenant is deleted, or you delete the app. Since you now pay by connection,
  abandoned trials cost money — Xero documents a cleanup routine for exactly this.
- **Cross-tenant contamination is your problem.** Xero's own guidance calls out shared caches, static variables and
  connection pools as the failure mode, and certification reviews multi-organisation handling if you offer it.
- **If you lose the user's token** you can still manage connections with the client-credentials grant and the
  `app.connections` scope — available to web (code-grant) apps only, and the GET form requires a tenant id or user id
  header to filter by.
- The `Organisation` endpoint is the cheapest way to identify what you just connected: name, `ShortCode` (for deep
  links), `CountryCode`, `BaseCurrency`, `Class` (spot trials) and `IsDemoCompany`.

**Product fact.** As of 2026-09-20, Unified.to's Xero connector resolves the tenant once, right after authorization: it
calls the connections endpoint filtered by the token's authentication event, falls back to an unfiltered listing if that
returns nothing, **stores the first organisation in the list against the connection**, and sends that id as the tenant
header on every list, get, create, update, delete and passthrough call. It also reads the organisation endpoint once to
cache the country and product version. The practical consequences: a customer who authorizes three organisations in one
flow gets **one** of them synced, the other two still count against the app's connection ceiling, and which one is
picked depends on Xero's ordering. Modelling several organisations means several connections in Unified.to, one per org.
Confirm the current behaviour with the connector's owner before you promise a customer multi-org behaviour.

## 8. The connection ceiling and certification

Your app cannot serve more organisations than its tier allows, and the free tier is very small.

| Tier | Connections | Monthly fee | Gate |
| --- | --- | --- | --- |
| Starter | 5 | free | none — where every new app begins |
| Core | 50 | $35 AUD | payment method on file |
| Plus | 1,000 | $245 AUD | **app certification**; App Store listing optional |
| Advanced | 10,000 | $1,445 AUD | certification + **security assessment** (initial and annual) |
| Enterprise | no limit | on application | certification + security assessment; App Store listing required |

Also true, and easy to miss:

- **Each organisation may connect at most two uncertified apps.** Custom Connections are exempt. So a customer already
  running two uncertified integrations cannot add yours, regardless of your own headroom.
- **Egress is metered.** Tiers include a monthly API egress allotment (10 GB on Core, 50 on Plus, 250 on Advanced) with
  overage at $2.40 AUD/GB; the organisation endpoint is excluded. A bulk-sync connector can blow through an allotment
  long before it hits the connection count, so raise it as a cost question early.
- **Connections and volume are metered per app** and cannot be pooled across apps, even your own.
- **Certification is a process, not a checkbox.** Xero expects roughly ten active customer connections and a
  "Sign Up with Xero" flow as prerequisites, then reviews against published checkpoints: connection management UI,
  branding and naming, minimal scopes with `offline_access`, error handling, account and payment mapping. You apply by
  upgrading the plan from the app's own page in the developer portal.
- **The security self-assessment gates 1,000 connections and the premium endpoints.** Xero shares it at around 800
  connections; until it is passed the app is capped at 999 and cannot use the Journals endpoint or the XPM/Xero HQ APIs.
- Some app categories (financial services, practice APIs) need approval *before* applying.

None of this is a step you can perform. Certification timelines, tier fees and security questionnaires are commercial
and compliance decisions — surface the ceiling, the cost and the gate, and hand back (see **Stop and ask**).

## 9. Token behaviour: 30 minutes, 60 days, and rotation on every use

| Token | Lifetime | Rule |
| --- | --- | --- |
| `access_token` | **30 minutes** | JWT; carries `scope` and `authentication_event_id`. Verify via the JWKS if you verify at all |
| `refresh_token` | **60 days, rolling** | Only issued when `offline_access` was requested. **Rotates on every use** |
| `id_token` | 5 minutes | Only with `openid`; identity claims only |
| authorization `code` | 5 minutes, single use | Reusing or delaying it gives `invalid_grant` |

The three ways this bites:

1. **Rotation.** Every refresh returns a *new* refresh token and invalidates the old one. A client that keeps reusing
   the original succeeds once and fails on the second refresh. Persist the new token in the same transaction as the
   access token.
2. **The 30-minute grace window.** If your app never receives or fails to save the refresh response, the previous
   refresh token still works for **30 minutes**. After that the customer must re-authorize. Worth a retry path.
3. **The 60-day idle death.** The window is rolling, so an actively refreshed connection lives forever — but a
   connection nobody touches for 60 days is gone, and Xero does not tell you when: there is no expiry field on the
   token. Compute `refresh_token_expires_at = now + 60 days` at every refresh and store it. An expired refresh token
   returns HTTP 400 with `invalid_grant`, and the connection still shows as connected on Xero's side until you delete
   it (§7).

## 10. Rate limits, correlation ids and support

Per **tenant**, not per app, except where noted:

- **Concurrent**: 5 calls in flight at once.
- **Minute**: 60 calls per minute.
- **Daily**: 1,000 on Starter; 5,000 on Core and above.
- **App minute limit**: 10,000 calls per minute across all tenants.

Every response carries `X-DayLimit-Remaining`, `X-MinLimit-Remaining` and `X-AppMinLimit-Remaining`. Exceeding a limit
returns **HTTP 429** with `X-Rate-Limit-Problem` naming which one; the minute and daily limits also return
**`Retry-After`** in seconds. Windows are fixed and reset at different times per tenant, so honour `Retry-After` rather
than computing your own backoff. Certified apps get "Rapid Sync" — lifted limits for the first 30 minutes of a new
organisation connection.

Every response also carries a correlation id — the troubleshooting page calls it `X-Correlation-Id` in prose and prints
it as `Xero-Correlation-Id`, so log whichever the response actually contains and quote it in any support ticket. All API
traffic requires **TLS 1.2 or higher**; older TLS gets a 403 with an HTML "Access Denied" body rather than JSON.

## 11. Verify end-to-end

Testing against the demo company alone hides the multi-org problem. Test both.

1. Authorize the demo company through your platform's real connect flow and confirm the token response carries a
   **refresh token** (it will not, if `offline_access` was missing).
2. Call the connections endpoint and confirm you store the `tenantId` and send it as a header on a real read call.
3. Authorize a **second** organisation with the same Xero user, then re-read the connections endpoint — confirm your
   platform's behaviour with two tenants on one token is what you told the customer it would be (§7).
4. Force a **refresh**, then force a **second** refresh using the token the first one returned. That is what catches a
   client ignoring rotation.
5. Confirm a non-admin but Standard/Adviser user can authorize, if customers will use one.

| Symptom | Cause |
| --- | --- |
| Xero 500 "Sorry, something went wrong" at the *start* of the flow | Unregistered `redirect_uri`, invalid scope, or wrong client ID (§4, §5) |
| `unauthorized_client` at the token exchange | `redirect_uri` at exchange differs from the one used for the code (§4) |
| `invalid_client` | Wrong client ID or secret; regenerate the secret if unsure (§6) |
| `invalid_grant` right after consent | Code expired (5 minutes), already used, or wrong `grant_type` (§9) |
| `invalid_grant` on a long-idle connection | Refresh token passed its rolling 60 days — customer must re-authorize (§9) |
| Second refresh fails, first one worked | Client is not storing the rotated refresh token (§9) |
| 403 with `AuthenticationUnsuccessful` | Missing/incorrect tenant header, user revoked access, user lost permission, or tenant deleted (§7) |
| 401 with `WWW-Authenticate: insufficient_scope` | A granular scope was never consented to — prompt a reconnect (§5) |
| Customer's org missing from the consent dropdown | User lacks Standard/Adviser permission, or that org is already connected to this app (§2) |
| New customers cannot connect, existing ones fine | App hit its tier's connection ceiling, or the org already has two uncertified apps (§8) |
| Reports or payroll endpoints 403 for one customer | Authorizing user lacks Reports access or Payroll Admin (§2) |
| 429 with `X-Rate-Limit-Problem` | Per-tenant minute/daily/concurrent limit — honour `Retry-After` (§10) |
| 403 with an HTML "Access Denied" body | TLS 1.1 or lower (§10) |
| Data lands against the wrong organisation | Tenant id reused across connections, or a multi-org authorization collapsed to one tenant (§7) |

## 12. Hand off — never commit the secret

- **Do not** write the client secret into source control, a test, a fixture, a committed `.env`, a ticket, a PR body or
  a chat channel. Values go to the user, for the secret store or console.
- If a code change is needed (a redirect host, a scope migration, storing rotated refresh tokens), keep it secret-free
  and say what the human must set out of band.
- Close with: app name and integration type; the Xero account that owns it; client ID; where the secret was delivered;
  the authorize, token, revocation and connections endpoints; the exact scope strings; the app's **current tier and
  connection ceiling**; whether the client stores rotated refresh tokens; how the tenant id is resolved and stored; and
  anything left for the user to do.

## Stop and ask

Hand back to a human rather than guessing when: the client does not persist rotated refresh tokens (report it — do not
register and hope); the customer count exceeds the current tier and someone must approve a paid tier or certification;
the roadmap needs the Journals endpoint, XPM/Xero HQ, Bulk Connections or any certification-gated scope; a customer
needs multi-organisation behaviour the connector does not implement (§7); the product might use Xero data in a way the
AI/ML training prohibition touches; certification, the security self-assessment, commercial terms or App Store listing
questions ask for compliance, legal or volume claims; or the portal does not match the **Platform state** section above.

## References

- OAuth 2.0 overview and flow comparison — https://developer.xero.com/documentation/guides/oauth2/overview
- Authorization code flow — https://developer.xero.com/documentation/guides/oauth2/auth-flow
- PKCE flow — https://developer.xero.com/documentation/guides/oauth2/pkce-flow
- Custom Connections — https://developer.xero.com/documentation/guides/oauth2/custom-connections
- Client credentials (connection management) — https://developer.xero.com/documentation/guides/oauth2/client-credentials
- Tenants and the connections endpoint — https://developer.xero.com/documentation/guides/oauth2/tenants
- Token types, rotation and revocation — https://developer.xero.com/documentation/guides/oauth2/token-types
- Scopes — https://developer.xero.com/documentation/guides/oauth2/scopes
- Granular scopes FAQ — https://developer.xero.com/faq/granular-scopes
- API limits and uncertified app limits — https://developer.xero.com/documentation/guides/oauth2/limits
- Troubleshooting (auth errors, correlation id, TLS) — https://developer.xero.com/documentation/guides/oauth2/troubleshooting
- OAuth 2.0 FAQ (redirect URI count, wildcards, token expiry) — https://developer.xero.com/faq/oauth2
- Permissions FAQ (who can authorize, Reports and Payroll roles) — https://developer.xero.com/faq/permissions
- Developer pricing and tiers — https://developer.xero.com/pricing
- Pricing and policy updates FAQ — https://developer.xero.com/faq/pricing-and-policy-updates
- Certification checkpoints — https://developer.xero.com/documentation/xero-app-store/app-partner-guides/certification-checkpoints
- Building and growing your app (certification path) — https://developer.xero.com/documentation/xero-app-store/app-partner-guides/building-and-growing-your-app
- Multi-tenancy best practices — https://developer.xero.com/documentation/best-practices/managing-connections/multi-tenancy
- Identifying inactive connections — https://developer.xero.com/documentation/best-practices/managing-connections/identifying-inactive-connections
- Managing tokens and tenant ids — https://developer.xero.com/documentation/best-practices/data-integrity/managing-tokens
- Development accounts and the demo company — https://developer.xero.com/documentation/development-accounts
- Getting started guide — https://developer.xero.com/documentation/getting-started-guide
- My Apps — https://developer.xero.com/app/manage
- Free Xero account signup — https://www.xero.com/signup/developers/
