LirPay
OpenAPI

LirPay Cloud OAuth

Connect your app to a merchant account with selectable permissions. LirPay Cloud uses Authorization Code with mandatory PKCE (S256), opaque Bearer tokens, and rotating refresh tokens. Existing API keys remain supported. OAuth authorizes Integration API access; it does not issue identity tokens or support implicit, password, or client credentials grants.

Register your application

Open Merchant → Integrations → LirPay Cloud. Set a name, 1–10 callback URLs, and the maximum scopes your app can request. Registration does not grant access to any account.

  • Confidential clients run on a server and receive a secret shown once. Keep the secret on your server.
  • Public clients run in browsers or installed apps and use their client ID without a secret.
  • Callbacks must use HTTPS; HTTP is allowed for localhost and loopback development. Credentials and fragments are forbidden. Scheme, host, port, path, and query must match exactly.
  • Merchants choose permissions on the consent screen. Write permissions and offline access are unchecked by default.

Manage an existing application

Search or filter Your applications, then open an app. Its page shows client ID, status, creation/update dates, active connections, and last successful API use. Settings edits name, description, website, callback URLs, and allowed scopes. Description and website appear during merchant consent. The client type is fixed; both types use PKCE, and server apps also authenticate with a secret.

  • Removing a scope immediately limits existing grants and tokens. Adding it back requires new merchant consent. Removing offline_access stops refreshes; grants with no API permissions are revoked.
  • Saving settings invalidates unfinished authorizations and unexchanged codes. Stale forms return 409 application_changed; cancel the draft and reload the app to view the latest settings.
  • Pause blocks new authorizations and revokes existing grants. Resume permits new connections, while old grants stay revoked. Permanent revocation cannot be undone; history remains available.
  • Rotate client secret replaces the old secret immediately and displays the new one once. Update your backend. Choose whether to disconnect existing accounts; otherwise their access tokens remain valid and refresh requires the new secret.
  • Connections shows merchant accounts, permissions, environment, expiry, status, and last successful request. Disconnect one or all accounts. Pages contain 100 records, and search applies to the displayed page. Activity updates at most once a minute.

These dashboard management endpoints require an active merchant session. OAuth resource tokens cannot manage apps. Revoked apps remain read-only. Connection lists never expose tokens or secret hashes. Disconnecting OAuth does not reverse financial operations or release outstanding balance-payment reservations.

GET /api/v2/merchant/oauth/clients
POST /api/v2/merchant/oauth/clients
GET /api/v2/merchant/oauth/clients/{client_id}
PUT /api/v2/merchant/oauth/clients/{client_id}
POST /api/v2/merchant/oauth/clients/{client_id}/status
POST /api/v2/merchant/oauth/clients/{client_id}/rotate-secret
DELETE /api/v2/merchant/oauth/clients/{client_id}
GET /api/v2/merchant/oauth/clients/{client_id}/connections?page=1
DELETE /api/v2/merchant/oauth/clients/{client_id}/connections/{grant_id}
DELETE /api/v2/merchant/oauth/clients/{client_id}/connections

GET returns client, issuer, authorization_endpoint, and token_endpoint. Use the client’s current version in the next update. PUT saves all settings; status accepts enabled and version; rotation accepts revoke_grants and version. Mutations return the updated client. Connection pages return connections and has_more. Another owner’s app returns 404; changing a revoked app returns 409 application_revoked.

{
  "name": "Accounting application",
  "client_type": "confidential",
  "description": "Reconcile merchant payments",
  "website_url": "https://app.example",
  "redirect_uris": ["https://app.example/oauth/callback"],
  "scopes": ["account:read", "payments:read", "offline_access"],
  "version": 0
}

This is a complete settings body for a confidential app; use the version returned by GET. Description is limited to 1,000 characters, and callback URLs must be unique. Merchants can separately view and revoke their own account’s grants through GET /api/v2/merchant/oauth/grants and DELETE /api/v2/merchant/oauth/grants/{grant_id}.

Discover the protocol

GET https://lirpay.org/.well-known/oauth-authorization-server
GET https://lirpay.org/api/v2/oauth/permissions
GET https://lirpay.org/api/v2/oauth/authorize
POST https://lirpay.org/api/v2/oauth/token
POST https://lirpay.org/api/v2/oauth/revoke

The metadata response publishes the issuer, endpoints, scopes, grant types, and client authentication methods. The resource API remains https://lirpay.org/api/v2/integration.

1. Start authorization

Generate a fresh verifier and state for each connection. Save them in a short-lived session bound to the initiating browser. This Node.js example builds the authorization URL:

import { randomBytes, createHash } from 'node:crypto';

const verifier = randomBytes(32).toString('base64url');
const state = randomBytes(32).toString('base64url');
const challenge = createHash('sha256').update(verifier).digest('base64url');
const params = new URLSearchParams({
  response_type: 'code',
  client_id: process.env.LIRPAY_CLIENT_ID,
  redirect_uri: 'https://app.example/oauth/callback',
  scope: 'account:read projects:read payments:read payments:write offline_access',
  state,
  code_challenge: challenge,
  code_challenge_method: 'S256',
  mode: 'live',
});
const url = 'https://lirpay.org/api/v2/oauth/authorize?' + params;
// Save verifier and state in the initiating session, then redirect the browser.
ParameterTypeInRequiredDescription
response_typestringqueryyescode only.
client_idstringqueryyesYour registered client ID.
redirect_uriURLqueryyesAn exact registered callback URL.
scopestringqueryyesSpace-separated subset of registered scopes. Include at least one API scope.
statestringqueryyesNonempty value up to 512 characters. Verify against your initiating session.
code_challengestringqueryyesUnpadded base64url SHA-256 of the verifier.
code_challenge_methodstringqueryyesS256 only; plain challenges are rejected.
modestringquerynolive (default) or test. The authorization fixes the environment for its tokens.

