Authentication
How to authenticate with the stemp API using OAuth2 tokens and API keys.
Authentication
The stemp API supports two authentication methods: OAuth2 tokens (for third-party apps) and API tokens (for server-to-server integrations). All API requests must include valid credentials.
Authentication Methods
| Method | Use Case | Header Format |
|---|---|---|
| OAuth2 Bearer Token | Third-party apps acting on behalf of an organization | Authorization: Bearer <access_token> |
| API Token | Your own backend services and scripts | Authorization: Bearer <api_token> |
Access Tokens
After completing the OAuth2 flow, you receive an access token. Include it in the Authorization header:
Authorization: Bearer <access_token>Token Properties
| Property | Details |
|---|---|
| Format | JWT (RS256 signed) |
| Expiration | 30 minutes (1800 seconds) |
| Audience | stemp:api:app |
| Claims | installationId, organizationId, clientId, scopes |
Token Lifecycle
OAuth2 Flow → Access Token (30 min) → Expires → Refresh → New Access Token
↑
Refresh Token (30 days) ──┘Refreshing Tokens
When an access token expires, use the refresh token to get a new one:
curl -X POST https://api.stemp.app/api/v1/apps/oauth2/refresh \
-H "Content-Type: application/json" \
-d '{
"refresh_token": "srt_your_refresh_token"
}'The response includes a new access token:
{
"access_token": "eyJhbGciOiJSUzI1NiJ9...",
"token_type": "bearer",
"expires_in": 1800,
"scope": "pass:read pass:create loyalty:manage",
"refresh_token": null
}If refresh_token is not null, your existing refresh token is being rotated — start using the new one. Refresh tokens are valid for 30 days.
API Tokens
For server-to-server integrations that don't require the OAuth2 flow, use API tokens. They work the same way in the Authorization header:
Authorization: Bearer sk_live_abc123...Error Responses
| Status | Meaning | What to Do |
|---|---|---|
| 401 Unauthorized | Token is missing, expired, or invalid | Refresh the token or re-authenticate |
| 403 Forbidden | Token is valid but lacks the required scope | Request additional scopes or use a different token |
Example 401 Response
{
"error": {
"code": "unauthorized",
"message": "Access token is expired"
}
}Automatic Token Refresh
Implement automatic token refresh in your application to handle expiration seamlessly:
let accessToken = '...'
let refreshToken = 'srt_...'
async function authenticatedFetch(url: string, options: RequestInit = {}) {
let response = await fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${accessToken}`,
},
})
if (response.status === 401) {
// Token expired — refresh it
const refreshResponse = await fetch('https://api.stemp.app/api/v1/apps/oauth2/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh_token: refreshToken }),
})
const tokens = await refreshResponse.json()
accessToken = tokens.access_token
if (tokens.refresh_token) {
refreshToken = tokens.refresh_token
}
// Retry the original request
response = await fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${accessToken}`,
},
})
}
return response
}Security Best Practices
- Store tokens securely — never expose them in client-side code, URLs, or logs.
- Use short-lived access tokens — the 30-minute expiration limits the damage from token leaks.
- Refresh proactively — refresh tokens before they expire to avoid request failures.
- Request only the scopes your application needs — follow the principle of least privilege.
- Monitor for 401 responses — implement automatic refresh logic rather than failing on expired tokens.