Connect your app to Notiondesk with OAuth

Build a Notiondesk OAuth app: register a client, run the OAuth 2.0 authorization code flow with PKCE, refresh tokens, and call the REST API.

7 min read

Notiondesk OAuth lets other Notiondesk customers connect your app to their workspace without sharing an API key. The customer signs in to Notiondesk, reviews what your app asks for, and approves it. Your app receives an access token for the Notiondesk REST API, and the customer can revoke it at any time. This article walks through registering your app, running the OAuth 2.0 authorization code flow with PKCE, refreshing tokens, and handling errors.

When to use OAuth instead of an API key

OAuth access tokenWorkspace API key
Use it forAn app other Notiondesk customers connect: a SaaS product, a marketplace integration, a reporting toolAutomation you run for your own workspace: a script, a scheduled job, a CI step
Who creates itThe customer, by approving your appYou, in Settings > Developers > API keys
LifetimeAccess token: 1 hour. Refresh token: 30 days, renewed on each refreshUntil it expires, is rotated or is revoked
Revoked fromSettings > Developers > Connected appsSettings > Developers > API keys

Every Notiondesk API route accepts both credentials and answers them the same way: the same scopes, responses and errors.

What your app needs to know

ItemValue
Authorization server metadatahttps://api.notiondesk.so/.well-known/oauth-authorization-server
Authorization endpointhttps://api.notiondesk.so/oauth/authorize
Token endpointhttps://api.notiondesk.so/oauth/token
Client registration endpointhttps://api.notiondesk.so/oauth/register
Resourcehttps://api.notiondesk.so
Scopesread, write, sync, admin
Grant typesauthorization_code with PKCE, and refresh_token
PKCE methodS256 only

Read the endpoints from the authorization server metadata document rather than hardcoding them.

Run your OAuth client on a server. Browser-based clients are not supported: keep the token exchange, the tokens and every API call on your backend.

Step 1: Register your app

Register once with dynamic client registration:

curl -X POST "https://api.notiondesk.so/oauth/register" \
  -H "Content-Type: application/json" \
  -d '{"client_name": "Acme Reports", "redirect_uris": ["https://acme.example/oauth/callback"], "token_endpoint_auth_method": "client_secret_post"}'

The 201 Created response contains your client_id and, because you asked for client_secret_post, a client_secret. The secret is shown once: store it with your other server secrets.

With "token_endpoint_auth_method": "none", your app is a public client with no secret, and PKCE protects its code exchange.

Instead of registering, you can use the HTTPS URL of a client metadata document as your client_id. Notiondesk fetches the document when a customer authorizes your app and caches it for up to five minutes.

Redirect URI rules

  • Use https, or http on a loopback host: 127.0.0.1, localhost or [::1].
  • A loopback redirect matches on any port. Any other redirect must match exactly.
  • Wildcards, fragments, credentials before the host (user@), backslashes, non-ASCII characters and percent-encoded hosts are refused.
  • The host a customer sees on the consent screen comes from your redirect URIs or your client_id URL. Register only hosts you own.

Step 2: Send the customer to Notiondesk

Create a random code_verifier, then derive the code_challenge as the SHA-256 hash of the verifier, encoded in base64url (PKCE). Redirect the customer's browser to:

https://api.notiondesk.so/oauth/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https%3A%2F%2Facme.example%2Foauth%2Fcallback
  &scope=read%20write
  &state=RANDOM_STATE
  &code_challenge=YOUR_CODE_CHALLENGE
  &code_challenge_method=S256
  &resource=https%3A%2F%2Fapi.notiondesk.so
  • resource is required and must be exactly https://api.notiondesk.so: lower case, no trailing slash, no path. A missing or different value answers invalid_request.
  • scope is a space-separated list. Ask only for the scopes your app uses.
  • state protects against cross-site request forgery. Check it when the customer comes back.

A token issued for the Notiondesk REST API works only with the REST API. A token issued for Workspace MCP is refused by the REST API, and the other way around.

Step 3: The customer approves your app

Notiondesk shows its consent screen. The customer signs in and sees your app's name and host, their workspace, and the scopes from your request that their role allows. They approve or deny the whole request.

The scopes your app can receive depend on the customer's workspace role:

Customer's roleScopes your app can receive
Viewer or Billing Managerread
Editorread, write, sync
Admin or workspace ownerread, write, sync, admin

admin lets your app change the help center's branding, configure integrations whose scripts load on the public help center, and delete redirects. Ask for it only if your app uses those routes.

