Lightning Payroll API Admin Setup Back to API documentation →
Partner integration guide

Provision, brand, monitor.

For Lightning Payroll partners who have been given an API-admin account and need to provision, brand, and manage their integration client. Swagger has the endpoint-by-endpoint contract; this guide is the recommended setup flow and the operational detail partners actually need while integrating.

18 endpoints 2 branding objects 90 day log retention bearer admin token

00What an API admin is

An API admin is a Lightning Payroll customer account that is allowed to own an OAuth client and manage partner-facing integration settings. That account can:

  • provision its OAuth client ID and client secret
  • manage registered redirect URIs
  • configure partner branding shown during hosted login and onboarding flows
  • inspect linked customer connections
  • revoke linked customer connections
  • review connection activity and error summaries for its client

01Before you start

You will need:

  1. a Lightning Payroll customer account that has been granted API-admin access
  2. the ability to sign in as that API-admin account
  3. a normal Lightning Payroll bearer token for that signed-in account, obtained as described under "Fetching that bearer token" below

This guide is about partner setup and management. It is separate from the OAuth client_id / client_secret flow your customers use when they authorise your integration; see the OAuth Authentication Guide for that.

Base URLs
BASE_URL="https://<your-au-sandbox-api-host>"     # AU sandbox
# BASE_URL="https://<your-nz-sandbox-api-host>"  # NZ sandbox

02Authentication for these endpoints

These endpoints do not use client_id and client_secret. They require a normal authenticated Lightning Payroll bearer token for the API-admin customer account: think of this as the API token for your own partner account, not the OAuth client credentials used later by customer authorisation.

Rate limits on /api are counted per API client rather than per IP address, so running your integration from a single server does not put all of your end customers in one bucket. The per-tier figures, and the two client-side habits that keep you well inside them, are in the OAuth Authentication Guide under "Rate limits".

Every request
BASE_URL="https://<your-au-sandbox-api-host>"     # AU sandbox
# BASE_URL="https://<your-nz-sandbox-api-host>"  # NZ sandbox
ADMIN_TOKEN="<lightning-payroll-api-admin-access-token>"

-H "Authorization: Bearer $ADMIN_TOKEN"

If the caller is not an API admin, the server returns:

403
{"detail": "Forbidden"}

Fetching that bearer token

ADMIN_TOKEN above is a session token for your own partner account. It is not issued by the OAuth flow, and the endpoints that mint it are the app's own session endpoints rather than published /api routes, so they do not appear in Swagger. They are documented here because partner integrations need them.

Send account_type=admin. An email address can name an administrator on one account and a payroll user on another, and when it names both, login asks which one you meant rather than guessing, because the two resolve to different payroll databases. A browser can answer that; a script cannot. The field states the identity up front so the question is never asked. Only admin and payroll are accepted, lowercase; any other value is ignored rather than rejected, so a typo behaves as though you sent nothing.

Note the two endpoints take different encodings. POST /login is form-encoded:

POST/login
POST /login
curl -sS -X POST "$BASE_URL/login" \
  -d "username=partner@example.com" \
  -d "password=<password>" \
  -d "account_type=admin"

If the account has two-factor authentication switched on, which is the default, this returns {"success": true, ...} and emails a six-digit code rather than a token. Exchange the code at POST /otp/verify, which is JSON:

POST/otp/verify
POST /otp/verify
curl -sS -X POST "$BASE_URL/otp/verify" \
  -H "Content-Type: application/json" \
  -d '{
        "username": "partner@example.com",
        "password": "<password>",
        "otp": "123456",
        "account_type": "admin"
      }'

The response carries the bearer token:

200 OK
{"access_token": "<lightning-payroll-api-admin-access-token>"}

An emailed code expires after 10 minutes and is consumed on use. If your partner account instead has an authenticator secret configured, otp is the current six-digit TOTP value and no email is sent.

Two operational notes, because both have bitten real integrations. Cache the token. It is valid for 30 minutes; re-authenticating per API call wastes a round trip and will eventually rate-limit you. Login is rate limited separately from /api, and more tightly. It is keyed on IP rather than on your OAuth client, so the partner tiers described in the OAuth Authentication Guide do not apply to it. A 429 mentioning a limit far below the /api figures is almost always the token fetch rather than the call you were making.

03Recommended setup sequence

1

GET /api/init-api-client

2

POST /api/update-api-client

3

Upload optional branding assets.

4
5

Use overview, connections, activity, and errors endpoints for monitoring.

04Two response objects you will see often

