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:
- a Lightning Payroll customer account that has been granted API-admin access
- the ability to sign in as that API-admin account
- 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_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".
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:
{"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:
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:
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:
{"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
GET /api/init-api-client
POST /api/update-api-client
Upload optional branding assets.
Start using the OAuth Authentication Guide.
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 accounteffective_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
| Method | Path | Purpose |
|---|---|---|
| GET | /api/init-api-client | Create the OAuth client if missing, or return the existing client |
| POST | /api/update-api-client | Update redirect URIs, rotate secret, timezone, and branding metadata |
| GET | /api/api-client/whitelabel-logo | Read primary logo metadata |
| POST | /api/api-client/whitelabel-logo | Upload primary logo |
| DELETE | /api/api-client/whitelabel-logo | Delete primary logo |
| GET | /api/api-client/whitelabel-dark-logo | Read dark-mode logo metadata |
| POST | /api/api-client/whitelabel-dark-logo | Upload dark-mode logo |
| DELETE | /api/api-client/whitelabel-dark-logo | Delete dark-mode logo |
| GET | /api/api-client/whitelabel-style | Read stylesheet metadata and CSS text |
| POST | /api/api-client/whitelabel-style | Upload stylesheet |
| DELETE | /api/api-client/whitelabel-style | Delete stylesheet |
| GET | /api/public-branding | Resolve public branding by client_id or branding_token |
| GET | /api/public-branding/logo | Fetch public logo bytes |
| GET | /api/api-client/overview | Roll-up metrics for the API client |
| GET | /api/api-client/connections | List connected customer accounts |
| DELETE | /api/api-client/connections/{customer_id} | Revoke all refresh tokens for one connected customer |
| GET | /api/api-client/activity | Recent authorization-code and refresh-token activity |
| GET | /api/api-client/errors | Recent 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.
Request
curl -sS "$BASE_URL/api/init-api-client" \ -H "Authorization: Bearer $ADMIN_TOKEN"
First-time response
{
"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
{
"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
clientSecretis 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-clientare 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.
Supported request fields
| Field | Type | Notes |
|---|---|---|
redirect_uris | string[] | Must be a JSON array. These are stored as exact-match callback URIs. |
regenerate_secret | boolean | When true, rotates the client secret and returns the new plaintext secret once. |
timezone | string | IANA timezone name, for example UTC or Australia/Brisbane. |
branding_display_name | string | Partner brand name shown in hosted flows. |
branding_subtitle | string | Secondary line used in hosted flows. |
branding_support_email | string | Support contact shown in branding payloads. |
branding_support_phone | string | Support phone shown in branding payloads. |
homepage_url | string | Partner homepage URL. |
support_url | string | Partner support URL. |
disable_dark_mode | boolean | Hints that hosted pages should avoid dark mode. |
auth_design | object | Login-screen design blob, echoed through GET /api/public-branding. null resets it. |
from_local_part | string | Local part of the From address on your sending domain, so no-reply gives no-reply@mail.example.com. |
reply_to | string | Reply-To address used on branded email. |
email_accent_color | string | Hex colour used in branded email, for example #00b1dd. |
email_hide_lp_attribution | boolean | Suppresses the Lightning Payroll attribution line in branded email. |
farm_focus_host_override_url | string | Overrides 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
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
{
"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
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_urismust be a list or the server returns422withredirect_uris must be a list.- Redirect URIs are treated as opaque strings. The OAuth flow later requires an exact string match.
- Invalid
timezonereturns422withInvalid timezone. - If no client exists yet, this endpoint returns
404withClient 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
| Asset | Endpoint base | Allowed files | Max size |
|---|---|---|---|
| Primary logo | /api/api-client/whitelabel-logo | jpg, jpeg, png, gif | 1 MB |
| Dark-mode logo | /api/api-client/whitelabel-dark-logo | jpg, jpeg, png, gif | 1 MB |
| Stylesheet | /api/api-client/whitelabel-style | .css, UTF-8 encoded | 64 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:
{
"error": "Stylesheet already exists. Set overwrite_existing=true to replace it.",
"requires_overwrite": true
}
Common asset errors
| Status | Meaning |
|---|---|
| 400 | Invalid upload such as empty file, wrong extension, unsupported image type, or oversized payload |
| 404 | Attempted to delete an asset that does not exist |
| 409 | Asset already exists and overwrite_existing=true was not supplied |
| 422 | Stylesheet 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
curl -sS "$BASE_URL/api/api-client/whitelabel-logo" \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -F "file=@partner-logo.png"
To replace an existing logo:
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
curl -sS "$BASE_URL/api/api-client/whitelabel-dark-logo" \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -F "file=@partner-logo-dark.png"
Upload a stylesheet
curl -sS "$BASE_URL/api/api-client/whitelabel-style" \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -F "file=@partner-theme.css"
Read current asset metadata
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
curl -sS -X DELETE "$BASE_URL/api/api-client/whitelabel-style" \ -H "Authorization: Bearer $ADMIN_TOKEN"
Asset response fields
- Logo endpoints return
whitelabel_logoorwhitelabel_dark_logo. - Style endpoints return
whitelabel_style. - Common fields include
has_logo/has_dark_logo/has_style,image_nameorcss_name,image_dataorcss_text, andupdated_at.
094) Public branding endpoints
These endpoints are unauthenticated and are used by hosted login and onboarding flows.
Resolve branding by client_id
curl -sS "$BASE_URL/api/public-branding?client_id=your-client-id"
Resolve branding by public branding token
curl -sS "$BASE_URL/api/public-branding?branding_token=public-branding-token"
Fetch logo bytes
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-brandingwith neitherclient_idnorbranding_tokenreturns the default Lightning Payroll branding payload. client_idlookup returns404if the client does not exist.client_idlookup returns403if the owning customer is not an API admin.branding_tokenlookup returns404if the branding record is missing./api/public-branding/logofalls back to the primary logo whenmode=darkis requested but no dark logo exists.- For partner preview and hosted-flow branding checks, treat
/api/public-brandingas 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
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
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
curl -sS -X DELETE "$BASE_URL/api/api-client/connections/17193" \ -H "Authorization: Bearer $ADMIN_TOKEN"
{
"message": "Connection revoked",
"revokedTokens": 1,
"revokedCodes": 0
}
| Field | Meaning |
|---|---|
revokedTokens | How many of that customer's refresh tokens under your client were still live and have now been revoked. |
revokedCodes | How 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
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
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
Call GET /api/init-api-client once during partner setup and store the returned clientId.
Register every production and test callback URL you plan to use in redirect_uris.
Treat the client secret like a password. If exposed, rotate it immediately with regenerate_secret=true.
Check upload responses for error on any non-2xx response, and handle 409 by prompting for an overwrite confirmation.
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.