BrightSpark API
Everything you need to add BrightSpark authentication and Sparks rewards to your platform.
Overview
The BrightSpark API provides authentication (OAuth 2.0 / OIDC) and Sparks rewards management. There are two integration paths depending on your needs:
Full SSO
Replace your login with "Sign in with BrightSpark". Best for new platforms or those wanting to join the ecosystem fully.
See Full SSO guide ↓Sparks-Only
Keep your existing auth. Connect your users to BrightSpark with a one-time link, then issue Sparks via API. Best for established platforms.
See Sparks-Only guide ↓Base URL
https://brightspark.co.zwResponse Format
All API responses use a consistent JSON envelope:
// Success
{
"success": true,
"data": { ... }
}
// Error
{
"success": false,
"error": "Description of what went wrong"
}Credentials
You'll receive a client_id and client_secret when your partner account is set up. Keep your client secret secure — never expose it in client-side code.
Authentication
The API uses two authentication methods depending on the endpoint:
Client Credentials (Basic Auth)
Used for server-to-server calls (earn API, reward listings). Send your credentials as a Base64-encoded Basic auth header.
const credentials = Buffer.from(
`${clientId}:${clientSecret}`
).toString('base64');
fetch('https://brightspark.co.zw/api/v1/sparks/earn', {
method: 'POST',
headers: {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ ... }),
});Bearer Token
Used for user-context calls (spend, balance, history). The token is the user's BrightSpark access token, obtained through the OAuth flow.
fetch('https://brightspark.co.zw/api/v1/sparks/balance', {
headers: {
'Authorization': `Bearer ${accessToken}`,
},
});Integration Path: Full SSO
Replace your platform's login with "Sign in with BrightSpark". Your users authenticate via BrightSpark and you receive their profile and access token. Best for new platforms or those joining the ecosystem fully.
BrightSpark implements the OAuth 2.0 Authorization Code flow with PKCE support.
Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
| /auth/authorize | GET | Start authorization |
| /auth/token | POST | Exchange code for tokens |
| /auth/userinfo | GET | Get user profile |
| /auth/revoke | POST | Revoke a token |
| /.well-known/openid-configuration | GET | OIDC discovery |
| /api/v1/auth/jwks | GET | JSON Web Key Set |
Authorization Request
GET /auth/authorize?
response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.com/callback
&scope=openid profile email sparks
&state=random_state_value
&code_challenge=PKCE_CHALLENGE
&code_challenge_method=S256Token Exchange
POST /auth/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)
grant_type=authorization_code
&code=AUTHORIZATION_CODE
&redirect_uri=https://yourapp.com/callback
&code_verifier=PKCE_VERIFIERAvailable Scopes
| Scope | Access |
|---|---|
| openid | Required. Returns ID token with subject identifier. |
| profile | Display name, avatar |
| Email address and verification status | |
| phone | Phone number and verification status |
| sparks | Sparks balance, spend API access |
| sparks:history | Transaction history |
Integration Path: Sparks-Only (Keep Your Auth)
Already have your own authentication? Keep it. Use a one-time account linking flow to connect your users to BrightSpark, then issue Sparks via API using your server-side credentials.
This is ideal for established platforms (retailers, large organisations) that don't want to change their login flow but want to participate in the Sparks ecosystem.
How It Works
User opts in
Your app shows a "Join Sparks Rewards" button or banner. This is the user's first and only interaction with BrightSpark.
Redirect to BrightSpark
When the user clicks, redirect them to BrightSpark's authorization endpoint. Pre-fill their phone number or email if you have it — this speeds up the flow.
User consents
The user either signs in to an existing BrightSpark account or creates a new one. They see a consent screen: "PnP will earn Sparks for you when you shop." They approve with one tap.
You receive their user ID
BrightSpark redirects back to your app with an authorization code. Exchange it for tokens and extract the user's BrightSpark ID from the sub claim. Store this ID against the user in your database.
Issue Sparks via API
From now on, use your client credentials and the stored user ID to call the earn API. The user never sees BrightSpark again unless they choose to visit their dashboard.
Linking Request
Use the standard authorization endpoint with scope=openid sparks. Add login_hint to pre-fill the user's phone number or email.
// Redirect user to BrightSpark (one-time)
const linkUrl = new URL(
'https://brightspark.co.zw/auth/authorize'
);
linkUrl.searchParams.set('response_type', 'code');
linkUrl.searchParams.set('client_id', CLIENT_ID);
linkUrl.searchParams.set('redirect_uri',
'https://yourapp.com/sparks/callback');
linkUrl.searchParams.set('scope', 'openid sparks');
linkUrl.searchParams.set('state', sessionState);
linkUrl.searchParams.set('login_hint',
user.phone); // or user.email — pre-fills login
// Redirect user
window.location.href = linkUrl.toString();Callback Handler
// Your callback endpoint
// Exchange code for tokens
const tokenRes = await fetch(
'https://brightspark.co.zw/auth/token',
{
method: 'POST',
headers: {
'Authorization': `Basic ${credentials}`,
'Content-Type':
'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: authorizationCode,
redirect_uri:
'https://yourapp.com/sparks/callback',
}),
}
);
const { access_token, id_token } =
await tokenRes.json();
// Decode ID token to get BrightSpark user ID
const payload = decodeJwt(id_token);
const brightsparkUserId = payload.sub;
// Store this against your user — you'll need
// it for all future earn API calls
await db.user.update({
where: { id: yourUserId },
data: { brightsparkId: brightsparkUserId },
});Then Issue Sparks
Use the stored brightsparkId with your client credentials. No user token needed — this is a server-to-server call.
// Server-side — called whenever user
// earns Sparks on your platform
await fetch(
'https://brightspark.co.zw/api/v1/sparks/earn',
{
method: 'POST',
headers: {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
userId: user.brightsparkId,
eventType: 'purchase',
amount: 50,
idempotencyKey: `order_${orderId}`,
reason: 'Grocery purchase',
}),
}
);User Experience
After linking, you can show Sparks activity in your own UI. Use the balance and history APIs with the user's access token (stored from the initial linking). Or simply display a "You earned 50 Sparks!" notification after each earn call and link to brightspark.co.zw/dashboard for the full experience.
Earn Sparks
/api/v1/sparks/earn(Client credentials)Award Sparks to a user. You decide what actions earn Sparks and how many. BrightSpark validates that your client is authorised, your budget has capacity, and the user hasn't exceeded their daily/monthly caps.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| userId | string | Yes | BrightSpark user ID (from OAuth) |
| eventType | string | Yes | Your event identifier (e.g. "purchase", "checkin") |
| amount | integer | Yes | Sparks to award (positive integer) |
| idempotencyKey | string | No | Prevents double-crediting on retries |
| reason | string | No | Human-readable description |
| metadata | object | No | Arbitrary key-value pairs for your records |
Example
const credentials = Buffer.from(
`${CLIENT_ID}:${CLIENT_SECRET}`
).toString('base64');
const res = await fetch(
'https://brightspark.co.zw/api/v1/sparks/earn',
{
method: 'POST',
headers: {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
userId: 'cm1abc123...',
eventType: 'match_prediction',
amount: 25,
idempotencyKey: 'pred_match_42_user_abc',
reason: 'Correct prediction: CAPS vs Dynamos',
metadata: { matchId: '42', score: '2-1' },
}),
}
);
const { data } = await res.json();
// data.transaction.id — transaction reference
// data.balance — user's new Sparks balanceResponse (201)
{
"success": true,
"data": {
"transaction": {
"id": "cm1xyz789...",
"type": "EARN",
"amount": 25,
"balanceAfter": 475,
"createdAt": "2026-03-21T10:00:00.000Z"
},
"balance": 475
}
}Spend Sparks
/api/v1/sparks/spend(Bearer token)Debit Sparks from a user's balance. Called when a user redeems a reward on your platform. The user must have sufficient balance.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| amount | integer | Yes | Sparks to debit (positive integer) |
| reason | string | Yes | What the user is redeeming |
| idempotencyKey | string | No | Prevents double-debiting on retries |
| metadata | object | No | Arbitrary data for your records |
Example
const res = await fetch(
'https://brightspark.co.zw/api/v1/sparks/spend',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${userAccessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 500,
reason: 'Premium content unlock: Season Review',
idempotencyKey: 'reward_content_123_user_abc',
}),
}
);Response (200)
{
"success": true,
"data": {
"transaction": {
"id": "cm1def456...",
"type": "SPEND",
"amount": -500,
"balanceAfter": 1950,
"createdAt": "2026-03-21T10:05:00.000Z"
},
"balance": 1950
}
}Balance
/api/v1/sparks/balance(Bearer token (scope: sparks))Retrieve a user's current Sparks balance and lifetime totals.
Response (200)
{
"success": true,
"data": {
"sparksBalance": 2450,
"sparksLifetimeEarned": 5200,
"sparksLifetimeSpent": 2750
}
}Transaction History
/api/v1/sparks/history(Bearer token (scope: sparks or sparks:history))Retrieve a user's Sparks transaction history with optional filters.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| type | string | Filter by type: EARN, SPEND, BONUS, ADJUSTMENT, REFUND |
| client_id | string | Filter by issuing client |
| from | ISO date | Start date filter |
| to | ISO date | End date filter |
| page | integer | Page number (default: 1) |
| limit | integer | Results per page (default: 20, max: 100) |
Reward Listings
Register your rewards with BrightSpark so users can discover them in the ecosystem directory. This is optional — rewards live on your platform, BrightSpark just showcases them.
Register a Listing
/api/v1/rewards(Client credentials)Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Reward name |
| description | string | No | Short description |
| costSparks | integer | Yes | Sparks cost to redeem |
| redeemUrl | URL | Yes | Link to your redemption page |
| imageUrl | URL | No | Reward image for the directory |
| category | string | No | Category label |
Update a Listing
/api/v1/rewards(Client credentials)Same fields as above (all optional), plus id (required) and isActive (boolean).
List All Rewards
/api/v1/rewards(None (public))Returns all active reward listings. Add ?cross_platform=true to filter to cross-platform listings only.
Error Codes
| Status | Meaning | Common Causes |
|---|---|---|
| 400 | Bad Request | Invalid JSON, missing required fields, validation failure |
| 401 | Unauthorized | Missing or invalid credentials / token |
| 403 | Forbidden | Insufficient scope, client not authorised for this action |
| 404 | Not Found | User or resource doesn't exist |
| 422 | Unprocessable | Insufficient balance, budget exceeded, user cap reached |
| 429 | Rate Limited | Too many requests — back off and retry |
Idempotency
Always send an idempotencyKey on earn and spend requests. If a request is retried with the same key, BrightSpark returns the original result without processing the transaction again. Keys are unique globally and are valid for 24 hours.