Many API-admin responses include two branding objects:

  • configured_branding: the branding values saved directly on your API-admin account
  • effective_branding: the branding Lightning Payroll will actually use after fallbacks and overrides are applied

If you are simply configuring your own partner branding, these two objects will usually match once setup is complete.

05Endpoint summary

MethodPathPurpose
GET/api/init-api-clientCreate the OAuth client if missing, or return the existing client
POST/api/update-api-clientUpdate redirect URIs, rotate secret, timezone, and branding metadata
GET/api/api-client/whitelabel-logoRead primary logo metadata
POST/api/api-client/whitelabel-logoUpload primary logo
DELETE/api/api-client/whitelabel-logoDelete primary logo
GET/api/api-client/whitelabel-dark-logoRead dark-mode logo metadata
POST/api/api-client/whitelabel-dark-logoUpload dark-mode logo
DELETE/api/api-client/whitelabel-dark-logoDelete dark-mode logo
GET/api/api-client/whitelabel-styleRead stylesheet metadata and CSS text
POST/api/api-client/whitelabel-styleUpload stylesheet
DELETE/api/api-client/whitelabel-styleDelete stylesheet
GET/api/public-brandingResolve public branding by client_id or branding_token
GET/api/public-branding/logoFetch public logo bytes
GET/api/api-client/overviewRoll-up metrics for the API client
GET/api/api-client/connectionsList connected customer accounts
DELETE/api/api-client/connections/{customer_id}Revoke all refresh tokens for one connected customer
GET/api/api-client/activityRecent authorization-code and refresh-token activity
GET/api/api-client/errorsRecent error logs and summary

061) Initialise or fetch the API client

This is the safest first call to make. If your client does not exist yet, Lightning Payroll creates it. If it already exists, Lightning Payroll returns the current configuration instead.

GET/api/init-api-client

Request

GET /api/init-api-client
curl -sS "$BASE_URL/api/init-api-client" \
  -H "Authorization: Bearer $ADMIN_TOKEN"

First-time response

200 OK (client created)
{
  "status": "Client created",
  "timezone": "UTC",
  "configured_branding": {
    "displayName": "",
    "subtitle": "",
    "supportEmail": "",
    "supportPhone": "",
    "homepageUrl": "",
    "supportUrl": "",
    "disableDarkMode": false,
    "publicBrandingToken": "public-branding-token",
    "authDesign": null
  },
  "farm_focus_host_override": {
    "canManage": false,
    "overrideUrl": ""
  },
  "effective_branding": {
    "source_type": "none",
    "source_customer_id": null,
    "source_client_id": null,
    "branding_token": null,
    "add_on_key": null,
    "add_on_label": "",
    "display_name": "",
    "subtitle": "",
    "company_name": "Lightning Payroll",
    "support_email": "support@lightningpayroll.com.au",
    "support_phone": "1300 515 895",
    "homepage_url": "",
    "support_url": "",
    "has_logo": false,
    "has_dark_logo": false,
    "has_favicon": false,
    "has_style": false,
    "disable_dark_mode": false,
    "uses_standalone_shell": false
  },
  "client": {
    "clientId": "your-client-id",
    "redirectUris": [],
    "hasClientSecret": true,
    "clientSecret": "plain-text-secret-shown-once"
  }
}

email_domain is absent from this branch. It appears only once the client exists, so treat it as optional and read it from the existing-client response below or from GET /api/api-client/email-domain.

Existing-client response

200 OK (client already exists)
{
  "status": "Client already exists",
  "timezone": "Australia/Brisbane",
  "configured_branding": {
    "displayName": "Farm Focus",
    "subtitle": "Powered by Lightning Payroll",
    "supportEmail": "support@example.com",
    "supportPhone": "1300 000 111",
    "homepageUrl": "https://partner.example.com",
    "supportUrl": "https://partner.example.com/support",
    "disableDarkMode": false,
    "publicBrandingToken": "public-branding-token",
    "authDesign": null
  },
  "email_domain": {
    "sendingDomain": "mail.example.com",
    "status": "verified",
    "verifiedAt": "2026-05-02T04:11:07",
    "fromLocalPart": "no-reply",
    "replyTo": "support@example.com",
    "fromEmail": "no-reply@mail.example.com",
    "emailAccentColor": "#00b1dd",
    "emailHideLpAttribution": false,
    "mailgunConfigured": true
  },
  "farm_focus_host_override": {
    "canManage": true,
    "overrideUrl": "https://farmfocus.example.com"
  },
  "effective_branding": {
    "source_type": "self",
    "source_customer_id": 12345,
    "source_client_id": null,
    "branding_token": "public-branding-token",
    "add_on_key": null,
    "add_on_label": "",
    "display_name": "Farm Focus",
    "subtitle": "Powered by Lightning Payroll",
    "company_name": "Farm Focus Pty Ltd",
    "support_email": "support@example.com",
    "support_phone": "1300 000 111",
    "homepage_url": "https://partner.example.com",
    "support_url": "https://partner.example.com/support",
    "has_logo": true,
    "has_dark_logo": true,
    "has_favicon": true,
    "has_style": true,
    "disable_dark_mode": false,
    "uses_standalone_shell": false
  },
  "client": {
    "clientId": "your-client-id",
    "redirectUris": [
      "https://partner.example.com/oauth/callback"
    ],
    "hasClientSecret": true
  }
}

