Authentication Guide

This guide explains step by step how to obtain an access token to use the CorpX PIX API.

Overview

The CorpX API uses OAuth 2.0 with the Client Credentials flow for authentication. You will need your credentials (client_id and client_secret) to obtain a valid access token.

Prerequisites

Before you begin, make sure you have:

  • Client ID - Your unique client identifier
  • Client Secret - Secret key for authentication
  • X-Tenant-Id - Your tenant identifier (e.g., tenant-suaempresa)

If you don’t have credentials yet, contact our support team.

Environments

EnvironmentAuthentication URLAPI URLNotes
Sandbox (dev)Temporarily unavailable.
Productionhttps://auth.api.corpx.com/oauth2/tokenhttps://tenant.api.corpx.com

Step 1: Request an Access Token

Request

curl -X POST "https://auth.api.corpx.com/oauth2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET"

Parameters

ParameterTypeRequiredDescription
grant_typestringYesAlways client_credentials
client_idstringYesYour client identifier
client_secretstringYesYour secret key
scopestringNoSpace-separated subset of the credential’s scopes. Omitting it returns a token with all of them

Success Response (200 OK)

{
"access_token": "eyJraWQiOiJ0emwzZWVYWGx1eVlDWHFwQXdBTzJWYWJNQ0llMFMyMXVRWGV2Y281N2RRPSIsImFsZyI6IlJTMjU2In0...",
"expires_in": 300,
"token_type": "Bearer"
}
FieldDescription
access_tokenJWT token for authentication in API calls
expires_inValidity time in seconds
token_typeToken type (always Bearer)
Reuse the token. Do not request a token per call.

The access_token lasts 5 minutes (expires_in: 300) and must be reused for every call until it expires. Minting a new token on every request is forbidden: it exhausts Cognito, slows your integration down, and can get token issuance refused.

Cache the token on your side. Only call /oauth2/token again when about 60 seconds remain on expires_in, or when the API answers 403 for an expired token. Do not hardcode the TTL — read expires_in from the response.

Error Response (401 Unauthorized)

{
"error": "invalid_client",
"error_description": "Client authentication failed"
}

Step 2: Use the Token in Requests

With the obtained token, include it in the Authorization header of all API requests.

Example: Check Balance

curl -X GET "https://tenant.api.corpx.com/v1/accounts/{accountId}/balance" \
-H "Authorization: Bearer eyJraWQiOiJ0emwzZWVYWGx1eVlDWHFwQXdBTzJWYWJNQ0llMFMyMXVRWGV2Y281N2RRPSIsImFsZyI6IlJTMjU2In0..." \
-H "X-Tenant-Id: tenant-suaempresa" \
-H "Content-Type: application/json"

Required Headers

HeaderDescription
AuthorizationAccess token in Bearer {access_token} format
X-Tenant-IdYour tenant identifier
Content-Typeapplication/json for requests with a body

Step 3: Renew the Token

The token expires after the time indicated in expires_in (5 minutes on current credentials). Reusing the same token until near that deadline is mandatory:

  1. Store the token in cache with the expires_in that came in the response
  2. Renew before expiration — with a 5-minute TTL, renewing in the last minute is already tight
  3. Handle the 403 for expired tokens — the API answers 403 Forbidden, not 401; when you get it, obtain a new token and retry the call

Automatic Renewal Example (Bash)

