---
name: monday-oauth-app
description: Signs in to monday.com and registers an app in the Developer Center to obtain OAuth2 client ID and client secret — with redirect URLs, the per-app-version scope list, the admin-install prerequisite, the two parallel OAuth flows (legacy non-expiring tokens vs the opt-in OAuth 2.1 flow with PKCE and refresh tokens), the dated `API-Version` header, the complexity budget and a safe credential handoff. Use when asked to get monday.com OAuth credentials, create a monday app, pick monday OAuth scopes, rotate a monday client secret, get a monday developer/sandbox account, or debug a monday authorization or API error like `invalid_scope`, `unauthorized_client`, `missingRequiredPermissions`, `USER_ACCESS_DENIED`, `DAILY_LIMIT_EXCEEDED`, `COMPLEXITY_BUDGET_EXHAUSTED`, or "Field ... doesn't exist on type". For any other vendor's developer portal, use that vendor's skill instead.
---

# monday.com OAuth2 App Registration

Get a working monday.com OAuth2 client — an app in the Developer Center, redirect URLs, a scope list, client ID and
secret — for a platform that connects many customers' monday.com accounts.

The registration form is unremarkable. Four things around it are not, and each of them costs a release:

1. **An admin has to install your app on the customer's account before OAuth can complete.** monday apps are
   installed objects, not just OAuth clients, and installation is an admin action. A member who clicks Connect on an
   account where nobody installed the app does not reach a consent screen (§6).
2. **Scopes live on the app version, not on the authorize call.** The authorize request may request a *subset* of
   what the app version declares and nothing more — and changing the declared scopes means a new app version, which
   existing users must manually approve before the new permissions work (§5, §3).
3. **There are two live OAuth flows.** The legacy flow issues tokens that never expire and has no refresh token at
   all. The opt-in **OAuth 2.1** flow has PKCE, expiring JWT access tokens, rotating refresh tokens, revocation, and
   a *different token endpoint*. The switch is a per-app-version toggle, so someone can flip it in the console and
   silently break a client that has no refresh path (§7).
4. **The API is GraphQL-only behind a dated `API-Version` header, and the default moves every quarter.** A client
   that sends no header is not frozen — it is pinned to whatever monday currently calls *Current*, and inherits each
   quarter's breaking changes on the day they land (§8).

## Inputs to collect before you start

Ask in one batch. Never invent.

| Input | Notes |
| --- | --- |
| **App name**, icon, colour, short description | Shown at consent and in the customer's installed-apps list |
| **monday.com account** that will own the app, and who else holds admin there | The app is an object in one account (§2) |
| **Redirect URLs** | Every callback host your platform serves (§4) |
| **Which objects the connector reads and writes** | Decides the scope list you declare on the app version (§5) |
| **Legacy flow or the new OAuth 2.1 flow?** | Must match what the client implements — do not guess (§7) |
| **Which monday plans your customers are on** | Decides the daily call cap and a few Enterprise-only endpoints (§9) |
| **Are webhooks in scope?** | Extra scopes, per-board registration, and a signing secret to verify (§10) |
| **Marketplace listing wanted, or private/shared-link distribution?** | A listing is optional for serving customers (§6) |
| **New app, or an edit to an existing one?** | A new client ID re-authorizes every customer (§1) |

## Quick Start

1. Confirm a **new** app is needed — existing customer connections are bound to the current client ID (§1).
2. Sign in, or create a free **developer account**, and open the Developer Center (§2).
3. **Create an app**; find client ID and secret under *General settings → App credentials* (§3).
4. Add **every** redirect URL, exactly (§4).
5. Declare the scope list on the app version under **OAuth & Permissions** (§5).
6. Promote the version to **live**, then **Install** it, then **Share** it so other accounts can install (§6).
7. Capture the credentials and decide which of the two OAuth flows you are on (§7).
8. Decide the `API-Version` you will pin, and put its quarterly rotation on someone's calendar (§8).
9. Check the daily call cap and complexity budget against what the connector actually does (§9).
10. Verify from a **second monday account you do not administer**, on a non-Enterprise plan (§10).
11. Hand the credentials over — never commit them (§11).

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