The authorization request expires in 10 minutes. Approval returns code, state, and iss=https://lirpay.org to your callback. Denial returns error=access_denied, state, and issuer. Invalid callbacks receive a JSON error without a redirect.

2. Exchange the code

Check state against the initiating session and issuer against discovery before exchanging. Consume state once. Codes expire 5 minutes after approval and are single-use. The verifier must contain 43–128 unreserved ASCII characters. PKCE is required for both client types.

curl -sS https://lirpay.org/api/v2/oauth/token \
  --user "$LIRPAY_CLIENT_ID:$LIRPAY_CLIENT_SECRET" \
  --data-urlencode 'grant_type=authorization_code' \
  --data-urlencode "code=$LIRPAY_CODE" \
  --data-urlencode 'redirect_uri=https://app.example/oauth/callback' \
  --data-urlencode "code_verifier=$LIRPAY_VERIFIER"

Send form-encoded parameters, not JSON. Confidential clients use HTTP Basic (URL-encode each credential before constructing Basic) or client_id and client_secret in the form body. Use one authentication method. Public clients omit Basic and send only client_id in the form along with the exchange fields.

{
  "access_token": "lc_at_example",
  "token_type": "Bearer",
  "expires_in": 899,
  "scope": "account:read payments:read offline_access",
  "refresh_token": "lc_rt_example"
}

Use the returned scope: merchants may grant fewer permissions. Access tokens last up to 15 minutes; expires_in is the remaining lifetime in seconds. Refresh tokens are issued only with offline_access. Grants expire 30 days after initial exchange; refresh does not extend that deadline.

3. Call the Integration API

curl -sS https://lirpay.org/api/v2/integration/account \
  -H "Authorization: Bearer $LIRPAY_ACCESS_TOKEN"

Omit API key headers. The account response contains oauth_client_id, oauth_grant_id, and api_key_id: null. Resources remain isolated by merchant and environment. Financial requests retain their Idempotency-Key requirements and replay protection across token refreshes.

Permissions

ParameterTypeInRequiredDescription
account:readscopebodynoRead the authorized merchant account.
projects:readscopebodynoRead approved projects and payment methods.
payments:readscopebodynoRead payment links and transactions.
payments:writescopebodynoCreate/cancel payment links and refund eligible payments.
balances:readscopebodynoRead balances and ledger entries.
balance-payments:readscopebodynoRead internal payments to this application.
balance-payments:writescopebodynoReserve funds, pay the application owner and refund internally.
withdrawals:readscopebodynoRead payout methods, quotes, and withdrawals.
withdrawals:writescopebodynoCreate and cancel withdrawals.
conversions:readscopebodynoRead pairs, quotes, and conversions.
conversions:writescopebodynoConvert funds and manage conversion rules.
fulfillments:readscopebodynoRead Robux deliveries and statistics.
fulfillments:writescopebodynoCreate Robux deliveries using merchant funds.
webhooks:readscopebodynoRead webhook configuration and delivery history.
webhooks:writescopebodynoManage configuration, test, rotate secrets, and redeliver events.
settings:readscopebodynoRead integration settings and supported payment methods.
settings:writescopebodynoUpdate integration settings.
reports:readscopebodynoRead financial summary reports.
offline_accessscopebodynoIssue refresh tokens; requires at least one API permission.

Request only the permissions your app needs. Write scopes can move funds or change configuration. Scopes are case-sensitive. Every resource endpoint checks its documented scope; missing permissions return 403 missing_scope. OAuth tokens cannot manage dashboard sessions, API keys, or OAuth apps.

Refresh and revoke

curl -sS https://lirpay.org/api/v2/oauth/token \
  --user "$LIRPAY_CLIENT_ID:$LIRPAY_CLIENT_SECRET" \
  --data-urlencode 'grant_type=refresh_token' \
  --data-urlencode "refresh_token=$LIRPAY_REFRESH_TOKEN"

Replace the refresh token atomically and serialize refreshes. Reusing an old refresh token revokes the entire authorization, including its access tokens. Optional scope can narrow permissions, never expand them; dropping offline_access stops refresh issuance. Existing access tokens keep their permissions until expiry or revocation. Replaying an exchanged code with the correct client and PKCE binding also revokes its grant. If you lose the response, reconnect instead of blindly retrying an exchange or refresh.

curl -sS https://lirpay.org/api/v2/oauth/revoke \
  --user "$LIRPAY_CLIENT_ID:$LIRPAY_CLIENT_SECRET" \
  --data-urlencode "token=$LIRPAY_REFRESH_TOKEN"

Public clients send client_id without a secret. Revoking either token disconnects its whole authorization; unknown tokens return 200. Merchants can Disconnect an app in LirPay Cloud. App owners can Revoke a registration to disable every grant. Revocation and account suspension block subsequent API requests.

Errors and token handling

OAuth errors use error and error_description: invalid_request, invalid_client (401), invalid_grant, invalid_scope, unsupported_grant_type, and unsupported_response_type. Resource tokens that expire or are revoked return 401 invalid_token. Reconnect if refresh fails. Tokens and secrets are stored as hashes; keep raw values out of URLs, logs, source control, and browser persistent storage. Token and resource endpoints support CORS for public clients and use Bearer credentials without cookies.

Protocol references: OAuth 2.0, PKCE, OAuth security recommendations, revocation, metadata, and issuer identification.