Skip to content
Developer Docs

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

shell
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'
python
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"]
javascript
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();
javascript
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;
java
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

json
{
  "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:

http
Authorization: Bearer {access_token}
x-tenant-id: {businessUuid}
HeaderDescription
AuthorizationToken returned in step 2, with Bearer prefix
x-tenant-idYour businessUuid, identifies the business you're operating on