---
name: atlassian-cloud-oauth
description: The shared Atlassian Developer Console / OAuth 2.0 (3LO) mechanics behind every Atlassian Cloud product registration — creating the app and enabling 3LO, the single callback URL per app, account-level vs resource-level grants, the classic and granular scope families, `offline_access`, rotating refresh tokens, the cloudid indirection and `/ex/{product}/{cloudid}/...` addressing, scope changes forcing re-consent, distribution and Marketplace approval. Read this first when registering any Atlassian Cloud OAuth app; the product skills (Atlassian Jira, Atlassian Confluence) build on it and cover only what their product adds. Use directly when the task is a plain Atlassian 3LO app with no particular product named. Covers Atlassian **Cloud** only — Data Center / Server uses a different auth model entirely.
---

# Atlassian Developer Console — shared OAuth 2.0 (3LO) registration mechanics

Everything common to registering an OAuth 2.0 (3LO) integration for **any** Atlassian Cloud product: the developer
console app, the one callback URL, the grant type, the scope families, `offline_access`, the client ID and secret,
rotating refresh tokens, the cloudid indirection, and distribution.

The product skills — Atlassian Jira, Atlassian Confluence — assume this file and cover only what their product adds on
top (its own scope family, its own API generations, its own permission model). **Read this first, then the product
skill.** If you are registering a plain Atlassian 3LO app with no particular product in play, this file is the whole
job.

**One app can serve several products, but that is a decision, not a default.** Jira, Jira Software, Jira Service
Management, Confluence and Assets are separate APIs with separate scope lists in the same console app. Adding "Jira
API" does not give you Confluence scopes. Where a platform maintains separate connectors per product, each usually
wants its own app and its own credentials — confirm with the connectors' owners rather than assuming either way.

Four things here are expensive to get wrong, and three of them are invisible at registration time:

1. **A 3LO app takes one callback URL.** Not a list. If your platform serves several callback hosts, that is several
   apps, or a redesign — decide before you register (§4).
2. **An access token is not bound to a site.** You discover the site's **cloudid** from a separate endpoint and address
   every API call as `/ex/{product}/{cloudid}/...`. A client that hardcodes `https://customer.atlassian.net`
   authenticates perfectly and then fails on every request (§6).
3. **Scopes come in two incompatible-feeling families** — classic and granular — and changing the set forces every
   existing user to re-consent (§5).
4. **Refresh tokens rotate.** Each refresh returns a new one and disables the old one. A client that does not persist
   the rotated token fails on the *second* refresh, not the first, which means it passes your smoke test (§8).

## Inputs to collect before you start

Ask in one batch. Never invent.

| Input | Notes |
| --- | --- |
| **App name**, description, avatar | Shown on the Atlassian consent screen |
| **Atlassian account** that will own the app | Console apps belong to an account, not an org (§2) |
| **A Cloud site to test against** | A free site is fine; a sandbox needs Premium/Enterprise (§2) |
| **Callback URL** — exactly one per app | Blocking. See §4 before promising anything |
| **Grant type**: account-level or resource-level | Chosen at creation, shapes what tokens can reach (§3) |
| **Which Atlassian products** | Jira, Jira Software, Jira Service Management, Confluence, Assets are separate APIs |
| **Scope set**, and whether it is classic or granular | The product skill supplies the strings (§5) |
| **Does the client persist rotated refresh tokens?** | Blocking — see §8 |
| **Distribution**: private, shared, or submitted for approval | §9 |

## Quick Start

1. Confirm a **new app** is needed — existing user grants are bound to the current client ID (§1).
2. Sign in to the Atlassian Developer Console with the account that should own the app (§2).
3. **Create → OAuth 2.0 integration**, name it, and choose the grant type (§3).
4. **Permissions → Add** the product API(s) you need, then add scopes to each (§5, and the product skill).
5. **Authorization → OAuth 2.0 (3LO) → Configure →** enter the single **Callback URL** and save (§4).
6. Confirm the client knows how to resolve and store the **cloudid** (§6). This is the usual failure.
7. Copy **Client ID** and **Secret** from **Settings** (§7).
8. Confirm `offline_access` is requested and rotated refresh tokens are persisted (§8).
9. Decide distribution — private, shared via link, or submitted for Atlassian approval (§9).
10. Verify with a real authorize → callback → accessible-resources → API call → double refresh (§10).
11. Hand the credentials over — never commit them (§11).

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