- **Two OAuth flows are live.** The legacy flow is the documented default; the **OAuth 2.1** flow is an opt-in
  **New OAuth Flow** toggle in *OAuth & Permissions*, enabled per **app version**. monday's own OAuth page carries a
  banner pointing new integrations at the 2.1 migration guide. No end-of-life date for the legacy flow is published.
- **Legacy tokens never expire.** monday's words: *"Tokens do not expire and are valid until the user uninstalls
  your app. Our OAuth flow does not support refresh tokens."* The legacy token response is `access_token`,
  `token_type` (always `Bearer`) and `scope` — no `expires_in`, no `refresh_token`, no revoke endpoint.
- **The 2.1 flow changes four things at once:** PKCE with `S256` is **required**; access tokens are JWTs with an
  `exp` claim; every refresh returns a **new** refresh token that replaces the old one; and the token endpoint moves
  from `https://auth.monday.com/oauth2/token` to `https://auth.monday.com/oauth_ms/oauth/token`. The authorize URL,
  the scopes, the redirect URLs and the 10-minute authorization code are unchanged.
- **There is a public discovery document** at `https://auth.monday.com/oauth_ms/.well-known/oauth-authorization-server`
  (unauthenticated). On this check it advertised `client_secret_post` **and** `client_secret_basic`, `S256` only,
  `code` only, grant types `authorization_code` / `refresh_token` / `client_credentials`, and a `scopes_supported`
  list of 26 scopes — five of which (`ai:consume`, `apps:read`, `apps:write`, `items:read`, `items:write`) do **not**
  appear in the scope table on the OAuth docs page. Treat discovery as the live truth and the table as the narrative.
- **A temporary legacy-token migration endpoint exists** (`/oauth_ms/oauth/migrate`) that swaps an old app token for
  a 2.1 access/refresh pair without sending users back through consent. monday says it *"is **temporary** and will be
  removed once the OAuth 2.1 migration is complete"* and that personal tokens cannot be migrated.
- **The API is GraphQL-only**, one endpoint, `POST https://api.monday.com/v2`, and version is chosen by the
  `API-Version: YYYY-MM` header. On this check: **`2026-04` Maintenance, `2026-07` Current, `2026-10` Release
  candidate**, with `2026-10` scheduled to become Current on **October 1, 2026**.
- **Credentials live under *General settings → App credentials*** (client ID, client secret, signing secret, app ID).
  The OAuth docs page still says "Basic Information tab" — that naming is stale; trust the Developer Center.
- **A free developer (sandbox) account is self-serve and instant**, with up to 10 seats, 1,000 items per product,
  10M API complexity and selected Pro/Enterprise features. No request form, no waiting period.
- **Marketplace listing is optional.** A public app can be shared by link — optionally restricted to named accounts —
  and installed without review. Unapproved apps show a banner saying monday has not vetted them.

If the Developer Center 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
re-authorizes, and an admin re-installs. Reuse the existing app for: adding a redirect URL, changing the scope list,
resetting a leaked secret, or diagnosing an authorization 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 environment split. Say which path you are taking before you touch anything.

Two monday-specific wrinkles before you decide:

- **Widening scopes is not free even on the same app.** Scope changes ship as a new app version, and monday says
  *"changes to scopes or permissions require manual approval from users"* — users get an in-app banner, keep using
  the app meanwhile, and *"some features may not work as expected until approval is granted."* So a scope widening is
  a slow, per-account rollout you cannot see from your side, not an instant switch.
- **The app is an object inside one monday account.** Whoever holds admin on that account can reach its credentials.
  Prefer a company-controlled account over a personal one, make sure at least two people who will still be there can
  administer it, and record the app name, app ID and client ID somewhere outside monday.

## 2. Account: sign in, or create a developer account

- Any monday.com **admin or member** can create apps; **guests** can only view apps they collaborate on, and
  **viewers cannot open the Developer Center at all**.
- Open it from your profile picture → **Developers** (it opens in a new tab).
- **A free developer account is the right testing surface** and is self-serve: sign up at
  `https://auth.monday.com/users/sign_up_new?developer=true`. It includes all monday products, up to 10 seats, 1,000
  items per product, 25,000 automation and integration actions a month, 10M API complexity, and account-, column- and
  item-level permissions. monday states plainly it is **for development and testing only**, not for production
  workflows or serving customers.