Important behaviour

  • clientSecret is returned only when the client is first created.
  • If a client already exists, the plaintext secret is not returned again.
  • If the saved timezone is invalid, the response falls back to UTC.
  • A branding row is created automatically the first time this endpoint runs.
  • For many partners, this endpoint and POST /api/update-api-client are the only setup calls needed before moving to OAuth.

072) Update client settings

Use this endpoint to maintain your callback URLs, rotate the secret, and update the branding metadata used in hosted flows.

POST/api/update-api-client

Supported request fields

FieldTypeNotes
redirect_urisstring[]Must be a JSON array. These are stored as exact-match callback URIs.
regenerate_secretbooleanWhen true, rotates the client secret and returns the new plaintext secret once.
timezonestringIANA timezone name, for example UTC or Australia/Brisbane.
branding_display_namestringPartner brand name shown in hosted flows.
branding_subtitlestringSecondary line used in hosted flows.
branding_support_emailstringSupport contact shown in branding payloads.
branding_support_phonestringSupport phone shown in branding payloads.
homepage_urlstringPartner homepage URL.
support_urlstringPartner support URL.
disable_dark_modebooleanHints that hosted pages should avoid dark mode.
auth_designobjectLogin-screen design blob, echoed through GET /api/public-branding. null resets it.
from_local_partstringLocal part of the From address on your sending domain, so no-reply gives no-reply@mail.example.com.
reply_tostringReply-To address used on branded email.
email_accent_colorstringHex colour used in branded email, for example #00b1dd.
email_hide_lp_attributionbooleanSuppresses the Lightning Payroll attribution line in branded email.
farm_focus_host_override_urlstringOverrides the host Farm Focus links point at. Ignored unless your account may manage it.

Every field is optional and applied only when the key is present, so a partial payload leaves everything it omits untouched. Sending a key as null (or "" for the text fields) clears it.

Request

POST /api/update-api-client
curl -sS "$BASE_URL/api/update-api-client" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{
    "redirect_uris": [
      "https://partner.example.com/oauth/callback",
      "https://partner.example.com/oauth/callback-alt"
    ],
    "timezone": "Australia/Brisbane",
    "branding_display_name": "Farm Focus",
    "branding_subtitle": "Powered by Lightning Payroll",
    "branding_support_email": "support@farmfocus.example",
    "branding_support_phone": "1300 000 111",
    "homepage_url": "https://partner.example.com",
    "support_url": "https://partner.example.com/support",
    "disable_dark_mode": false
  }'

Response

200 OK
{
  "message": "Client updated successfully",
  "new_secret": "unchanged",
  "timezone": "Australia/Brisbane",
  "configured_branding": {
    "displayName": "Farm Focus",
    "subtitle": "Powered by Lightning Payroll",
    "supportEmail": "support@farmfocus.example",
    "supportPhone": "1300 000 111",
    "homepageUrl": "https://partner.example.com",
    "supportUrl": "https://partner.example.com/support",
    "disableDarkMode": false,
    "publicBrandingToken": "public-branding-token",
    "authDesign": null
  },
  "email_domain": {
    "sendingDomain": "mail.example.com",
    "status": "verified",
    "verifiedAt": "2026-05-02T04:11:07",
    "fromLocalPart": "no-reply",
    "replyTo": "support@example.com",
    "fromEmail": "no-reply@mail.example.com",
    "emailAccentColor": "#00b1dd",
    "emailHideLpAttribution": false,
    "mailgunConfigured": true
  },
  "farm_focus_host_override": {
    "canManage": true,
    "overrideUrl": "https://farmfocus.example.com"
  },
  "effective_branding": {
    "source_type": "self",
    "source_customer_id": 12345,
    "source_client_id": null,
    "branding_token": "public-branding-token",
    "add_on_key": null,
    "add_on_label": "",
    "display_name": "Farm Focus",
    "subtitle": "Powered by Lightning Payroll",
    "company_name": "Farm Focus Pty Ltd",
    "support_email": "support@farmfocus.example",
    "support_phone": "1300 000 111",
    "homepage_url": "https://partner.example.com",
    "support_url": "https://partner.example.com/support",
    "has_logo": false,
    "has_dark_logo": false,
    "has_favicon": false,
    "has_style": false,
    "disable_dark_mode": false,
    "uses_standalone_shell": false
  }
}