- **Apps are created in the Developer Console** at `https://developer.atlassian.com/console/myapps/` — profile icon →
  *Developer console* → **Create** → **OAuth 2.0 integration**. Console apps are listed with a **3LO** lozenge.
  Connect apps are not listed there at all.
- **Two grant types are offered at creation**, and the choice changes what a token can reach:
  - **Account-level grant** — consent applies at the Atlassian account level; one grant can cover multiple sites, and
    sites can be *added to the same grant later* when the user consents again.
  - **Resource-level grant** — consent is limited to the site(s) the user picks on the consent screen; tokens from
    that grant cannot be used with any other site.
  Treat this as effectively permanent for a live app: changing it re-consents everybody. Atlassian's OAuth changelog
  also records a 2026 option for newly created 3LO apps to opt into **resource-restricted tokens**, scoping issued
  access tokens to the one site the user picks at consent. Check the changelog for the current shape before relying
  on either name.
- **One callback URL per app.** The console's Authorization → OAuth 2.0 (3LO) → Configure screen has a single
  *Callback URL* field, and the docs describe it in the singular. There is no documented way to register a list (§4).
- **Scopes are split into classic and granular**, per product API, with a soft ceiling: *"It's recommended that you
  use less than 50 scopes in an application."* Which family to prefer is **product-specific** — the general
  recommendation leans classic, but a product's newer API generation may document granular scopes only. The product
  skill decides this; do not generalise from one product to another (§5).
- **Changing scopes forces re-consent.** Atlassian states it plainly on the console management page: *"Note that users
  who previously consented to the scopes will need to re-consent to the new scopes."*
- **Refresh tokens rotate**, with a **90-day inactivity expiry** reset on every rotation and a **10-minute reuse
  leeway** for concurrency. No absolute cap is documented (§8).
- **Apps are private by default.** Distribution → *Enable sharing* turns on link-based sharing; users installing an
  unapproved integration are *"warned that the app has not yet been reviewed by Atlassian"* (§9).
- **App ownership is no longer strictly single-account.** Atlassian's OAuth changelog records a 2026 change allowing
  **multiple admins** on a console app and transfer of ownership. That softens, but does not remove, the offboarding
  problem in §2 — confirm what the console actually offers on the app you are working on.
- **Atlassian names two practices as non-compliant.** From the 3LO pages: *"Apps that collect API tokens or instruct
  customers to create individual 3LO apps don't comply with our Security requirements for cloud apps and Acceptable
  use policy."* The stated best practice is *"Build a single, distributable 3LO app for your integration."* If the
  platform you are registering for does either thing, that is a finding to raise, not a step to work around (§9).
- **No install cap is documented** for shared-but-unapproved 3LO apps — unlike several other portals. Do not promise
  a number; the constraint that bites is the unreviewed-app warning, not a counter.
- **Admins cannot revoke one user's grant** from the Connected Apps screen — the grant is between the app and the
  user; an admin can only uninstall.

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

**Atlassian Cloud only.** **Data Center / Server** does not use this at all — different auth models, registered inside
the customer's own instance, with no Atlassian-hosted developer console, no `auth.atlassian.com`, and no cloudid. If
the customer's URL is not `*.atlassian.net` (or a custom Cloud domain), you are in the wrong document; the product
skill says what the Data Center path actually is for that product.

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

A new app means a **new client ID, and every existing user grant is bound to the old one** — every connected user
re-authorizes. Reuse the existing app for: changing scopes (accepting the re-consent, §5), rotating a compromised
secret, flipping distribution, or diagnosing a failure.

Register a **new** app only when the user explicitly wants one: a second callback host that the one-URL limit forces
into its own app (§4), a replacement for a compromised app, a separate product, or a deliberate grant-type change.
Say which path you are taking before you touch the console.

## 2. Account and test site

- **Atlassian account** — sign in at `https://developer.atlassian.com`, then profile icon → *Developer console*.
  There is no separate "developer program" enrolment for a 3LO integration; any Atlassian account can create one.
  The app belongs to that account, so use a shared/role account rather than an individual's, or you inherit an
  offboarding problem later — even where multiple admins can now be assigned (see Platform state).