- **Who can use the API at all** is a seat question, and it is not the same as who can log in: admins, members and
  guests can use the API, but **viewers, deactivated or disabled users, users with unconfirmed email addresses and
  users on student accounts cannot**. Guests additionally cannot hold a personal API token — OAuth is their only
  route. A customer connecting as a view-only user gets a token that fails with `USER_ACCESS_DENIED`, which reads
  like your bug and is not (§10).

Hand control back to the user for anything only they can do: signup, email verification, 2FA, accepting developer
terms, promoting someone to admin, installing the app. 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 the exact ordered click path with the literal values to paste (§4 redirect URLs, §5 scope list), then
continue once they report back with the client ID.

## 3. Create the app, and understand app versions

Developer Center → create an app. The sections that matter:

| Section → tab | Holds |
| --- | --- |
| **General settings → Display information** | App name, short description, icon, colour |
| **General settings → App credentials** | **Client ID, client secret, signing secret, app ID** |
| **Build → OAuth & Permissions** | **The scope list**, redirect URLs, and the **New OAuth Flow** toggle (§7) |
| **Build → Webhooks** | App lifecycle webhooks (install/uninstall), distinct from board webhooks (§10) |
| **Manage → App versions** | Draft / live / deprecated versions, gradual release, "Active for me" |
| **Distribute → Install app / Share app / Submit to marketplace** | §6 |

**Versioning is the part people miss.** monday apps have no major/minor numbers: *"each version represents a complete
snapshot of your app."* A draft inherits everything from the live version — scopes, OAuth settings, features, name —
and changes nothing for users until promoted. Exactly **one** version is live at a time; promoting a new one
deprecates the old one, which keeps working for users already on it. A collaborator can mark a draft **Active for me**
to test it alone without affecting anyone.

Practical consequence: **you cannot change scopes, or flip the OAuth flow, on the live version in place.** You make a
draft, change it, test it as Active-for-me, and promote. Build that round trip into any change plan.

## 4. Redirect URLs

