Authentication
OAuth2 Client Credentials authentication with Basic Auth. You get a short-lived access_token that travels on every call.
Authentication in the Zertiban API is based on the OAuth2 Client Credentials standard, using Basic Auth to obtain the token. This mechanism is designed for secure server-to-server integrations, where the client exchanges its credentials for a short-lived access_token used in every subsequent API call.
Credentials (clientId and clientSecret) are not sent in the request body, but in the header via Basic Auth. They must be included in the Authorization header, automatically encoded by standard HTTP clients.
Endpoint
POST/idp/oauth2/token with grant_type=client_credentials.
Credentials go in the header
Authorization as Basic Auth, not in the request body.
curl's -u clientId:clientSecret option, Python requests' auth=(id, secret) and axios' auth: { username, password } build this header automatically.
Historical note: scope is not required
Previous PagaFactu documentation also included --data-urlencode 'scope=openid profile api'. It is not necessary.
Request the token
curl -X POST https://nc-api-sandbox.zertiban.com/idp/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-u '{clientId}:{clientSecret}' \
--data-urlencode 'grant_type=client_credentials'import requests
response = requests.post(
"https://nc-api-sandbox.zertiban.com/idp/oauth2/token",
auth=(CLIENT_ID, CLIENT_SECRET),
data={"grant_type": "client_credentials"}
)
token = response.json()["access_token"]
expires_in = response.json()["expires_in"]const credentials = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64');
const response = await fetch('https://nc-api-sandbox.zertiban.com/idp/oauth2/token', {
method: 'POST',
headers: {
Authorization: `Basic ${credentials}`,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'grant_type=client_credentials'
});
const { access_token, expires_in } = await response.json();const response = await axios.post(
'https://nc-api-sandbox.zertiban.com/idp/oauth2/token',
'grant_type=client_credentials',
{
auth: { username: CLIENT_ID, password: CLIENT_SECRET },
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}
);
const { access_token, expires_in } = response.data;TokenResponse token = WebClient.create("https://nc-api-sandbox.zertiban.com")
.post().uri("/idp/oauth2/token")
.headers(h -> h.setBasicAuth(clientId, clientSecret))
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(BodyInserters.fromFormData("grant_type", "client_credentials"))
.retrieve()
.bodyToMono(TokenResponse.class)
.block();
String accessToken = token.getAccessToken();
int expiresIn = token.getExpiresIn(); Receive the access_token
{
"access_token": "eyJraWQiOiJ...",
"token_type": "Bearer",
"expires_in": 300
}Refresh before it expires
The token lasts 300 seconds (5 minutes) by default. Always use the expires_in value from the response, don't hardcode it, since it may vary. Refresh it before it expires to avoid 401 errors.
Use the token on every call
From now on, all calls carry these two headers:
Authorization: Bearer {access_token}
x-tenant-id: {businessUuid}| Header | Description |
|---|---|
Authorization | Token returned in step 2, with Bearer prefix |
x-tenant-id | Your businessUuid, identifies the business you're operating on |