- **A Cloud site to test against** — a free Atlassian Cloud site is enough for an end-to-end check. You need site
  admin rights on it to install and to see the consent screen behave. The product skill gives the signup link.
- **A sandbox** is a different thing and is gated: Atlassian sandboxes require **Premium or Enterprise**, and creating
  one requires **organization admin** permissions. Do not plan around a sandbox unless the user already has one.

Hand control back to the user for anything only a human can do: account signup and email verification, MFA, accepting
developer terms, or creating a site. 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. Give
the user an exact, ordered click path with the literal values to paste — the callback URL (§4) and the scope list
from the product skill — then continue once they report back with the client ID.

## 3. Create the app and enable 3LO

1. Profile icon → **Developer console** → **Create** → **OAuth 2.0 integration**.
2. Enter the **app name** and choose the **grant type** — account-level or resource-level (see Platform state). For a
   multi-tenant connector where one customer may have several sites, account-level is usually what you want;
   resource-level is the tighter, site-pinned option. Either way, §6 still applies.
3. **Permissions → Add** the API(s) the integration calls. Jira platform, Jira Software, Jira Service Management,
   Confluence and Assets are **separate APIs with separate scope lists**. Add each product you actually call, and no
   more.
4. **Authorization → OAuth 2.0 (3LO) → Configure** → enter the **Callback URL** → **Save changes** (§4).
5. **Settings** holds the app name, description, avatar, **Client ID** and **Secret** (§7), and **Delete app**.

Two console facts worth knowing before you go looking for them:

- The **App ID** on the Overview page is *not* the OAuth `client_id`. The client ID lives on **Settings**.
- **You can only delete an app if it is not installed anywhere.** Uninstall first, then delete.

## 4. The callback URL — you get one

`redirect_uri` must match the registered Callback URL, and the console takes a single value. This is the constraint
that decides your architecture, so settle it before registering.

For Unified.to the callbacks are one per data center:

```
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
```

With one callback URL per app, that means either:

- **One app per data center** — four apps, four client ID/secret pairs, each registered with its own callback. This is
  the normal answer, and it is what the credential store must be able to hold. Say so explicitly in your handoff.
- **One app, one callback**, with that single host fanning out internally. This is a platform change, not a console
  setting — do not assume it exists.

Multiply that by products: a platform with separate Jira and Confluence connectors and four data centers is eight
apps, not four. Confirm the current callback list with the platform owner rather than assuming; data centers get
added.

Other notes:

- The `redirect_uri` you send on **both** the authorize request and the token exchange must match the registered
  value. Mismatches surface at connect time, not at save time.
- The callback is your platform's URL, not Atlassian's, and does **not** vary per customer site — unlike the API base
  URL, which does (§6). Do not conflate the two.
- Saved changes can take a short while to propagate; a failure immediately after saving is worth one retry before
  it means anything.

## 5. Scopes — the two families, and `offline_access`

The product skill supplies the scope strings. This section is how Atlassian treats them.

Atlassian runs **two parallel scope families** over the same APIs, and the console presents them per API:

- **Classic scopes** are broad and product-wide — a handful per product, shaped `read:<product>-<area>` (for example
  `read:jira-work`, `read:confluence-content.all`).
- **Granular scopes** are per-resource and follow a `verb:resource:product` shape — `read:issue:jira`,
  `read:page:confluence`, `write:comment:confluence` — and there are several hundred of them.

What matters operationally:

- **Which family to use is a product question, not a portal question.** Atlassian's general guidance leans toward
  classic scopes where they are available, but a product's newer API generation may document granular scopes only, in
  which case classic is not an option for those endpoints. **Read the product skill before choosing**, and check the
  **OAuth scopes required** field on the specific endpoint you intend to call.
- **Pick a family and stay in it per product.** Atlassian's 3LO pages stop short of an outright prohibition on
  mixing, but everything around it treats the two as an either/or choice, and the console's own advice when you
  approach the scope ceiling is to *"ensure you're using classic scopes to the maximum extent possible and remove any
  unnecessary granular scopes."* A set that is mostly one family with one or two of the other bolted on is the shape
  most likely to behave surprisingly — a granular scope present in the token while the endpoint checks the classic
  one, or the reverse, produces a 401 with a scope-mismatch message on an endpoint you are certain you are entitled
  to call. If you find such a mix, treat it as a defect to resolve deliberately, not a harmless accident.
