Guides
Connect to TallyPip
Put a “Connect to TallyPip” button in your app. People sign in to TallyPip, choose a business and press Allow; your app gets tokens for that business, with no keys to copy. It's standard OAuth 2.0 (the authorization code flow), so most OAuth libraries work as they are.
How it works
- Register your app: its name, website and the addresses TallyPip may send people back to. You get a client id and a client secret.
- Your app sends the person to TallyPip's Allow page.
- They sign in, see your app's name and what it may do, choose one of their businesses and press Allow.
- TallyPip sends them back to your app with a one-time code.
- Your server swaps the code for an access token (for the API) and a refresh token (for new access tokens).
A connection is for one business and acts as the person who connected it, with their role there. Only a business's owner or an admin can connect an app. To connect another business, send them through again: each business gets its own tokens.
A new app is in development until TallyPip makes it live: until then only you can connect it, to your own businesses (a sandbox is perfect). See Your apps for going live.
| Address | What for |
|---|---|
https://tallypip.com/oauth/authorize | The Allow page (a link the person opens) |
https://tallypip.com/oauth/token | Swap a code or a refresh token for tokens (from your server) |
https://tallypip.com/oauth/revoke | Disconnect (from your server) |
2. They come back with a code
When they press Allow, TallyPip sends them to your redirect_uri with a code and your state:
https://yourapp.example.com/oauth/callback?code=Xy9…&state=b7f3c1e9a2
Check state is the one you made for this person. The code works once, for 10 minutes: swap it straight away. If they press Cancel, or something in the request was wrong, you get an error instead:
https://yourapp.example.com/oauth/callback?error=access_denied&error_description=The+person+didn%27t+allow+it.&state=b7f3c1e9a2
| error | Means |
|---|---|
access_denied | They pressed Cancel. |
invalid_request | Something was missing or wrong (a too-long state, a bad code_challenge). |
invalid_scope | scope wasn't read or write. |
unsupported_response_type | response_type wasn't code. |
temporarily_unavailable | TallyPip's API is paused for a short while. Try again later. |
3. Swap the code for tokens
From your server, swap the code. Say who your app is with HTTP Basic auth (client id and secret), or send client_id and client_secret in the form instead:
curl https://tallypip.com/oauth/token \ -u tpc_your_client_id:tps_your_client_secret \ -d grant_type=authorization_code \ -d code=Xy9… \ -d redirect_uri=https://yourapp.example.com/oauth/callback
Send redirect_uri if you sent one to the Allow page (it must be the same). The body can be form-encoded (the standard) or JSON. The answer:
{
"access_token": "tpo_…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "tpr_…",
"scope": "read write",
"business_id": "6f1c…",
"business_name": "Oak Street Dental"
}Save both tokens with the business id, on your server, like passwords. A scope of read write means the connection can add things; read means look only.
Calling the API
Use the access token exactly like a key:
curl https://tallypip.com/api/v1/me \ -H "Authorization: Bearer tpo_…"
Everything in the API guide works the same: the same endpoints, limits, idempotency and webhooks. GET /me shows "kind": "connection" and your app's name. The business sees your app under Settings › Connected apps, with when it was last used, and owners and admins get an email when it's connected.
Refreshing
An access token lasts an hour (expires_in). Before it runs out, or when the API answers 401, get new tokens with the refresh token:
curl https://tallypip.com/oauth/token \ -u tpc_your_client_id:tps_your_client_secret \ -d grant_type=refresh_token \ -d refresh_token=tpr_…
- The answer is the same shape, with a new refresh token every time. Save it in place of the old one: the old one stops working.
- The access token you had keeps working until its own hour is up, so requests already on their way don't fail.
- Each refresh token lasts 100 days, and every refresh gives a new one, so a connection that refreshes at least that often never runs out.
- Two refreshes at once? The second gets
invalid_grant; use the tokens from the first. But if an old refresh token is used again later, TallyPip assumes it was copied and stops the connection for safety; the person connects again. - Webhooks your app added stay through every refresh.
PKCE
PKCE (RFC 7636) makes a stolen code useless. TallyPip supports it for every app and we recommend it. Make a random code_verifier (43 to 128 letters, digits and -._~), and send its SHA-256, base64url-encoded without padding, as code_challenge with code_challenge_method=S256:
// Node.js
import { createHash, randomBytes } from "node:crypto";
const verifier = randomBytes(32).toString("base64url");
const challenge = createHash("sha256").update(verifier).digest("base64url");Then send code_verifier with the code when you swap it. Only S256 is accepted. Your client secret is still needed: TallyPip connections are for apps with a server.
Disconnecting
When someone disconnects inside your app, stop the connection with either token:
curl https://tallypip.com/oauth/revoke \ -u tpc_your_client_id:tps_your_client_secret \ -d token=tpr_…
It always answers 200 (even for a token that was already stopped). People can also disconnect your app in Settings › Connected apps; then the API answers 401 and refreshing gives invalid_grant. Show them a “Connect to TallyPip” button again.
A connection also stops working if the person who connected it leaves the business's team. If you delete your app on Your apps, every business is disconnected.
The apps directory
Live apps can be listed in the TallyPip apps directory, where businesses find apps by category. On Your apps, open Apps directory under your app and fill in:
- a square logo (PNG, JPEG or WebP; TallyPip makes it a 256 by 256 picture), also shown on the Allow page;
- who makes it, a category, one line about it and a description (what it does with TallyPip, and who it's for);
- the Get app address: the page in your app that starts connecting, which sends people to the Allow page (step 1);
- your privacy policy and a support email.
Then press Ask for review. We check the app and the listing and email you when it's published, at tallypip.com/apps/your-app. If you change the listing, logo, name or website later, the directory keeps showing the approved version until you ask for a review again and we publish the changes.
Errors
The token and revoke addresses answer errors as JSON (RFC 6749), with a plain-words error_description:
{ "error": "invalid_grant", "error_description": "That code has run out (codes last 10 minutes). Connect again." }| error | Means |
|---|---|
invalid_client (401) | The client id or secret is wrong. If you made a new secret, use that one. |
invalid_grant (400) | The code or refresh token is wrong, used, run out or disconnected; or the redirect_uri or code_verifier doesn't match. Ask the person to connect again. |
unsupported_grant_type (400) | grant_type must be authorization_code or refresh_token. |
slow_down (429) | Too many requests. Wait a minute (Retry-After). |
temporarily_unavailable (503) | TallyPip's API is paused for a short while. Try again later (Retry-After). |
Stuck? Email support@tallypip.com with your client id (never your secret).