Register **every** callback host your platform serves. 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
```

What monday documents:

- **Multiple redirect URLs are supported.** monday: *"You can use multiple URLs if you want to route users to
  different pages based on their context."* If the console only ever accepts one value, stop and report that rather
  than registering four separate apps.
- **`redirect_uri` is conditionally required on the authorize call.** With exactly one URL configured it may be
  omitted and monday uses it; **with more than one configured it is required**. Since a multi-region platform always
  has several, treat it as mandatory and always send it.
- **It must match a registered value**, and it must be sent again at token exchange, where monday uses it *"for
  validation only — there is no redirection"* and requires it to equal the value from the authorize step.
- The redirect carries back `code` (valid **10 minutes**) and your `state`. monday's 2.1 guide also notes a `status`
  parameter to check that the user approved. Errors come back on the redirect URI, not as a monday-hosted page.

## 5. Scopes

Scopes are **declared on the app version** in *OAuth & Permissions*, then optionally narrowed per authorize request:
space-separated in the `scope` parameter, and *"All requested scopes must match the list you configured in your app."*
Omit `scope` entirely and monday uses the app's full configured list — which is usually not what you want on a
connector where different customers enable different objects.

The catalogue as monday's OAuth page spells it:

| Scope | Grants |
| --- | --- |
| `me:read` | The authorizing user's own profile — the minimum for identifying a connection |
| `boards:read` / `boards:write` | Board data: boards, groups, items, subitems, column values |
| `workspaces:read` / `workspaces:write` | Workspaces |
| `users:read` / `users:write` | Profiles of the account's users |
| `teams:read` / `teams:write` | The account's teams |
| `account:read` | General information about the account |
| `webhooks:read` / `webhooks:write` | Reading and creating board webhook subscriptions |
| `updates:read` / `updates:write` | Updates (monday's comment threads) and replies |
| `docs:read` / `docs:write` | monday docs |
| `assets:read` | Files on items the user can see |
| `tags:read` | The account's tags |
| `departments:read` / `departments:write` | Departments (added February 2026) |
| `notifications:write` | Sending notifications as the user |

The live discovery document additionally advertises `items:read`, `items:write`, `apps:read`, `apps:write` and
`ai:consume`, which the docs table does not list. **Read the per-endpoint "Required scope" line in the API reference
for the operations your connector calls** rather than reasoning from the table — every query and mutation in monday's
reference names its scope (for example the webhook mutations name `webhooks:write`), and a scope name that does not
exist is rejected as `invalid_scope`.

**Three rules that bite:**

1. **Read and write do not imply each other.** `boards:write` does not grant `boards:read`. Declare both for anything
   you both read and modify.
2. **Scopes are a ceiling, not a grant.** The token can never exceed the authorizing user's own permissions — monday
   is explicit that API permissions mirror the UI, including board-, column-, item- and account-level access. A token
   with `boards:read` held by a user who cannot see a private board cannot see that board, full stop.
3. **Exceeding the granted scopes is an HTTP 200.** monday returns `missingRequiredPermissions` as an
   application-level error inside a 200 response — *"The operation has exceeded the OAuth permission scopes granted
   for the app."* A client that only checks HTTP status will read this as success with empty data.

> **As of 2026-09-20, this platform's monday connector requests scopes per object, and the union of everything it can
> ask for is:** `me:read`, `boards:read`, `boards:write`, `workspaces:read`, `users:read`, `users:write`,
> `teams:read`, `teams:write`, `webhooks:read`, `webhooks:write`. A login-only connection requests `me:read` alone.
> Every one of these must be declared on the app version, or the customers who enable those objects fail at consent.
> It does not request `account:read`, `updates:*`, `docs:*`, `assets:read`, `tags:read`, `departments:*`,
> `notifications:write` or `workspaces:write`. Confirm the current list with the connector's owner before declaring
> anything.

## 6. Install, share, and the admin gate

This is monday's distribution model, and it is the step that makes a correctly registered app fail for a customer.

**Installation is a prerequisite for authorization, and it is an admin action.** monday's own description of the
`force_install_if_needed` parameter says it *"automatically redirect[s] users to the app installation page if the app
is not installed. Once an admin installs the app, they are redirected back to the OAuth page to complete the
authentication process."* Add `force_install_if_needed=true` to the authorize URL so a non-installed account gets
routed into installation instead of into a dead end — but understand that this is a redirect to a human step, not an
automation of it. A member at a customer who cannot install apps still needs their admin.

The three distribution states:

| State | What it means |
| --- | --- |
| **Installed on your own account** | Promote a version to live, then *Distribute → Install app*. Visible to nobody outside your account |
| **Public, shared by link** | *Share app* → accept developer terms → get a shareable `auth.monday.com` URL. Optionally restrict to named accounts. Customers install from that link; it appears under *Installed outside the marketplace* |
| **Listed in the marketplace** | Reviewed and approved by monday, discoverable by all users |

**A marketplace listing is optional for serving customers** — link sharing is enough, and needs no review. The cost of
skipping it is a banner telling users monday has not vetted the app. If a listing is wanted, know what it involves:
seven requirement categories (listing page, documentation and support, legal, partnership, privacy and security,
product, UI/UX), a submission form, and a review conducted with you on a shared monday board. monday aims for an
**initial response within 72 business hours**; total duration depends on the app. monday also states that apps built
primarily with no-code platforms or AI-generated "vibe code" are **not eligible**, and that submissions duplicating
existing marketplace functionality may be declined. Do not promise a customer a listing date.

**Account selection.** A user can belong to several monday accounts, and the consent screen offers an account picker.
To pin a specific one, either send them to `https://<slug>.monday.com/oauth2/authorize?...` (the picker still works,
your slug is the default) or add `subdomain=<slug>` to the authorize URL (the picker is then locked to that account).
For a multi-tenant connect flow you generally want neither — but if a customer reports connecting "the wrong
company", this is the mechanism.

## 7. Capture the credentials, and pick your flow

From **General settings → App credentials**: **client ID**, **client secret**, **signing secret** (used to verify
board webhook deliveries, §10) and the app ID. Record also the redirect URLs, the declared scope list, and which app
version is live.

### Legacy flow (the documented default)

- Authorize: `GET https://auth.monday.com/oauth2/authorize` with `client_id`, `redirect_uri`, `scope`, `state`, and
  optionally `subdomain`, `app_version_id`, `force_install_if_needed`.