- **You can only request scopes you already added to the app.** The docs are explicit: *"Only choose from the scopes
  that you have already added to the APIs for your app in the developer console."* An authorize URL asking for a scope
  the console does not know about fails at the consent screen.
- **Changing scopes re-consents everyone.** Every user who already granted the old set must grant again. For a live
  connector this is a customer-visible migration, not a config tweak — plan it, do not slip it into a release.
- **Scopes never exceed the user's own permissions.** An app holding a project- or space-administration scope still
  cannot act if the authorizing user lacks that permission in the product. A scope failure and a permission failure
  look similar and are fixed in different places.
- **Under 50 scopes per app** is the recommendation, with a live count shown in the console.
- **`offline_access` is a scope**, not a flag, and without it you get no refresh token at all (§8). An authorization
  that is only ever used once — an identity/login flow — can legitimately omit it; anything that must keep working
  tomorrow cannot.
- **Identity scopes are their own small family.** `read:me` (the authenticated user's public profile, via the User
  Identity API) and `read:account` are not product scopes and do not appear on a product's scope page. Treat a
  login-only authorization as its own scope set.
- If an operation's reference page says *"Apps can't access this REST resource"*, no scope will help — it is not
  available to 3LO at all.

**Other Atlassian products are other scope families.** One console app *can* serve several products if you add each
product's API and its scopes, but a platform that ships separate connectors per product usually wants separate apps.
Decide that with the connectors' owners, not by default.

## 6. The cloudid indirection — read this before writing any client code

This is the single most common way an Atlassian integration fails after a flawless registration.

**An access token is not bound to a site.** It is bound to a *grant*, and a grant can cover one site or several. The
site's identity is a UUID Atlassian calls the **cloudid**, and you have to go and ask for it:

```
GET https://api.atlassian.com/oauth/token/accessible-resources
Authorization: Bearer <access_token>
Accept: application/json
```

The response is an **array** of sites the token can be used with:

```json
[
  { "id": "8594f221-9797-5f78-1fa4-485e198d7cd0", "name": "Site name 2",
    "url": "https://your-domain2.atlassian.net", "scopes": ["write:jira-work", "read:jira-user"] },
  { "id": "1324a887-45db-1bf4-1e99-ef0ff456d421", "name": "Site name 1",
    "url": "https://your-domain1.atlassian.net", "scopes": ["write:jira-work", "read:jira-user", "manage:jira-configuration"] }
]
```

Every API call then goes to `https://api.atlassian.com/ex/{product}/{cloudid}/{api}`, where `{product}` is the
product's own segment (`jira`, `confluence`, …) and `{api}` is that product's REST base path. **The `{api}` part is
product-specific and is where people get it wrong** — the product skill spells out the exact path for each API
generation. Getting `{product}` right and `{api}` wrong yields a 404 that looks like a permissions problem.

The traps, in the order they bite:

- **A client that hardcodes `https://<customer>.atlassian.net` breaks.** OAuth 2.0 (3LO) tokens are not accepted
  there. Authorization succeeds, the token looks fine, and every subsequent request fails. The site URL in the
  response is for *display*; the `id` is what you address.
- **The array can hold more than one site.** Under an account-level grant, sites can be added to the same grant over
  time. Taking `[0]` picks an arbitrary site and silently reads the wrong one. Either let the customer choose, or
  match the site you were told to connect by its `url`, and say which you did.
- **One site can carry several products.** A single cloudid commonly serves both Jira and Confluence on the same
  Atlassian site; the product segment in the path, not the cloudid, selects which. A fallback that reuses a cloudid
  resolved for one product while keeping another product's path segment silently addresses the wrong API.
- **Resolving it once and caching it forever is only half right.** The cloudid of a given site is stable, but the
  *set* of sites a token reaches is not — it changes when the user re-consents. Atlassian's instruction is to call
  `accessible-resources` with the current token and use what comes back, so re-check on re-authorization and when a
  call starts 403-ing, not only at first connect.
- **The per-site `scopes` array is the real answer** to "does this token have what I need?" — it is scoped to that
  site, and it is how you detect a partial grant before a customer reports a broken feature.

## 7. Capture the credentials

