Authentication
All API calls send Authorization: Bearer <token>. There are three kinds of credential.
| Credential | Prefix | Obtained by | Bound to | Use for |
|---|---|---|---|---|
| OAuth access token | tps_at_live_ / tps_at_test_ |
Authorization code + PKCE | One business, the scopes it granted | Apps that businesses connect |
| Client-credentials token | tps_at_… |
grant_type=client_credentials |
Your application; reaches only businesses that connected it | Platform connectors, background jobs |
| Business API key | tps_sk_live_ / tps_sk_test_ |
Business → Integrations → API keys | One business | A business's own website or scripts |
Access tokens last 1 hour. Refresh tokens last 90 days and rotate: each refresh returns a new refresh token and invalidates the old one.
Authorization code with PKCE
- Create a PKCE pair (
code_verifier,code_challenge = BASE64URL(SHA256(verifier))). - Send the business owner's browser to:
https://pos.techtursolutions.com/oauth/authorize?response_type=code
&client_id=…&redirect_uri=…&scope=products:read%20orders:write
&state=…&code_challenge=…&code_challenge_method=S256
The owner signs in, chooses which of their businesses to connect (only businesses where their role holds integrations.manage), and approves the scopes. Sandbox apps can only be connected to sandbox businesses and live apps to live businesses.
- You receive
redirect_uri?code=…&state=…(orerror=access_denied). Checkstate. - Exchange the code (form-encoded):
curl -X POST https://pos.techtursolutions.com/api/oauth/token \
-d grant_type=authorization_code -d client_id=$CLIENT_ID \
-d code=$CODE -d redirect_uri=$REDIRECT_URI -d code_verifier=$VERIFIER
{ "access_token": "tps_at_live_…", "token_type": "Bearer", "expires_in": 3600,
"scope": "products:read orders:write", "refresh_token": "tps_rt_live_…", "business_id": "cm…" }
Codes are single-use and expire after 5 minutes. Replaying a used code revokes every token it produced (RFC 6749 §4.1.2). The redirect_uri must exactly match one registered on the application; registered URIs must be https, except http://localhost for development.
Refreshing
curl -X POST …/api/oauth/token -d grant_type=refresh_token -d client_id=$CLIENT_ID -d refresh_token=$RT
If the business has disconnected your app, refresh fails with invalid_grant.
Client credentials
curl -X POST …/api/oauth/token -u "$CLIENT_ID:$CLIENT_SECRET" -d grant_type=client_credentials -d scope="platform:identity products:write"
The token carries no business. When you call a business route, POS checks that the business has an active connection to your application and uses the intersection of the token's scopes and the scopes that business granted.
Revoking
POST /api/oauth/revoke with token, client_id, client_secret. Revoking the application in the console, or a business disconnecting it, revokes its tokens immediately.
Scopes
| Scope | Allows |
|---|---|
business:read |
Business profile, locations, registers |
products:read / products:write |
Catalogue read / create, update, sync |
inventory:read / inventory:write |
Stock levels / stock adjustments |
orders:read / orders:write |
Orders and sales / submit and update orders, refunds |
customers:read / customers:write |
Customers |
reports:read |
Sales summaries; also lets a token list all sales, not just its own |
webhooks:manage |
A business's own webhook endpoints |
platform:identity |
Mint launch tickets (platform connectors only) |
platform:businesses |
Resolve linked businesses (platform connectors only) |
platform:metrics |
Report platform metrics (platform connectors only) |
Dashboard-only routes (accounting, staff, settings) are never available to tokens.
Secrets
Client secrets are stored as Argon2id hashes and shown once. Rotate them in the console (the old secret stops working at once). Never put tokens in URLs, front-end bundles or logs.