Appearance
Authentication Guide
The Stackd API uses Laravel Sanctum personal access tokens. Every request to a tenant-scoped endpoint must carry a valid token in the Authorization header.
Issuing a Token
Tokens are issued from within the TenantAdmin panel or via the API itself.
Via TenantAdmin (recommended for operators)
- Log in to your TenantAdmin at
https://{slug}.yourloyalty.app/admin - Navigate to Settings → API Tokens
- Click New Token, give it a descriptive name, and select the scopes you need
- Copy the token immediately — it is shown once only
Via API
If you are provisioning tokens programmatically (e.g. in a CI pipeline or integration bootstrap script), use the token endpoint. This requires an active web session, not a token — so it is suited to server-to-server provisioning flows where the calling service has already authenticated via the web login.
http
POST /api/v1/tokens
Content-Type: application/json
Authorization: Bearer <existing-token-with-any-scope>
{
"name": "My Integration",
"scopes": ["customers:read", "transactions:write"]
}Response (201):
json
{
"data": {
"id": 7,
"name": "My Integration",
"token": "8|aBcDeFgHiJkLmNoPqRsTuVwXyZ...",
"abilities": ["customers:read", "transactions:write"],
"created_at": "2026-05-20T09:00:00+02:00"
}
}Store the token value. It will not be returned again.
Using a Token
Pass the token as a Bearer token in every API request:
http
GET /api/v1/customers
Authorization: Bearer 8|aBcDeFgHiJkLmNoPqRsTuVwXyZ...Scopes
Each token has one or more scopes (called "abilities" in Sanctum). A request to a scope-protected endpoint with a token that lacks the required scope returns 403 Forbidden.
| Scope | Endpoints covered |
|---|---|
customers:read | GET /customers, GET /customers/{ulid}, GET /customers/{ulid}/transactions |
customers:write | POST /customers, PATCH /customers/{ulid} |
transactions:write | GET /transactions/{ulid}, POST /transactions/{ulid}/void |
rewards:read | GET /rewards, POST /redemptions, GET /redemptions/{ulid}, POST /redemptions/{ulid}/fulfil |
coupons:read | GET /coupons, POST /coupons/{ulid}/redeem, GET /coupons/{ulid}/redemptions |
reports:read | GET /reports/summary, GET /reports/customers/top, GET /reports/coupons |
The GET /api/v1/tokens, POST /api/v1/tokens, and DELETE /api/v1/tokens/{id} endpoints require only a valid authenticated session — no specific scope.
Platform endpoints (/api/v1/platform/*) require the caller to be authenticated as a super-admin user. They do not use token scopes.
Principle of least privilege: issue tokens with only the scopes your integration actually needs. A read-only reporting integration should carry only reports:read.
Rate Limits
| Authentication state | Limit |
|---|---|
| Authenticated (valid token) | 60 requests / minute per user |
| Unauthenticated | 10 requests / minute per IP |
When you exceed the limit, the API returns 429 Too Many Requests with a Retry-After header indicating how many seconds to wait.
http
HTTP/1.1 429 Too Many Requests
Retry-After: 42
Content-Type: application/problem+json
{
"type": "https://httpstatuses.com/429",
"title": "Too Many Requests",
"status": 429,
"detail": "Too Many Attempts."
}Revoking a Token
http
DELETE /api/v1/tokens/{id}
Authorization: Bearer <any-valid-token>Use GET /api/v1/tokens to list your tokens and find the id.
Security Recommendations
- Never commit tokens to source control. Use environment variables or a secrets manager.
- Rotate tokens periodically. Issue a new token, update your integration, then revoke the old token.
- Use narrow scopes. A compromised
customers:readtoken cannot void transactions. - Use HTTPS only. All requests must use TLS. Plain HTTP is rejected.