The customer cannot approve the request when:

  • their role allows none of the scopes you asked for;
  • their workspace does not have API access. The consent screen then shows "The workspace plan does not include API access." API access requires an Enterprise plan.

After approval, the browser returns to your redirect_uri with a code, your state, and iss, the identifier of the Notiondesk authorization server. If the customer denies, the redirect carries error=access_denied instead.

Step 4: Exchange the code for tokens

The code is valid for 60 seconds and works once:

curl -X POST "https://api.notiondesk.so/oauth/token" \
  -d grant_type=authorization_code \
  -d client_id=YOUR_CLIENT_ID \
  -d client_secret=YOUR_CLIENT_SECRET \
  -d code=THE_CODE \
  -d code_verifier=YOUR_CODE_VERIFIER \
  -d redirect_uri=https://acme.example/oauth/callback

Leave out client_secret for a public client. The response:

{
  "access_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "8f3c...",
  "scope": "read write"
}

scope lists the scopes the customer granted, which can be fewer than you asked for. Store both tokens on your server.

Step 5: Call the Notiondesk API

Send the access token as a bearer token, exactly like an API key:

curl "https://api.notiondesk.so/sites" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "nd-version: 2026-10-16"

The token's scopes are limited to the approving customer's current role: a customer changed to Viewer leaves your app with read only, and a route that needs a missing scope answers 403 insufficient_scope.

Rate-limit budgets and Idempotency-Key values belong to the grant. Each approval is a new grant with its own budgets and keys, and a token refresh keeps both.

Step 6: Refresh the access token

An access token lasts one hour. Before it expires, exchange the refresh token for a new pair:

curl -X POST "https://api.notiondesk.so/oauth/token" \
  -d grant_type=refresh_token \
  -d client_id=YOUR_CLIENT_ID \
  -d client_secret=YOUR_CLIENT_SECRET \
  -d refresh_token=THE_REFRESH_TOKEN

Each refresh returns a new refresh token valid for 30 days and invalidates the one you sent. Store the new refresh token before you use the new access token.

Never reuse a refresh token. Sending one that was already used revokes the whole grant, access token included, because reuse means the token was copied. The customer must then authorize your app again.

How customers revoke your app

Workspace owners and Admins see every app with access to their workspace under Settings > Developers > Connected apps. Apps using the REST API show REST API in the Access column, with the app, the person who authorized it, its scopes and its last activity. Revoke ends the grant immediately: its access token and refresh token stop working on the next request.

A grant also ends on its own:

  • after 30 days without a refresh;
  • when its refresh token is reused.

While the approving customer lacks the role a route needs, or the workspace plan lacks API access, requests are refused. They work again if the role or the plan comes back before the grant ends.

Troubleshooting Notiondesk OAuth

The authorization and token endpoints answer standard OAuth errors, not the Notiondesk API error envelope:

{"error": "invalid_grant", "error_description": "Invalid authorization code."}
errorStatusWhen
invalid_request400A parameter is missing or invalid: an unknown resource, a missing PKCE challenge, an unregistered redirect_uri, an unknown client at the authorization endpoint, or an unsupported grant_type
invalid_client401The client secret is missing or wrong
invalid_grant400The code or refresh token is unknown, expired, already used or revoked, the code_verifier or redirect_uri does not match, or the token endpoint does not know the client
server_error500Notiondesk failed; retry later

The authorization endpoint shows these errors itself and never redirects an invalid request to your redirect_uri. A denied consent reaches your app only as the error=access_denied redirect.

Calls to the API itself answer the Notiondesk API error envelope. An expired or revoked access token answers 401 authentication_error with the message "Invalid or missing API key.", the same message an unusable API key gets.

Common questions

Can I call the Notiondesk API from a single-page app with OAuth?

No. Browser-based clients are not supported. Run the OAuth flow and every API call on your server, and keep tokens out of the browser.

How long does a Notiondesk access token last?

One hour. Refresh it with the refresh token, which lasts 30 days and is replaced on every refresh. A grant that is not refreshed for 30 days ends.

Can my OAuth app use Workspace MCP?

No. A token issued for the REST API, with resource=https://api.notiondesk.so, is refused by Workspace MCP. Workspace MCP issues its own tokens to AI clients.

Why did my app receive fewer scopes than it asked for?

The customer's workspace role limits the scopes. An Editor can grant read, write and sync, but not admin. The scope field of the token response lists what was granted.

What's next

Related articles

Was this page helpful?

Still need help? Ask us