From the Developer Console → your app → **Settings**: the **Client ID** and the **Secret**. Capture alongside them:

- **Authorization endpoint** — `https://auth.atlassian.com/authorize`
- **Token endpoint** — `https://auth.atlassian.com/oauth/token` (POST, JSON body)
- **API base** — `https://api.atlassian.com/ex/{product}/{cloudid}` (§6)
- **`audience=api.atlassian.com`** — a *required* authorize parameter, and Atlassian-specific. Omitting it is an
  early, confusing failure.
- **`prompt=consent`** — also required, so the grant screen actually renders.
- Which data center / environment this app's single callback serves (§4), and which product(s) its APIs cover.

The full authorize URL Atlassian documents:

```
https://auth.atlassian.com/authorize?
  audience=api.atlassian.com&
  client_id=YOUR_CLIENT_ID&
  scope=REQUESTED_SCOPE_ONE%20REQUESTED_SCOPE_TWO&
  redirect_uri=https://YOUR_APP_CALLBACK_URL&
  state=YOUR_USER_BOUND_VALUE&
  response_type=code&
  prompt=consent
```

Scopes are **space-delimited**. `state` is required for security and must be unguessable and bound to the user.

Rotating the secret invalidates the old one: existing access tokens keep working until they expire, but every token
exchange and refresh fails until the new secret is deployed. Never rotate without an 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 rotated, then move on.

## 8. Token lifetimes and rotating refresh tokens

| Thing | Value | Source |
| --- | --- | --- |
| Refresh token | Only issued if `offline_access` is in the authorize `scope` | 3LO docs |
| Refresh token behavior | **Rotating** — each use returns a new one and **disables the one you used** | 3LO docs |
| Inactivity expiry | **90 days**, *reset* by each rotation | 3LO docs |
| Reuse leeway | **10 minutes** — within it, breach detection does not fire on repeated exchange of the same token | 3LO docs |
| Absolute maximum lifetime | **Not documented** — do not assume one exists, and do not assume one does not | — |
| Access token | `expires_in` (seconds) in the token response — read it, do not hardcode | 3LO docs |

The refresh call:

```
POST https://auth.atlassian.com/oauth/token
Content-Type: application/json

{ "grant_type": "refresh_token", "client_id": "...", "client_secret": "...", "refresh_token": "..." }
```

The response carries a new `access_token` **and a new `refresh_token`**, and *"the refresh token you used for the
request is disabled."*

**The failure mode that passes your smoke test.** A client that does not store the rotated refresh token refreshes
successfully the first time — it still holds a valid token — and fails the *second* time, hours or days later, with:

```
403 Forbidden  {"error": "invalid_grant", "error_description": "Unknown or invalid refresh token."}
```

Atlassian lists the causes for that exact error: the user changed their Atlassian account password, the refresh token
expired (the user must re-authorize from scratch), or *"Your app is not replacing the previous refresh token with the
new refresh token returned during access token request."*

So there are two separate questions, and a platform can answer yes to the first and no to the second:

1. **Does the client implement rotation** — does it read `refresh_token` out of the refresh response at all?
2. **Does it persist it**, atomically, before the next refresh, including under concurrent refreshes of the same
   connection? The 10-minute leeway exists precisely because concurrency here is real.

The other quiet killer is **inactivity**. A connection nobody refreshes for 90 days is dead and the customer must
re-authorize. Idle connections are the ones that break, so a periodic refresh is a feature, not a nicety. A platform
that treats the refresh token as expiring at, say, 89 days is deliberately staying inside that window — confirm the
number rather than assuming it.

## 9. Distribution, approval and the Marketplace

Apps are **private by default** — only the creating account can install and use them.

To share: **Distribution → Enable sharing** toggle, then **Authorization → OAuth 2.0 (3LO) → Configure** and copy the
**Authorization URL(s)** to distribute. Note what this does and does not mean:

- **3LO apps install per user**, not per site. There is no admin-installs-for-everyone path here; every user who
  connects goes through consent themselves.
- **Unapproved integrations show a warning.** Users are *"warned that the app has not yet been reviewed by
  Atlassian."* For a commercial connector that warning is a conversion and trust problem, and it is the real reason
  to seek approval.