#!/bin/bash
# Variables
CLIENT_ID="your_client_id"
CLIENT_SECRET="your_client_secret"
AUTH_URL="https://auth.api.corpx.com/oauth2/token"
# Function to get token
get_token() {
response=$(curl -s -X POST "$AUTH_URL" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=$CLIENT_ID" \
-d "client_secret=$CLIENT_SECRET")
echo "$response" | jq -r '.access_token'
}
# Get token
TOKEN=$(get_token)
# Use the token
curl -X GET "https://tenant.api.corpx.com/v1/accounts/{accountId}/balance" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Tenant-Id: tenant-suaempresa"

Full Examples in Different Languages

Python

import requests
# Credentials
CLIENT_ID = "your_client_id"
CLIENT_SECRET = "your_client_secret"
TENANT_ID = "tenant-suaempresa"
# Get token
auth_response = requests.post(
"https://auth.api.corpx.com/oauth2/token",
data={
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET
}
)
token = auth_response.json()["access_token"]
# Use the token
headers = {
"Authorization": f"Bearer {token}",
"X-Tenant-Id": TENANT_ID,
"Content-Type": "application/json"
}
response = requests.get(
"https://tenant.api.corpx.com/v1/accounts/{accountId}/balance",
headers=headers
)
print(response.json())

Node.js

const axios = require('axios');
const CLIENT_ID = 'your_client_id';
const CLIENT_SECRET = 'your_client_secret';
const TENANT_ID = 'tenant-suaempresa';
let cached = { token: null, expiresAt: 0 };
async function getToken() {
if (cached.token && Date.now() < cached.expiresAt) {
return cached.token;
}
const response = await axios.post(
'https://auth.api.corpx.com/oauth2/token',
new URLSearchParams({
grant_type: 'client_credentials',
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET
}),
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
);
cached = {
token: response.data.access_token,
expiresAt: Date.now() + (response.data.expires_in - 60) * 1000
};
return cached.token;
}
async function getBalance(accountId) {
const token = await getToken();
const response = await axios.get(
`https://tenant.api.corpx.com/v1/accounts/${accountId}/balance`,
{
headers: {
'Authorization': `Bearer ${token}`,
'X-Tenant-Id': TENANT_ID,
'Content-Type': 'application/json'
}
}
);
return response.data;
}

Credential scopes

Every credential (client_id/client_secret) carries a set of scopes that defines exactly what it can do. You pick the scopes when creating the credential in the backoffice panel (API Credentials, available to users with the tenant manager profile).

Scopes you grant yourself:

ScopeUnlocks
readAccount-wide queries: balance, statement, timeline, entries
qrcode.manageStatic and dynamic QR code: create, cancel and check the payment of your own QR
pix_keys.managePIX keys: list, create and delete
webhooks.manageWebhook subscriptions, deliveries and retries
exports.createStatement exports: create, track and download
med.defendMED: query, answer and attach evidence (does not decide the refund)
kyc.readKYC evidence of your own accreditations, including the holder’s biometrics

kyc.read is the only query scope not covered by read: since it delivers customer faces, it has to be granted explicitly. The endpoint also requires the kyc_artifacts feature enabled for the tenant — see Evidence files.

Every scope already queries its own domain. That is what makes an inflow-only credential possible: grant qrcode.manage alone and it issues QR codes and sees whether they were paid without access to the balance and the statement. Only check read when the credential genuinely needs the account-wide queries.

Scopes that move money are issued by CorpX, never from the panel:

ScopeUnlocks
pix_out.createPIX out in every variant (/pix/out/*), with the status of its own payments
refund.createRefund of a received PIX, with the status of its own refunds
internal_transfer.createInternal transfer, with the destination lookup
ted.createTED, with the status of its own transfers
boleto_payment.createBoleto preview, payment and status
credentials.delegateIssue and revoke credentials bound to a single account, signed — see below
pin.manageRegister, change and invalidate the transaction PIN of the account’s operators
security_locks.manageConfigure the account’s cashout locks (block, hours, IP)

Requesting one of these from the panel is refused with 403 scope_not_self_service — contact support and we issue the credential.

Delegated credentials

The credentials.delegate scope is CorpX-only: the internet-banking master issues children bound to one account. If you received that child (IB API feature), this BaaS section is not your guide — go to Internet banking.

Per-account restriction

A credential can be restricted to specific accounts of your tenant. That is how you give a subsystem a credential that only issues QR codes for one account. Calls to accounts outside the set answer 403 forbidden — the generic permission error, not insufficient_scope: the scope is there, the account is not. The message deliberately does not distinguish a nonexistent account from one outside the set.

Combining both dimensions: qrcode.manage plus a single account yields a credential that charges by QR code on that account and sees nothing else — neither its balance nor the tenant’s other accounts.

When a scope is missing

If the credential lacks the scope for the route, the API answers 403 Forbidden naming what is missing:

{ "errorCode": "insufficient_scope", "message": "esta credencial não tem escopo para esta operação; é necessário o escopo qrcode.manage" }

Credentials issued before granular scopes keep working unchanged: they retain the coarse pair (api2/read api2/write) and full access.

Access suspended by a pendency

When there is an open pendency with CorpX, a tenant’s access can be suspended temporarily. Suspension does not invalidate your credential: the same client_id/client_secret keeps issuing tokens normally, and the refusal comes on the API call.

StateWhat keeps workingResponse on other calls
SuspendedReads (GET) — balance, statement, operation status403 tenant_suspended on writes
DisabledNothing403 tenant_disabled on every route
{ "errorCode": "tenant_suspended", "message": "há uma pendência em aberto: operações de escrita estão suspensas até a regularização, consultas seguem disponíveis" }

Two things worth knowing:

  • Money does not stop. Incoming PIX is still credited and you still receive the webhooks for those events. What suspension blocks is starting new operations.
  • Reactivation is immediate. Once the pendency is cleared, access returns on the next call — no new credential and no new token needed.

The reason for the pendency is not in the error body: it shows up in the backoffice panel, for your team’s operators.

Common Errors

ErrorCauseSolution
invalid_clientIncorrect Client ID or SecretCheck your credentials
invalid_grantInvalid grant typeUse client_credentials
401 UnauthorizedMissing Authorization headerSend Authorization: Bearer <token>
403 Forbidden (invalid/expired token)Expired token, invalid token, or bad signatureObtain a new token. Note that expiry is 403, not 401
403 insufficient_scopeCredential without the route’s scopeCreate a credential with the scope named in the message
403 tenant_suspendedOpen pendency: writes are suspendedClear it with CorpX support; reads keep working
403 tenant_disabledTenant disabledContact CorpX support to restore access
403 forbiddenValid token, but no permission on that tenant or accountCheck X-Tenant-Id and whether the credential is restricted to other accounts

Best Practices

  1. Reuse the token — one token per call is forbidden
  2. Never expose the client_secret in client-side code (frontend)
  3. Use environment variables to store credentials
  4. Monitor expiration and renew tokens proactively
  5. Use HTTPS for all communications

Next Steps

Now that you know how to authenticate, explore the other guides: