Skip to main content
With an OAuth app, your internal portal can read a member’s notes on their behalf without each member issuing an API key. The member signs in to Tiro and passes a consent screen once, and the app then acts only within the permissions that member already has.

When should you use an OAuth app?

OAuth apps support the Authorization Code flow only, and PKCE is required. There is no client_credentials flow for obtaining a token without a user, so use an API key for server-to-server integrations.

Step 1. Register an OAuth app

Organization apps are registered by an organization Admin or Developer, and workspace apps by a workspace Admin, at Tiro Platform.
1

Open the OAuth apps screen

In the Tiro Platform side menu, open the [OAuth apps] menu for your organization or workspace and press [Register OAuth app].
2

Enter the app details

Enter the app name, the Redirect URI, and the scopes the app will request from users. The Redirect URI must start with https://, and it won’t register if the URL carries user info or a fragment after #. It has to match the URL you return to after authorization character for character, and it can’t be edited after registration, so register a new app if the URL changes.
3

Store the Client ID and Client Secret

Right after registration, the screen shows the Client ID and Client Secret. The Client Secret is shown this one time and can’t be retrieved again. Store it somewhere safe, such as a server environment variable, right away.
http://localhost can’t be registered as a Redirect URI. For local development, register an HTTPS tunnel or a development domain as the Redirect URI and let that address forward the callback to your machine.
The OAuth app registration screen is rolling out gradually. If you don’t see the menu, ask your account manager or partners@theplato.io and we’ll issue an app for you.
Once you close the registration screen, the Client Secret can’t be viewed anywhere again. If you lose it, register a new app or ask your account manager to reissue it. Discarding an app also disconnects every user connection made through it.

Scopes you can request

An OAuth app can request only the permissions a user is able to delegate. Organization management scopes (organization_member:*, workspace:*, session:write) can’t be delegated and are available to organization API keys only. What the app actually reaches is the overlap between the scopes the user consented to and the permissions that user holds in Tiro. Workspaces and notes the user can’t see stay out of reach no matter which scopes were granted. Send the user from your app to the Tiro authorization page. First generate a random code_verifier, then hash that string as UTF-8 bytes with SHA-256 and send the unpadded Base64URL encoding of the digest as code_challenge. Sending a hex string or the raw digest bytes gets rejected at the token exchange step.
After the user signs in with their Tiro account, reviews the app name and requested scopes on the consent screen, and approves, code and state come back to your Redirect URI.

Validate state on the callback

Check state before you exchange the token. Store the value you generated for the authorize request in the user’s session, compare it with the value that comes back on the callback, then clear the stored value. If it’s missing or different, stop the request right there. Skipping this check allows login CSRF: an authorization the attacker started gets attached to the victim’s session, connecting them to an account they know nothing about.
Members of organizations using SSO sign in through their corporate IdP as usual before the consent screen. The consent screen is scoped to that member’s Tiro account, and the app receives that account’s permissions only.

Step 3. Exchange the code for a token

Turn the validated code into an access token from your server. Handle the Client Secret on the server only and never put it in browser or app code. An authorization code works exactly once, so sending it twice is rejected with invalid_grant.
Client authentication works with both HTTP Basic and body parameters (client_id, client_secret).
expires_in is the remaining lifetime in seconds and varies with the app’s configuration. Don’t hardcode the value; use whatever comes back in the response. Apps that also receive a refresh_token should follow the refresh procedure below, and apps that don’t can send the user back to the authorization page before the token expires.

Step 4. Call the API

Put the access token in the Authorization header exactly as you would an API key. The endpoints you can call and the response formats match what’s covered in the API Overview.
The notes in the response are the ones the consenting user can see in the app. It’s the same result as calling with that user’s account API key.

Step 5. Refresh the token

If the token exchange response includes a refresh_token, you can get a new access token without asking the user to consent again.
  • A refresh token works exactly once. When the response carries a new refresh token, discard the previous value and store the new one.
  • Sending a refresh token that was already used is treated as theft, and every token on that user connection is invalidated. You then have to ask the user to consent again.
  • A connection left unrefreshed for a long time expires. Each refresh pushes the expiry further out, so connections in steady use stay alive.
  • Refreshes are rejected once the app has been discarded or the user has disconnected it.
If a network problem leaves you without a response to a refresh request, don’t send the same refresh token again. The server may have already rotated it. Clearing the stored token and prompting the user to reauthorize is the safer move.

Disconnect

When a user stops using the app, revoke the refresh token to disconnect. Access tokens already issued expire on their own once expires_in passes.

Server metadata

The endpoints and supported methods are published in the standard metadata document.

Common errors


Related pages: Authentication · Integrate at the organization level · API Overview