Developer Documentation
Everything you need to add TinTorch to your app - from a one-click login button to full OpenID Connect and the Subscriptions API.
What you can build
Overview
TinTorch Account is a standard OpenID Connect provider. Any compliant OIDC/OAuth 2.0 client library can integrate with it - point your library at the discovery document and it auto-configures every endpoint below. Tokens are signed with RS256 and verifiable via the JWKS endpoint.
| Issuer | https://account.tintorch.com |
| Discovery | https://account.tintorch.com/.well-known/openid-configuration |
| Authorization | https://account.tintorch.com/sso/authorize |
| Token | https://account.tintorch.com/api/sso/token |
| UserInfo | https://account.tintorch.com/api/sso/userinfo |
| JWKS | https://account.tintorch.com/api/sso/jwks |
Scopes
Request the scopes your app needs. openid is always required; add offline_access to receive a refresh token.
openidVerify your identityprofileYour name, username, photo, locale and timezoneemailYour email address and verification statusphoneYour phone numberorganizationsThe workspaces you belong to and your rolesoffline_accessStay signed in and refresh access without re-authenticatingHow the flow works
- 1. Redirect the user to the authorization endpoint with your
client_id,redirect_uri,response_type=code, ascope, a randomstate, and (for public clients) a PKCEcode_challenge. - 2. The user signs in and consents. TinTorch redirects back to your
redirect_uriwith acodeand yourstate. - 3. Your server exchanges the code at the token endpoint for an
access_token,id_token, and (withoffline_access) arefresh_token. - 4. Verify the
id_tokensignature against the JWKS, or call the UserInfo endpointwith the access token to get the user's profile.
Sign in and open the Developer section to register an app and get a client_id and client_secret.
Quickstart
The fastest way to add TinTorch login: drop in one script tag and a button. We open a small popup, the user confirms, and you get their profile back - no backend, no redirects, no library. This uses a public (PKCE) client, so there is no client secret to manage.
1Register a public client
In the Developer section, create an app as a public client (PKCE, no secret) and add this exact redirect URI:
https://account.tintorch.com/sso/popup-callback2Drop in the button
Add the script and any element with data-tintorch-login. The script is served from TinTorch, so it always stays up to date.
<script src="https://account.tintorch.com/tintorch.js" data-client-id="YOUR_CLIENT_ID"></script>
<button data-tintorch-login>Sign in with TinTorch</button>
<script>
window.addEventListener("tintorch:login", (e) => {
const { user, accessToken } = e.detail;
console.log("Signed in:", user); // { sub, name, email, picture, ... }
// ...create a session, redirect, etc.
});
window.addEventListener("tintorch:error", (e) => {
console.error("Login failed:", e.detail.message);
});
</script>3Or trigger it from your own code
Prefer your own button? Call TinTorch.signIn() from a click handler - it resolves with the signed-in user.
<script src="https://account.tintorch.com/tintorch.js"></script>
<script>
document.querySelector("#login").addEventListener("click", async () => {
try {
const { user, accessToken, idToken } = await TinTorch.signIn({
clientId: "YOUR_CLIENT_ID",
scope: "openid profile email",
});
console.log(user);
} catch (err) {
console.error(err.message); // "popup_closed", "popup blocked", ...
}
});
</script>4React / SPA
Load the script once and call the global from any component.
import { useEffect, useCallback } from "react";
const SRC = "https://account.tintorch.com/tintorch.js";
export function LoginButton() {
useEffect(() => {
if (document.querySelector(`script[src="${SRC}"]`)) return;
const s = document.createElement("script");
s.src = SRC;
document.head.appendChild(s);
}, []);
const onClick = useCallback(async () => {
const { user, accessToken } = await window.TinTorch.signIn({
clientId: "YOUR_CLIENT_ID",
});
// ...send accessToken to your backend to create a session
}, []);
return <button onClick={onClick}>Sign in with TinTorch</button>;
}Security: the popup uses OAuth 2.0 authorization code + PKCE with a per-request state nonce. The token is exchanged from the browser (public client) - for higher-trust apps, send the returned accessToken to your backend and verify it against the JWKS, or use the Next.js / Node.js server-side flows instead.
Most libraries auto-configure from the discovery document at https://account.tintorch.com/.well-known/openid-configuration.
Workspaces
A user belongs to one or more workspaces (their personal one, plus any team they own or were invited to). Read them with the signed-in user's access token to build your own workspace switcher - including the image the owner uploaded in TinTorch Account, so the icon matches everywhere.
curl https://account.tintorch.com/api/organizations \
-H "Authorization: Bearer USER_ACCESS_TOKEN"
# Response:
# {
# "organizations": [
# {
# "id": "clx…",
# "slug": "acme-1a2b3c",
# "name": "Acme Inc.",
# "image": "https://…/workspaces/clx….png",
# "personal": false,
# "role": "ADMIN",
# "memberCount": 7
# }
# ]
# }The same list (minus memberCount) ships in the organizations claim when you request that scope, so a token you already hold may be enough. Membership itself is managed in TinTorch Account - workspace owners invite people by email and the invitation is accepted there.
Subscriptions
If your app is a first-party product with plans, start a subscription from your backend using the signed-in user's access token. We resolve your product from the token's client, so a user can only be subscribed to your plans. Free plans subscribe immediately; paid plans return a hosted checkout url to redirect the user to.
curl -X POST https://account.tintorch.com/api/subscriptions/checkout \
-H "Authorization: Bearer USER_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"plan": "pro",
"successUrl": "https://your-app.com/billing/done",
"cancelUrl": "https://your-app.com/billing"
}'
# Response:
# { "url": "https://.../checkout", "message": null } # paid → redirect to url
# { "url": null, "message": "Subscribed to ..." } # free → already activeBody: plan (the plan key) or planId; optional organizationId(defaults to the user's workspace) and successUrl/cancelUrl. The token must include the organizations scope context; plans are billed through whichever gateway each plan is configured with.