- Exchange: `POST https://auth.monday.com/oauth2/token` with `client_id`, `client_secret`, `redirect_uri`, `code`.
  No `grant_type` is documented for this endpoint.
- Response: `access_token`, `token_type: "Bearer"`, `scope`. **No expiry. No refresh token. No revoke endpoint.**
  The token dies when the user uninstalls the app.
- Consequence to state in a security review: **a leaked legacy token is a long-lived bearer credential** carrying
  everything the authorizing user can reach within the granted scopes, and you cannot revoke it from your side —
  uninstalling the app on that account is the kill switch.

### OAuth 2.1 flow (opt-in, per app version)

Enable it in *OAuth & Permissions* on a **draft** version, test it as Active-for-me, then promote. What changes:

- Authorize gains **required** `code_challenge` and `code_challenge_method=S256` (plain is rejected); the verifier is
  43–128 characters and must be sent as `code_verifier` at exchange.
- Token endpoint moves to `POST https://auth.monday.com/oauth_ms/oauth/token`, JSON body, with an explicit
  `grant_type` (`authorization_code`, then `refresh_token` for renewals).
- Access tokens are **JWTs that expire** — decode `exp`; monday's advice is to refresh when under five minutes
  remain, and to retry once on a `401`.
- **Refresh tokens rotate on every refresh.** Store the newest and discard the old one, or the next refresh fails.
- Revocation exists: `POST https://auth.monday.com/oauth_ms/oauth/revoke` with `token`, `client_id`, `client_secret`
  and an optional `token_type_hint`, answering `{ "success": true }`.

**Do not flip this toggle because it looks more modern.** It is a client-side contract change: a connector with no
refresh implementation will work for exactly as long as the first access token lives and then fail everywhere at once.
Flip it only when the client already implements PKCE, refresh-token rotation and the new endpoint — and verify on a
draft version first.

Resetting the client secret: monday does not document the effect on already-issued tokens. Assume the worst, never
reset without explicit go-ahead and a cutover plan, and verify the effect on one test connection before telling
customers anything.

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.

**The personal API token alternative, and why it is not the answer here.** Every admin and member can mint a personal
V2 token from the Developer Center (admins also from *Administration → Connections*). It carries **all permission
scopes** and exactly that person's UI permissions, never expires, and is regenerable — regenerating invalidates the
old one immediately. Fine for a script or a first read path; not a multi-tenant answer, because it is one person's
credential, every action is attributed to them, guests cannot have one at all, and it dies when they leave. If someone
proposes personal tokens to skip registration, say this.

> **As of 2026-09-20, this platform's monday connector uses the legacy flow.** It sends `client_id`, `redirect_uri`,
> `response_type`, `state` and a space-delimited `scope` to `https://auth.monday.com/oauth2/authorize`, **does not use
> PKCE**, and exchanges the code by POSTing a **JSON body** of `client_id`, `client_secret`, `redirect_uri` and `code`
> to `https://auth.monday.com/oauth2/token`. It stores no refresh token and has no refresh path — correct for the
> legacy flow, and fatal under the 2.1 flow. **So the app it is pointed at must have the New OAuth Flow toggle OFF.**
> It also accepts a monday API token as an alternative credential for single-account setups. Confirm with the
> connector's owner before changing anything in the console.

## 8. API versions — the recurring maintenance cost

monday runs **at least three versions in parallel** — Release candidate (unstable), Current (default, stable),
Maintenance (stable, previous) — and ships a new RC at the start of each quarter at 12:00 AM UTC. Each version is
stable for **at least six months**, and the lifecycle is fixed: RC → Current after 3 months → Maintenance after 3
more → Deprecated, announced *at least* six months in advance.

The header is `API-Version: YYYY-MM`, for example `API-Version: 2026-07`. Behaviour worth knowing exactly:

| What you send | What you get |
| --- | --- |
| No header at all | **The Current version** — which changes every quarter, under you |
| A well-formed name that never existed (e.g. `2024-02`) | The **Current** version — silently, no error |
| A version that has since been **deprecated** | The **Maintenance** version — silently |
| A malformed name (e.g. `2023`) | `InvalidVersionException` |
| A field that does not exist in the version you asked for | An error: `Field '<x>' doesn't exist on type '<Y>'`, code `undefinedField` |

