Skip to main content

Authentication

Billerang v1 uses Keycloak (realm billerang) for authentication. Server-to-server API calls use the OAuth 2.0 client credentials grant and send the resulting bearer token on every request.

Get a token

curl -X POST "https://auth.billerang.com/realms/billerang/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=$CLIENT_ID" \
-d "client_secret=$CLIENT_SECRET"

For a local stack the token endpoint is the local Keycloak container, typically http://localhost:8081/realms/billerang/protocol/openid-connect/token.

Response:

{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 300
}

Call the API

Send the token as a Bearer credential on every request:

curl -H "Authorization: Bearer $ACCESS_TOKEN" \
https://acme.billerang.com/api/v1/sellers

Required headers:

Authorization: Bearer <token>
Content-Type: application/json
Accept: application/json

Token refresh

The client credentials grant issues a short-lived access token (expires_in above, in seconds). There is no refresh token in this grant; when the token expires, request a new one with the same client credentials call. Client code should treat a 401 on any call as a signal to fetch a fresh token and retry once.

Scripted setup

#!/bin/bash
export KEYCLOAK_URL="https://auth.billerang.com"
export KEYCLOAK_REALM="billerang"
export CLIENT_ID="your-client-id"
export CLIENT_SECRET="your-client-secret"

get_token() {
curl -s -X POST "${KEYCLOAK_URL}/realms/${KEYCLOAK_REALM}/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=${CLIENT_ID}" \
-d "client_secret=${CLIENT_SECRET}" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])"
}

export ACCESS_TOKEN=$(get_token)

Postman

{
"auth": {
"type": "oauth2",
"oauth2": [
{"key": "tokenName", "value": "Billerang Token"},
{"key": "accessTokenUrl", "value": "{{keycloak_url}}/realms/{{realm}}/protocol/openid-connect/token"},
{"key": "clientId", "value": "{{client_id}}"},
{"key": "clientSecret", "value": "{{client_secret}}"},
{"key": "grant_type", "value": "client_credentials"}
]
}
}

Error responses

401 Unauthorized

{
"status": "FAIL",
"message": "Authentication required"
}

403 Forbidden

{
"status": "FAIL",
"message": "Insufficient permissions for this operation"
}

See API principles for the full error response convention.