- **Approval is separate from a listing.** To get the integration reviewed and approved, follow Atlassian's
  *Listing a third party integration on the Atlassian Marketplace* process — and, per the console docs, *"you don't
  need an informative Atlassian Marketplace listing to submit your integration for approval."*
- **A Marketplace listing for a 3LO integration is informational only.** Enabling sharing does not put the app on the
  Marketplace, and when listed, 3LO integrations *"appear as informational listings only, with limited Marketplace
  features"* — the listing links out to your own site, where you handle distribution. Review requires giving
  Atlassian access to a test instance of your software.
- **No install cap is documented.** Do not quote one.

**Two practices Atlassian names as non-compliant.** From the 3LO pages: *"Apps that collect API tokens or instruct
customers to create individual 3LO apps don't comply with our Security requirements for cloud apps and Acceptable use
policy."* The stated best practices are to build a **single, distributable 3LO app**, identify it clearly in the
Marketplace, and transition customers away from custom 3LO apps and API tokens. If the platform you are registering
for asks each customer to create their own app, or accepts Atlassian API tokens as an auth method, say so in your
summary and flag it to the owner. That is a product decision with a compliance dimension — report it; do not quietly
register around it.

**Data security policies can block a compliant app.** An organization admin can attach an **app access rule** to a
data security policy and block apps from reading user-generated content in selected spaces or projects, independently
of scopes and consent. The product skill says what that looks like for its API. Note the asymmetry Atlassian
documents: apps authenticating with **Atlassian API tokens or user credentials cannot be blocked this way**, which
means the compliant OAuth path is the blockable one and the non-compliant token path is not. That is worth saying out
loud when someone proposes tokens as a workaround.

**Data residency.** Atlassian's data residency covers data held in Atlassian's cloud for eligible products on
eligible plans. It does **not** cover data a third-party app stores on its own infrastructure outside Atlassian. If a
customer asks whether connecting your integration keeps their data in-region, the honest answer is that Atlassian's
controls stop at Atlassian's boundary and your platform's own residency story takes over. Do not answer a customer's
residency question on the platform's behalf — hand it to someone who can.

## 10. Verify end-to-end

Authorizing from the account that owns the app proves less than you think. Test the path a customer takes:

1. Run a real **authorize → callback** round trip through the platform's connect flow, on a **different** Atlassian
   account and a site that account administers, with `audience`, `prompt=consent`, `state` and the exact
   `redirect_uri`.
2. Confirm the token response contains a **`refresh_token`** — if not, `offline_access` was missing from `scope`.
3. Call **`/oauth/token/accessible-resources`** and look at the whole array: how many sites came back, and does the
   per-site `scopes` list contain everything you asked for?
4. Make one real read against **`https://api.atlassian.com/ex/{product}/{cloudid}/...`** — not against the
   customer's `.atlassian.net` host — using the exact API path the product skill gives.
5. Force a **refresh**, then force a **second refresh using the token returned by the first**. This is the step that
   catches a client ignoring rotation, and it is the step most often skipped.
6. If the app is shared, have someone outside the owning account connect, and confirm whether they see the
   unreviewed-app warning (§9).

| Symptom | Cause |
| --- | --- |
| Consent screen errors before showing anything | Missing `audience=api.atlassian.com`, or `prompt=consent` omitted (§7) |
| `redirect_uri` rejected | Not byte-identical to the one registered callback, or sent on authorize but not on exchange (§4) |
| Consent screen rejects a scope | Scope not added to the app's API in the console, or the wrong product's API not added (§5) |
| Auth succeeds, every API call 401s or 404s | Client is calling `<site>.atlassian.net` instead of `/ex/{product}/{cloudid}` (§6) |
| Auth succeeds, calls 404 on one product only | Right cloudid, wrong product segment or wrong API base path (§6) |
| No `refresh_token` in the token response | `offline_access` not in the authorize `scope` (§8) |
| First refresh works, later ones return `invalid_grant` | Rotated refresh token not persisted (§8) |
| Connection dies after a quiet ~3 months | 90-day inactivity expiry; the user must re-authorize (§8) |
| Reads the wrong site | `accessible-resources[0]` taken blindly on a multi-site grant (§6) |
| 401 "scope does not match" on an endpoint you have the scope for | Classic/granular family mismatch for that endpoint (§5) |
| Endpoint says apps can't access it | Not available to 3LO at all — no scope fixes it (§5) |
| Everyone suddenly has to re-consent | Scopes were changed on the app (§5) |
| Works for the app owner, warns everyone else | App shared but not approved (§9) |
| Content silently missing for one customer only | A data security policy app access rule (§9) |
| Nothing resembles this document; no `auth.atlassian.com` | It is Data Center/Server, not Cloud — wrong auth model entirely |