monday's guidance is explicit: *"We strongly encourage production applications to pass a version name in each API
call. If you don't, your app will always get the Current version."* Every response echoes an `API-Version` header, and
the `version` / `versions` queries report the same thing — use them to prove what you are actually running on.

**Why this is the recurring cost.** Quarterly Current promotions carry real breaking changes. The **`2026-10`**
version, Current from **October 1, 2026**, removes the legacy `User` fields `photo_original`, `photo_thumb`,
`photo_thumb_small`, `photo_tiny`, `photo_small`, `is_guest`, `is_admin`, `is_view_only`, `is_pending`, `enabled`,
`is_verified`, `join_date`, `encrypt_api_token` and `sign_up_product_kind`, and removes the `kind`, `newest_first`
and `non_active` arguments on `Query.users` in favour of `user_kind`, `sort` and `status`. `2026-07` before it
capped `Query.users` `limit` at 1,000 and renamed a Connect Boards argument. An unversioned client picks all of that
up on the promotion date with no deploy of its own.

So: pin a version deliberately, subscribe to the release notes, and diary the next promotion. Pinning does not make
the problem go away — it converts a surprise outage into a scheduled migration, which is the whole point.

> **As of 2026-09-20, this platform's monday connector sends no `API-Version` header on any call**, so it runs on
> whatever monday currently calls Current and will move to `2026-10` on October 1, 2026 without a deploy. Its user
> queries select `photo_original`, `photo_small`, `photo_thumb`, `photo_thumb_small`, `photo_tiny`, `join_date`,
> `enabled`, `is_admin`, `is_guest`, `is_pending`, `is_view_only` and `is_verified` — **twelve fields that `2026-10`
> removes**. This is a dated, testable prediction: run one people/employee read against `API-Version: 2026-10` today
> and it either already fails with `undefinedField` or it does not. Raise it with the connector's owner now rather
> than on October 1.

## 9. Limits, plans and seats

monday does **not** rate-limit primarily by request count. It bills a **complexity budget** per token per minute,
plus four other ceilings on top:

| Limit | Value | Error |
| --- | --- | --- |
| Complexity, single query | 5,000,000 points | `ComplexityException` |
| Complexity, app (OAuth) tokens | **5M points/minute for reads and 5M for writes, separately** | `COMPLEXITY_BUDGET_EXHAUSTED` (429) |
| Complexity, personal tokens | 10M/minute combined; **1M** on trial, NGO and free accounts | as above |
| Daily calls | **Free/Standard/Basic 1,000 · Pro 10,000 · Enterprise 25,000**, resetting midnight UTC | `DAILY_LIMIT_EXCEEDED` |
| Requests per minute | Other 1,000 · Pro 2,500 · Enterprise 5,000 | `Minute limit rate exceeded` / `Rate Limit Exceeded` |
| Concurrency | Other 40 · Pro 100 · Enterprise 250 | `maxConcurrencyExceeded` |
| Per IP | 5,000 requests per 10 seconds | `IP_RATE_LIMIT_EXCEEDED` |

Notes that change how you design a sync:

- **The per-minute complexity budget is a sliding window** that resets 60 seconds after the first call, and reads and
  writes have separate budgets for app tokens. You can be throttled at a low request count by a few deep nested
  queries — which is exactly what a naive "fetch the board with all items and all column values" query is.
- **Ask the API what a query costs.** Add the `complexity` field (`before`, `query`, `after`, `reset_in_x_seconds`)
  to any query or mutation; on its own it counts as **0.1** of a daily call, so fold it into real calls rather than
  polling it separately.
- **The daily cap is the one that decides whether your product works.** 1,000 calls/day covers Free, Standard *and*
  Basic — an initial sync of a moderately sized account can eat it before lunch, and it resets only at midnight UTC.
  Requests that hit a rate limit still cost **0.1 calls**, and high-complexity queries cost **more than one**. An
  increase can be requested through monday's API analytics dashboard if an account consistently exceeds it.
- **Back off on the numbers monday hands you.** Rate-limit errors carry a `retry_in_seconds` field, responses carry
  `Retry-After`, and every response carries `RateLimit-Policy` and `RateLimit` headers. Each response also carries a
  `request_id` in `extensions` — quote it in support tickets.