Secret rotation example

Rotate the client secret
curl -sS "$BASE_URL/api/update-api-client" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"regenerate_secret": true}'

The response returns the new secret in new_secret. Store it immediately. It is not recoverable later.

Important behaviour

  • redirect_uris must be a list or the server returns 422 with redirect_uris must be a list.
  • Redirect URIs are treated as opaque strings. The OAuth flow later requires an exact string match.
  • Invalid timezone returns 422 with Invalid timezone.
  • If no client exists yet, this endpoint returns 404 with Client not found.

083) Branding assets

These endpoints manage the images and custom CSS shown in hosted login and onboarding flows for your integration.

For the full branding surface, including the favicon and email banner assets, the designer-oriented asset-spec table, and how branded emails render, see the API Branding & Co-Branding Guide.

Asset rules

AssetEndpoint baseAllowed filesMax size
Primary logo/api/api-client/whitelabel-logojpg, jpeg, png, gif1 MB
Dark-mode logo/api/api-client/whitelabel-dark-logojpg, jpeg, png, gif1 MB
Stylesheet/api/api-client/whitelabel-style.css, UTF-8 encoded64 KB

Asset error handling

Asset-management endpoints use standard 4xx status codes for user-correctable problems and still return a JSON body with an error field. Example overwrite response:

409 Conflict
{
  "error": "Stylesheet already exists. Set overwrite_existing=true to replace it.",
  "requires_overwrite": true
}

Common asset errors

StatusMeaning
400Invalid upload such as empty file, wrong extension, unsupported image type, or oversized payload
404Attempted to delete an asset that does not exist
409Asset already exists and overwrite_existing=true was not supplied
422Stylesheet upload was not valid UTF-8

On any non-2xx response, inspect the JSON body for error and, where relevant, requires_overwrite.

Upload a primary logo

POST /api/api-client/whitelabel-logo
curl -sS "$BASE_URL/api/api-client/whitelabel-logo" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -F "file=@partner-logo.png"

To replace an existing logo:

POST /api/api-client/whitelabel-logo (overwrite)
curl -sS "$BASE_URL/api/api-client/whitelabel-logo" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -F "overwrite_existing=true" \
  -F "file=@partner-logo.png"

Upload a dark-mode logo

POST /api/api-client/whitelabel-dark-logo
curl -sS "$BASE_URL/api/api-client/whitelabel-dark-logo" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -F "file=@partner-logo-dark.png"

Upload a stylesheet

POST /api/api-client/whitelabel-style
curl -sS "$BASE_URL/api/api-client/whitelabel-style" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -F "file=@partner-theme.css"

Read current asset metadata

GET the three asset endpoints
curl -sS "$BASE_URL/api/api-client/whitelabel-logo" \
  -H "Authorization: Bearer $ADMIN_TOKEN"

curl -sS "$BASE_URL/api/api-client/whitelabel-dark-logo" \
  -H "Authorization: Bearer $ADMIN_TOKEN"

curl -sS "$BASE_URL/api/api-client/whitelabel-style" \
  -H "Authorization: Bearer $ADMIN_TOKEN"

Delete an asset

DELETE /api/api-client/whitelabel-style
curl -sS -X DELETE "$BASE_URL/api/api-client/whitelabel-style" \
  -H "Authorization: Bearer $ADMIN_TOKEN"

Asset response fields

  • Logo endpoints return whitelabel_logo or whitelabel_dark_logo.
  • Style endpoints return whitelabel_style.
  • Common fields include has_logo / has_dark_logo / has_style, image_name or css_name, image_data or css_text, and updated_at.

094) Public branding endpoints

These endpoints are unauthenticated and are used by hosted login and onboarding flows.

Resolve branding by client_id

GET /api/public-branding
curl -sS "$BASE_URL/api/public-branding?client_id=your-client-id"

Resolve branding by public branding token

GET /api/public-branding (by token)
curl -sS "$BASE_URL/api/public-branding?branding_token=public-branding-token"

Fetch logo bytes

