Developer Documentation

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.zw

Response 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

EndpointMethodPurpose
/auth/authorizeGETStart authorization
/auth/tokenPOSTExchange code for tokens
/auth/userinfoGETGet user profile
/auth/revokePOSTRevoke a token
/.well-known/openid-configurationGETOIDC discovery
/api/v1/auth/jwksGETJSON 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=S256

Token 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_VERIFIER

Available Scopes

ScopeAccess
openidRequired. Returns ID token with subject identifier.
profileDisplay name, avatar
emailEmail address and verification status
phonePhone number and verification status
sparksSparks balance, spend API access
sparks:historyTransaction 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

1

User opts in

Your app shows a "Join Sparks Rewards" button or banner. This is the user's first and only interaction with BrightSpark.

2

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.

3

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.

4

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.

5

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

POST/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

FieldTypeRequiredDescription
userIdstringYesBrightSpark user ID (from OAuth)
eventTypestringYesYour event identifier (e.g. "purchase", "checkin")
amountintegerYesSparks to award (positive integer)
idempotencyKeystringNoPrevents double-crediting on retries
reasonstringNoHuman-readable description
metadataobjectNoArbitrary 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 balance

Response (201)

{
  "success": true,
  "data": {
    "transaction": {
      "id": "cm1xyz789...",
      "type": "EARN",
      "amount": 25,
      "balanceAfter": 475,
      "createdAt": "2026-03-21T10:00:00.000Z"
    },
    "balance": 475
  }
}

Spend Sparks

POST/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

FieldTypeRequiredDescription
amountintegerYesSparks to debit (positive integer)
reasonstringYesWhat the user is redeeming
idempotencyKeystringNoPrevents double-debiting on retries
metadataobjectNoArbitrary 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

GET/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

GET/api/v1/sparks/history(Bearer token (scope: sparks or sparks:history))

Retrieve a user's Sparks transaction history with optional filters.

Query Parameters

ParameterTypeDescription
typestringFilter by type: EARN, SPEND, BONUS, ADJUSTMENT, REFUND
client_idstringFilter by issuing client
fromISO dateStart date filter
toISO dateEnd date filter
pageintegerPage number (default: 1)
limitintegerResults 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

POST/api/v1/rewards(Client credentials)

Request Body

FieldTypeRequiredDescription
namestringYesReward name
descriptionstringNoShort description
costSparksintegerYesSparks cost to redeem
redeemUrlURLYesLink to your redemption page
imageUrlURLNoReward image for the directory
categorystringNoCategory label

Update a Listing

PATCH/api/v1/rewards(Client credentials)

Same fields as above (all optional), plus id (required) and isActive (boolean).

List All Rewards

GET/api/v1/rewards(None (public))

Returns all active reward listings. Add ?cross_platform=true to filter to cross-platform listings only.

Error Codes

StatusMeaningCommon Causes
400Bad RequestInvalid JSON, missing required fields, validation failure
401UnauthorizedMissing or invalid credentials / token
403ForbiddenInsufficient scope, client not authorised for this action
404Not FoundUser or resource doesn't exist
422UnprocessableInsufficient balance, budget exceeded, user cap reached
429Rate LimitedToo 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.

Need help?

Contact the BrightSpark team for integration support.

Contact Us