- **Application-level errors arrive as HTTP 200** with an `errors` array, and responses can be **partial**: some
  fields resolved, others errored. A client that branches only on HTTP status will record silent data loss.
- **Plan gating is real but narrow.** The API itself is on every plan. Individual capabilities are not — the
  `2026-10` board-export query and its status poll, for example, are Enterprise-only. Check the reference page for
  each operation rather than assuming parity.
- **Seat gating is the one customers hit.** Viewers, deactivated or disabled users, users with unconfirmed email
  addresses and users on student accounts cannot use the API at all: `USER_ACCESS_DENIED` (403), *"The user is
  unauthorized to use the API."* An admin can also restrict API access by IP, which surfaces as a 401 *"Your ip is
  restricted"* — from monday's side, not yours.

## 10. Verify end-to-end

Authorizing on the account that owns the app, as its admin, on a developer account with 10M complexity, proves almost
nothing. Test the path a customer takes:

1. Authorize from a **second monday account you do not administer**, on a **non-Enterprise plan**, through your
   platform's real connect flow, using the shared link and `force_install_if_needed=true`.
2. Confirm what happens when the app is **not yet installed** there, and that your connect flow shows the customer
   something better than a blank failure.
3. Read the consent screen: the permissions listed must match the scope list you declared, and the account picker
   must land on the account the customer means.
4. Confirm the token response shape matches the flow you are on — legacy: no `expires_in`, no `refresh_token`; 2.1:
   a JWT with `exp`, a refresh token, and a *new* refresh token after one forced refresh.
5. Call one operation per object the connector maps, and **check the `errors` array on every 200**, not the status
   code. `missingRequiredPermissions` here means a scope you never declared.
6. Repeat one read with `API-Version` set to the **next** version (`2026-10` at time of writing) and diff the result.
   This is the cheapest possible early warning for §8.
7. Authorize as a **plain member with limited board access**, and confirm the connector degrades sensibly — missing
   boards, not a crash. Then confirm a **view-only** user fails with a message a support agent can act on.
8. If webhooks are in play: board webhooks are created per board through the API (`webhooks:write`), monday verifies
   your endpoint by POSTing a `challenge` you must echo back verbatim, and deliveries may carry a **JWT in the
   `Authorization` header signed with your app's signing secret** — verify it. Then delete the subscription and
   confirm it is gone.
9. Run a realistic initial sync against an account on a 1,000-call/day plan and watch the daily counter, not just the
   minute counter.

| Symptom | Cause |
| --- | --- |
| Consent never appears; user lands on an install page or a dead end | App not installed on that account; only an admin can install (§6) |
| `403 unauthorized_client` / `403 access_denied` on the redirect | App version not live/shared for that account, or the user declined (§6) |
| `403 invalid_scope` | A requested scope is not declared on the app version, or the name does not exist (§5) |
| `400 invalid_request` at authorize | Missing or mismatched parameter — usually `redirect_uri` omitted while several are configured (§4) |
| Token exchange fails with a valid-looking code | Code older than 10 minutes, `redirect_uri` not identical to the authorize value, or the client is on the wrong token endpoint for the flow the app version is set to (§7) |
| Everything 401s some time after connecting, all at once | The app version was switched to the New OAuth Flow; tokens now expire and nothing refreshes them (§7) |
| `missingRequiredPermissions` inside an HTTP 200 | The operation exceeds the granted scopes — declare and re-consent (§5) |
| `USER_ACCESS_DENIED` (403) | Authorizing user is view-only, deactivated, unverified or on a student account (§9) |
| `UserUnauthorizedException` (403) on one board only | The user does not have permission to that board; the token cannot exceed them (§5) |
| `Unauthorized` (401) | Token missing or malformed in the `Authorization` header (§7) |
| `Your ip is restricted` (401) | The customer's admin restricted API access by IP (§9) |
| `Field '<x>' doesn't exist on type '<Y>'` / `undefinedField` | You are on a different API version than you think — usually because no `API-Version` header is sent (§8) |
| `InvalidVersionException` | Malformed version name, e.g. `2023` instead of `2023-04` (§8) |
| `DAILY_LIMIT_EXCEEDED` | The plan's daily call cap; resets midnight UTC (§9) |
| `COMPLEXITY_BUDGET_EXHAUSTED` / `ComplexityException` at low request counts | Deep nested queries, not request volume (§9) |
| Webhook verified once, then silence | Board webhooks are per board — a new board is not covered by an old subscription (§10) |

