TattooBookingDevelopers

OAuth 2.0

If you're building a product that ANY TattooBooking tenant could sign up to connect (a SaaS marketplace integration, a multi-tenant CRM, a Zapier-style platform), use OAuth 2.0 to let users authorize your app without ever sharing an API key with you.

Conformance: RFC 6749 (core), RFC 7636 (PKCE — required), RFC 7009 (revocation), RFC 7662 (introspection — first-party only), RFC 8414 (server metadata).

1. Register your app

OAuth apps are registered by TattooBooking super-admins today. To register yours, email developers@tattoobooking.com with:

  • App name + description + homepage URL + logo URL
  • List of redirect URIs your app uses (exact-match strings; subdomain wildcards not supported)
  • Requested scopes — be minimal, ask for only what you need

You'll receive:

  • client_id — public, starts with tboa_
  • client_secret — keep secret, starts with tbosk_

2. Direct users to authorize

https://api.tattoobooking.com/oauth/authorize
  ?response_type=code
  &client_id=tboa_xxxxxxxxxxxx
  &redirect_uri=https://yourapp.com/oauth/callback
  &scope=read:clients%20write:appointments
  &state=<csrf-random>
  &code_challenge=<base64url(sha256(verifier))>
  &code_challenge_method=S256

PKCE is required — generate a random code_verifier (43–128 chars), hash it with SHA-256, base64url-encode for the challenge, and stash the verifier server-side keyed by state.

3. User approves at the consent screen

If the user isn't logged in to TattooBooking, they'll be prompted to sign in first. Then they see a consent screen showing your app's name + logo + the scopes you're requesting + which tenant they're authorizing.

On Approve, they're 302-redirected to your redirect_uri with?code=<authcode>&state=<original-state>.

On Deny, redirected with ?error=access_denied&state=<original-state>.

4. Exchange the code for tokens

POST https://api.tattoobooking.com/oauth/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <base64(client_id:client_secret)>

grant_type=authorization_code
&code=<authcode-from-redirect>
&redirect_uri=https://yourapp.com/oauth/callback
&code_verifier=<the-verifier-you-stashed>
HTTP/1.1 200 OK
{
  "access_token": "tboat_xxxxxxxxxxxx",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "tbort_xxxxxxxxxxxx",
  "scope": "read:clients write:appointments"
}

Authorization codes are single-use and expire in 60s. If you accidentally try to redeem a code twice, the entire grant is revoked (security countermeasure against intercepted codes).

5. Call the API

curl https://api.tattoobooking.com/v1/clients \
  -H "Authorization: Bearer tboat_xxxxxxxx"

Same /v1/* surface, same scopes, same rate-limit tier — the only difference from API keys is the token format.

6. Refresh when expired

Access tokens live 1 hour. When you get a 401 with code: expired_access_token, refresh:

POST /oauth/token

grant_type=refresh_token
&refresh_token=<your-current-refresh-token>
&client_id=tboa_xxx
&client_secret=tbosk_xxx

Each refresh issues a NEW refresh token and invalidates the old one (rotation). If you ever present an already-rotated refresh token, we treat it as a stolen-token replay and revoke the entire grant. Make sure your code always replaces the stored refresh token with the new one returned by /oauth/token.

7. Revoke when done

POST /oauth/revoke

token=<access-or-refresh-token>
&token_type_hint=refresh_token
&client_id=tboa_xxx
&client_secret=tbosk_xxx

Revoking a refresh token cascades to all live access tokens in the same grant. Per RFC 7009, the response is always 200 — we never leak whether a given token was valid.

Server metadata discovery (RFC 8414)

Get the full set of endpoints + supported grants from one URL — useful for plugging us into an OAuth client library that knows how to read it.

GET https://api.tattoobooking.com/.well-known/oauth-authorization-server

Security notes

  • Always use HTTPS for your redirect_uri. We reject HTTP redirect_uris in production.
  • Validate state on the redirect. Match it against what you sent — protects against CSRF.
  • Don't embed client_secret in mobile apps. Public clients (mobile, SPAs) use PKCE without a secret. If you need a confidential client, run a backend that holds the secret.