## 11. 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 callback host, a scope, cloudid handling), keep it secret-free and say what the human
  must set out of band.
- Close with: app name and App ID; the owning Atlassian account; the **client ID**; where the secret was delivered;
  the grant type chosen; the single callback URL and which data center it serves (and whether more apps are needed,
  §4); which product APIs were added; the exact scope strings and whether they are classic or granular; confirmation
  that `offline_access` is requested; the state of rotated-refresh-token persistence and who owns any remaining work;
  the distribution state and whether approval is being sought; and anything left for the user to do.

## Stop and ask

Hand back to a human rather than guessing when: the platform needs more callback hosts than one app allows and
nobody has decided between multiple apps and a single callback (§4); the client hardcodes a site URL or takes the
first entry from `accessible-resources` without a rule (§6); the client does not persist rotated refresh tokens (§8);
a scope change would re-consent live customers (§5); the choice between account-level and resource-level grants is
not obviously the user's (§3); whether one app should serve several Atlassian products is genuinely open (§5); the
platform collects Atlassian API tokens or asks customers to create their own 3LO apps, which Atlassian names as
non-compliant (§9); a Marketplace submission asks for security, privacy, compliance or support commitments; a
customer asks about data residency for data held outside Atlassian (§9); the target is Data Center/Server; or the
console does not match the **Platform state** section above.

**No browser automation:** if the session cannot drive a browser, do not simulate clicks or claim a step was done.
Produce the exact click path and literal values for the user, and resume when they report back.

## References

Portal-wide and product-neutral only; each product skill carries its own. Every link verified to resolve on
2026-09-20.

- Developer Console — https://developer.atlassian.com/console/myapps/
- Enabling OAuth 2.0 (3LO) — https://developer.atlassian.com/cloud/oauth/getting-started/enabling-oauth-3lo/
- Implementing OAuth 2.0 (3LO) — https://developer.atlassian.com/cloud/oauth/getting-started/implementing-oauth-3lo/
- Making calls to the API (the `/ex/{product}/{cloudid}` form) — https://developer.atlassian.com/cloud/oauth/getting-started/making-calls-to-api/
- Implementing the refresh token flow — https://developer.atlassian.com/cloud/oauth/getting-started/refresh-tokens/
- Managing your OAuth 2.0 (3LO) apps (distribution, scope re-consent, deletion) — https://developer.atlassian.com/cloud/oauth/getting-started/managing-oauth-apps/
- OAuth 2.0 (3LO) FAQ — https://developer.atlassian.com/cloud/oauth/getting-started/faq/
- OAuth 2.0 changelog (grant types, resource-restricted tokens, app ownership) — https://developer.atlassian.com/cloud/oauth/changelog/
- Authentication and authorization for developers (which auth model applies where) — https://developer.atlassian.com/developer-guide/auth/
- What is a data security policy? (app access rules) — https://developer.atlassian.com/cloud/admin/data-security-policy-guide/
- Security requirements for cloud apps — https://developer.atlassian.com/platform/marketplace/security-requirements/
- Listing a third party integration on the Atlassian Marketplace (the approval path) — https://developer.atlassian.com/platform/marketplace/knowledge-base/listing-a-third-party-integration-on-the-atlassian-marketplace/
- List and manage apps — https://developer.atlassian.com/platform/marketplace/listing-and-managing-apps/
- App approval guidelines — https://developer.atlassian.com/platform/marketplace/app-approval-guidelines/
- Atlassian Acceptable Use Policy — https://www.atlassian.com/legal/acceptable-use-policy
- Understand data residency — https://support.atlassian.com/security-and-access-policies/docs/understand-data-residency/
- Create a sandbox (Premium/Enterprise, org admin) — https://support.atlassian.com/organization-administration/docs/create-a-sandbox/
- Manage API tokens for your Atlassian account (the non-compliant path, for identification) — https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/