## 11. Hand off — never commit the secret

- **Do not** write the client secret or signing 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 list, an `API-Version` header), keep it secret-free and say
  what the human must set out of band.
- Close with: app name, app ID, and the monday account that owns it; client ID; where the secret was delivered;
  whether a signing secret was issued and who holds it; the authorize and token URLs **and which of the two flows the
  app version is set to**; the exact declared scope strings; the redirect URLs; the distribution state (installed /
  shared by link / submitted to marketplace); the `API-Version` the client sends, or a flag that it sends none; and
  anything left for the user to do.

## Stop and ask

Hand back to a human rather than guessing when: someone proposes enabling the **New OAuth Flow** toggle (it is a
client contract change, §7); a scope list needs widening (it ships as a new app version and every customer must
approve it, §1); a customer's admin will not install the app; someone proposes personal API tokens in place of OAuth;
a marketplace submission asks for legal, security, privacy, support-SLA or pricing commitments you were not given;
an `API-Version` needs pinning or moving and nobody owns the migration (§8); a client secret reset is proposed
without a cutover plan; or the Developer Center does not match the **Platform state** section above.

## References

Official monday.com docs only; every URL below returned HTTP 200 on 2026-09-20. The Developer Center itself is
reached from your monday profile menu → **Developers** and requires sign-in.

- OAuth and permissions (flow, parameters, scope table, error codes) — https://developer.monday.com/apps/docs/oauth
- Migrating to the new OAuth 2.1 flow (PKCE, refresh, revoke, discovery, legacy-token migration) — https://developer.monday.com/apps/docs/migrating-to-the-new-oauth-flow
- OAuth discovery document (live list of scopes, grant types, auth methods) — https://auth.monday.com/oauth_ms/.well-known/oauth-authorization-server
- The Developer Center (tabs, where credentials live) — https://developer.monday.com/apps/docs/the-developer-center
- Create an app — https://developer.monday.com/apps/docs/create-an-app
- App versioning (draft/live/deprecated, scope-change approval) — https://developer.monday.com/apps/docs/app-versioning
- Install and share your apps — https://developer.monday.com/apps/docs/install-and-share-your-apps
- App review checklist — https://developer.monday.com/apps/docs/app-review-checklist
- App listing guidelines — https://developer.monday.com/apps/docs/app-listing-guidelines
- Submit your app (review phases, 72-business-hour initial response) — https://developer.monday.com/apps/docs/submit-your-app
- API basics, including **who can use the API** — https://developer.monday.com/api-reference/docs/basics
- Authentication (header format, personal tokens, token permissions) — https://developer.monday.com/api-reference/docs/authentication
- Developer / sandbox account — https://developer.monday.com/api-reference/docs/developer-sandbox-account
- API versioning (header, lifecycle, release schedule, troubleshooting) — https://developer.monday.com/api-reference/docs/api-versioning
- Release notes (per-version breaking changes) — https://developer.monday.com/api-reference/docs/release-notes
- API changelog — https://developer.monday.com/api-reference/changelog
- Apps changelog — https://developer.monday.com/apps/changelog
- Rate limits (complexity, daily, minute, concurrency, IP) — https://developer.monday.com/api-reference/docs/rate-limits
- Complexity query — https://developer.monday.com/api-reference/docs/complexity
- Optimizing API usage — https://developer.monday.com/api-reference/docs/optimizing-api-usage
- Errors (codes, 200-with-errors format, `request_id`) — https://developer.monday.com/api-reference/docs/errors
- Board webhooks (challenge handshake, JWT signing, scopes) — https://developer.monday.com/api-reference/reference/webhooks
- App lifecycle webhooks — https://developer.monday.com/apps/docs/webhooks-1
- Integration authorization (JWT verification, signing secret, short-lived tokens) — https://developer.monday.com/apps/docs/integration-authorization
- API analytics (usage dashboard, daily-limit increase requests) — https://developer.monday.com/api-reference/docs/api-analytics
- Plans and pricing — https://monday.com/pricing
- Marketplace — https://monday.com/marketplace
- Status page — https://status.monday.com/
