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 token | Workspace API key | |
|---|---|---|
| Use it for | An app other Notiondesk customers connect: a SaaS product, a marketplace integration, a reporting tool | Automation you run for your own workspace: a script, a scheduled job, a CI step |
| Who creates it | The customer, by approving your app | You, in Settings > Developers > API keys |
| Lifetime | Access token: 1 hour. Refresh token: 30 days, renewed on each refresh | Until it expires, is rotated or is revoked |
| Revoked from | Settings > Developers > Connected apps | Settings > 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
| Item | Value |
|---|---|
| Authorization server metadata | https://api.notiondesk.so/.well-known/oauth-authorization-server |
| Authorization endpoint | https://api.notiondesk.so/oauth/authorize |
| Token endpoint | https://api.notiondesk.so/oauth/token |
| Client registration endpoint | https://api.notiondesk.so/oauth/register |
| Resource | https://api.notiondesk.so |
| Scopes | read, write, sync, admin |
| Grant types | authorization_code with PKCE, and refresh_token |
| PKCE method | S256 only |
Read the endpoints from the authorization server metadata document rather than hardcoding them.
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, orhttpon a loopback host:127.0.0.1,localhostor[::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_idURL. 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.soresourceis required and must be exactlyhttps://api.notiondesk.so: lower case, no trailing slash, no path. A missing or different value answersinvalid_request.
scopeis a space-separated list. Ask only for the scopes your app uses.
stateprotects 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 role | Scopes your app can receive |
|---|---|
| Viewer or Billing Manager | read |
| Editor | read, write, sync |
| Admin or workspace owner | read, 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/callbackLeave 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_TOKENEach 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.
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."}error | Status | When |
|---|---|---|
invalid_request | 400 | A 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_client | 401 | The client secret is missing or wrong |
invalid_grant | 400 | The 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_error | 500 | Notiondesk 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.