GET /api/public-branding/logo
curl -sS "$BASE_URL/api/public-branding/logo?client_id=your-client-id" --output logo.bin
curl -sS "$BASE_URL/api/public-branding/logo?client_id=your-client-id&mode=dark" --output logo-dark.bin

Public branding notes

  • Calling /api/public-branding with neither client_id nor branding_token returns the default Lightning Payroll branding payload.
  • client_id lookup returns 404 if the client does not exist.
  • client_id lookup returns 403 if the owning customer is not an API admin.
  • branding_token lookup returns 404 if the branding record is missing.
  • /api/public-branding/logo falls back to the primary logo when mode=dark is requested but no dark logo exists.
  • For partner preview and hosted-flow branding checks, treat /api/public-branding as the canonical public branding API.

105) Connection monitoring and support

All monitoring endpoints require the API-admin bearer token and an existing client. Request-log retention is currently 90 days.

Overview

GET/api/api-client/overview
GET /api/api-client/overview
curl -sS "$BASE_URL/api/api-client/overview" \
  -H "Authorization: Bearer $ADMIN_TOKEN"

Returns roll-up metrics such as totalConnections, activeConnections, totalTokens, activeTokens, authsLast30Days, tokensIssuedLast30Days, requestsLast90Days, errorRateLast90Days, errorsLast24Hours, errorsLast7Days, errorsLast30Days, errorsLast90Days, avgDurationMsLast90Days, lastActivityAt, and lastRequestAt.

Connections

GET/api/api-client/connections
GET /api/api-client/connections
curl -sS "$BASE_URL/api/api-client/connections?status=all" \
  -H "Authorization: Bearer $ADMIN_TOKEN"

Supported status values: all, active, inactive.

Each connection item includes customerId, companyName, email, status, activeTokens, totalTokens, revokedTokens, expiredTokens, firstAuthorizedAt, lastAuthAt, lastTokenIssuedAt, tokensLast30Days, authsLast30Days, requestsLast24Hours, requestsLast7Days, requestsLast30Days, requestsLast90Days, errorRateLast90Days, avgDurationMsLast90Days, lastRequestAt, and scopes.

Revoke one customer connection

DELETE/api/api-client/connections/{customer_id}
DELETE /api/api-client/connections/17193
curl -sS -X DELETE "$BASE_URL/api/api-client/connections/17193" \
  -H "Authorization: Bearer $ADMIN_TOKEN"
200 OK
{
  "message": "Connection revoked",
  "revokedTokens": 1,
  "revokedCodes": 0
}
FieldMeaning
revokedTokensHow many of that customer's refresh tokens under your client were still live and have now been revoked.
revokedCodesHow many of that customer's unused authorization codes under your client were invalidated.

This revokes all non-revoked refresh tokens for that customer under your client, and it also invalidates any authorization code they have been issued but not yet exchanged. That second step matters: an unexchanged code is still redeemable for a fresh token pair, so without it a customer could re-establish the connection you just severed. It does not delete historical logs.

If the customer has no refresh-token rows and no unused authorization codes for your client, the server returns 404 with No active connection found for that customer. A customer who has authorized but not yet completed the token exchange therefore still counts as a connection.

Activity feed

GET/api/api-client/activity
GET /api/api-client/activity
curl -sS "$BASE_URL/api/api-client/activity?limit=50" \
  -H "Authorization: Bearer $ADMIN_TOKEN"

Optional filters: customer_id, limit (1 to 250). Event types currently returned: authorization_code_issued, refresh_token_issued.

Error summary

GET/api/api-client/errors
GET /api/api-client/errors
curl -sS "$BASE_URL/api/api-client/errors?limit=100" \
  -H "Authorization: Bearer $ADMIN_TOKEN"

Returns summary counts for the last 24 hours, 7 days, 30 days, and 90 days; top status-code breakdown; top failing endpoints; and recent error entries with customer identifiers, method, path, status, error details, request ID, and timestamp.

11Interpreting effective_branding.source_type

effective_branding.source_type tells you where the resolved branding came from. It can currently be none, self, oauth_client, or add_on. For most API-admin setup work you will see self once your own branding identity exists.

12Operational recommendations

1

Call GET /api/init-api-client once during partner setup and store the returned clientId.

2

Register every production and test callback URL you plan to use in redirect_uris.

3

Treat the client secret like a password. If exposed, rotate it immediately with regenerate_secret=true.

4

Check upload responses for error on any non-2xx response, and handle 409 by prompting for an overwrite confirmation.

5

Use /api/api-client/connections, /api/api-client/activity, and /api/api-client/errors as your first-line support tools when a customer reports an integration issue.

13Related guides