Overview
This guide walks you through setting up OAuth 2.0 authentication for the GetBill API. OAuth 2.0 provides secure, standardized authorization that allows your application to access GetBill data on behalf of your company.
Why OAuth 2.0?
Security Your credentials are never stored in the client application. Tokens can be revoked without changing passwords.
Scoped Access Request only the permissions your application needs. Users can see exactly what access they’re granting.
Standardized OAuth 2.0 is an industry standard supported by all major platforms and programming languages.
Scalable Suitable for everything from simple scripts to large enterprise integrations.
Step 1: Create an OAuth Client
Access Your Dashboard
Log in to your GetBill account
Navigate to Company
Find the API Client Management section
Click Create New OAuth Client
Save Your Credentials
After creating the client, you’ll receive:
Public identifier for your application (safe to store in client-side code)
Secret key for your application (keep this secure!)
Store your Client Secret securely! Never commit it to version control or expose it in client-side code.
Step 2: Choose Your Grant Type
Client Credentials Grant
Best for : Server-to-server applications, background jobs, automated systems
When to use :
Your application runs on a secure server
No user interaction is required
You’re accessing your own company’s data
Flow :
Your application requests a token directly from the authorization server
No user authorization step is required
Token is returned immediately
Authorization Code Grant
Best for : Web applications where users need to authorize access
When to use :
Multiple users will access the application
Users need to authorize what data your app can access
You’re building a third-party integration
Flow :
Redirect user to GetBill authorization page
User approves access
GetBill redirects back with authorization code
Exchange authorization code for access token
Step 3: Implementation Examples
Client Credentials Implementation
JavaScript (Node.js)
Python
PHP
class GetBillOAuthClient {
constructor ( clientId , clientSecret ) {
this . clientId = clientId ;
this . clientSecret = clientSecret ;
this . accessToken = null ;
this . refreshToken = null ;
this . expiresAt = null ;
}
async getAccessToken () {
if ( this . accessToken && this . expiresAt > Date . now ()) {
return this . accessToken ;
}
if ( this . refreshToken ) {
return this . refreshAccessToken ();
}
return this . requestNewToken ();
}
async requestNewToken () {
const response = await fetch ( 'https://getbill.io/oauth/token' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/x-www-form-urlencoded' ,
},
body: new URLSearchParams ({
grant_type: 'client_credentials' ,
client_id: this . clientId ,
client_secret: this . clientSecret ,
scope: 'debts:read debts:write followups:read company:read'
})
});
if ( ! response . ok ) {
throw new Error ( `Token request failed: ${ response . statusText } ` );
}
const tokenData = await response . json ();
this . updateTokens ( tokenData );
return this . accessToken ;
}
async refreshAccessToken () {
const response = await fetch ( 'https://getbill.io/oauth/token' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/x-www-form-urlencoded' ,
},
body: new URLSearchParams ({
grant_type: 'refresh_token' ,
refresh_token: this . refreshToken ,
client_id: this . clientId ,
client_secret: this . clientSecret
})
});
if ( ! response . ok ) {
// Refresh failed, request new token
return this . requestNewToken ();
}
const tokenData = await response . json ();
this . updateTokens ( tokenData );
return this . accessToken ;
}
updateTokens ( tokenData ) {
this . accessToken = tokenData . access_token ;
this . refreshToken = tokenData . refresh_token ;
this . expiresAt = Date . now () + ( tokenData . expires_in * 1000 ) - 60000 ; // 1 min buffer
}
async makeAPIRequest ( endpoint , options = {}) {
const token = await this . getAccessToken ();
return fetch ( `https://getbill.io/external-api/v1 ${ endpoint } ` , {
... options ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json' ,
... options . headers
}
});
}
}
// Usage
const client = new GetBillOAuthClient ( 'your_client_id' , 'your_client_secret' );
// Make API calls
const response = await client . makeAPIRequest ( '/company/profile' );
const profile = await response . json ();
import requests
import time
from datetime import datetime, timedelta
class GetBillOAuthClient :
def __init__ ( self , client_id , client_secret ):
self .client_id = client_id
self .client_secret = client_secret
self .access_token = None
self .refresh_token = None
self .expires_at = None
def get_access_token ( self ):
if self .access_token and self .expires_at > datetime.now():
return self .access_token
if self .refresh_token:
return self .refresh_access_token()
return self .request_new_token()
def request_new_token ( self ):
data = {
'grant_type' : 'client_credentials' ,
'client_id' : self .client_id,
'client_secret' : self .client_secret,
'scope' : 'debts:read debts:write followups:read company:read'
}
response = requests.post(
'https://getbill.io/oauth/token' ,
data = data
)
response.raise_for_status()
token_data = response.json()
self .update_tokens(token_data)
return self .access_token
def refresh_access_token ( self ):
data = {
'grant_type' : 'refresh_token' ,
'refresh_token' : self .refresh_token,
'client_id' : self .client_id,
'client_secret' : self .client_secret
}
try :
response = requests.post(
'https://getbill.io/oauth/token' ,
data = data
)
response.raise_for_status()
token_data = response.json()
self .update_tokens(token_data)
return self .access_token
except requests.RequestException:
# Refresh failed, request new token
return self .request_new_token()
def update_tokens ( self , token_data ):
self .access_token = token_data[ 'access_token' ]
self .refresh_token = token_data.get( 'refresh_token' )
expires_in = token_data.get( 'expires_in' , 3600 )
self .expires_at = datetime.now() + timedelta( seconds = expires_in - 60 )
def make_api_request ( self , endpoint , method = 'GET' , ** kwargs ):
token = self .get_access_token()
headers = kwargs.get( 'headers' , {})
headers[ 'Authorization' ] = f 'Bearer { token } '
headers[ 'Content-Type' ] = 'application/json'
kwargs[ 'headers' ] = headers
return requests.request(
method,
f 'https://getbill.io/external-api/v1 { endpoint } ' ,
** kwargs
)
# Usage
client = GetBillOAuthClient( 'your_client_id' , 'your_client_secret' )
# Make API calls
response = client.make_api_request( '/company/profile' )
profile = response.json()
<? php
class GetBillOAuthClient {
private $clientId ;
private $clientSecret ;
private $accessToken ;
private $refreshToken ;
private $expiresAt ;
public function __construct ( $clientId , $clientSecret ) {
$this -> clientId = $clientId ;
$this -> clientSecret = $clientSecret ;
}
public function getAccessToken () {
if ( $this -> accessToken && $this -> expiresAt > time ()) {
return $this -> accessToken ;
}
if ( $this -> refreshToken ) {
return $this -> refreshAccessToken ();
}
return $this -> requestNewToken ();
}
private function requestNewToken () {
$data = [
'grant_type' => 'client_credentials' ,
'client_id' => $this -> clientId ,
'client_secret' => $this -> clientSecret ,
'scope' => 'debts:read debts:write followups:read company:read'
];
$response = $this -> makeHttpRequest ( 'https://getbill.io/oauth/token' , $data );
$this -> updateTokens ( $response );
return $this -> accessToken ;
}
private function refreshAccessToken () {
$data = [
'grant_type' => 'refresh_token' ,
'refresh_token' => $this -> refreshToken ,
'client_id' => $this -> clientId ,
'client_secret' => $this -> clientSecret
];
try {
$response = $this -> makeHttpRequest ( 'https://getbill.io/oauth/token' , $data );
$this -> updateTokens ( $response );
return $this -> accessToken ;
} catch ( Exception $e ) {
return $this -> requestNewToken ();
}
}
private function updateTokens ( $tokenData ) {
$this -> accessToken = $tokenData [ 'access_token' ];
$this -> refreshToken = $tokenData [ 'refresh_token' ] ?? null ;
$expiresIn = $tokenData [ 'expires_in' ] ?? 3600 ;
$this -> expiresAt = time () + $expiresIn - 60 ; // 1 min buffer
}
public function makeAPIRequest ( $endpoint , $method = 'GET' , $data = null ) {
$token = $this -> getAccessToken ();
$headers = [
'Authorization: Bearer ' . $token ,
'Content-Type: application/json'
];
$url = 'https://getbill.io/external-api/v1' . $endpoint ;
$ch = curl_init ();
curl_setopt ( $ch , CURLOPT_URL , $url );
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
curl_setopt ( $ch , CURLOPT_HTTPHEADER , $headers );
curl_setopt ( $ch , CURLOPT_CUSTOMREQUEST , $method );
if ( $data ) {
curl_setopt ( $ch , CURLOPT_POSTFIELDS , json_encode ( $data ));
}
$response = curl_exec ( $ch );
$httpCode = curl_getinfo ( $ch , CURLINFO_HTTP_CODE );
curl_close ( $ch );
if ( $httpCode >= 400 ) {
throw new Exception ( "API request failed with status $httpCode " );
}
return json_decode ( $response , true );
}
private function makeHttpRequest ( $url , $data ) {
$ch = curl_init ();
curl_setopt ( $ch , CURLOPT_URL , $url );
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
curl_setopt ( $ch , CURLOPT_POST , true );
curl_setopt ( $ch , CURLOPT_POSTFIELDS , http_build_query ( $data ));
$response = curl_exec ( $ch );
$httpCode = curl_getinfo ( $ch , CURLINFO_HTTP_CODE );
curl_close ( $ch );
if ( $httpCode >= 400 ) {
throw new Exception ( "Token request failed with status $httpCode " );
}
return json_decode ( $response , true );
}
}
// Usage
$client = new GetBillOAuthClient ( 'your_client_id' , 'your_client_secret' );
// Make API calls
$profile = $client -> makeAPIRequest ( '/company/profile' );
?>
Authorization Code Implementation
const express = require ( 'express' );
const session = require ( 'express-session' );
const { URLSearchParams } = require ( 'url' );
const app = express ();
app . use ( session ({
secret: 'your-session-secret' ,
resave: false ,
saveUninitialized: true
}));
const CLIENT_ID = 'your_client_id' ;
const CLIENT_SECRET = 'your_client_secret' ;
const REDIRECT_URI = 'http://localhost:3000/oauth/callback' ;
// Step 1: Redirect to authorization
app . get ( '/login' , ( req , res ) => {
const state = Math . random (). toString ( 36 ). substring ( 2 , 15 );
req . session . oauth_state = state ;
const authURL = new URL ( 'https://getbill.io/oauth/authorize' );
authURL . searchParams . append ( 'response_type' , 'code' );
authURL . searchParams . append ( 'client_id' , CLIENT_ID );
authURL . searchParams . append ( 'redirect_uri' , REDIRECT_URI );
authURL . searchParams . append ( 'scope' , 'debts:read followups:read company:read' );
authURL . searchParams . append ( 'state' , state );
res . redirect ( authURL . toString ());
});
// Step 2: Handle callback
app . get ( '/oauth/callback' , async ( req , res ) => {
const { code , state } = req . query ;
// Verify state parameter
if ( state !== req . session . oauth_state ) {
return res . status ( 400 ). send ( 'Invalid state parameter' );
}
try {
// Exchange authorization code for access token
const tokenResponse = await fetch ( 'https://getbill.io/oauth/token' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/x-www-form-urlencoded' ,
},
body: new URLSearchParams ({
grant_type: 'authorization_code' ,
client_id: CLIENT_ID ,
client_secret: CLIENT_SECRET ,
code: code ,
redirect_uri: REDIRECT_URI
})
});
const tokenData = await tokenResponse . json ();
if ( ! tokenResponse . ok ) {
throw new Error ( tokenData . error_description || 'Token exchange failed' );
}
// Store tokens in session (in production, use a secure database)
req . session . access_token = tokenData . access_token ;
req . session . refresh_token = tokenData . refresh_token ;
res . redirect ( '/dashboard' );
} catch ( error ) {
console . error ( 'OAuth callback error:' , error );
res . status ( 500 ). send ( 'Authentication failed' );
}
});
// Protected route
app . get ( '/dashboard' , async ( req , res ) => {
if ( ! req . session . access_token ) {
return res . redirect ( '/login' );
}
try {
// Make API call with stored token
const profileResponse = await fetch ( 'https://getbill.io/external-api/v1/company/profile' , {
headers: {
'Authorization' : `Bearer ${ req . session . access_token } `
}
});
const profile = await profileResponse . json ();
res . json ( profile );
} catch ( error ) {
console . error ( 'API call error:' , error );
res . status ( 500 ). send ( 'Failed to fetch profile' );
}
});
app . listen ( 3000 , () => {
console . log ( 'Server running on http://localhost:3000' );
console . log ( 'Visit http://localhost:3000/login to start OAuth flow' );
});
Step 4: Security Best Practices
Secure Token Storage
Use environment variables for client secrets
Store tokens securely (encrypted database, secure session storage)
Never log or expose tokens in error messages
State Parameter
Always use the state parameter in authorization code flow
Generate a random, unique state for each authorization request
Verify the state parameter in your callback
HTTPS Only
Always use HTTPS for all OAuth flows
Validate SSL certificates
Never send tokens over unencrypted connections
Token Management
Implement automatic token refresh
Handle token expiration gracefully
Revoke tokens when no longer needed
Environment Variables Example
# .env file
GETBILL_CLIENT_ID = your_client_id_here
GETBILL_CLIENT_SECRET = your_client_secret_here
GETBILL_REDIRECT_URI = https://yourapp.com/oauth/callback
// Load from environment
const CLIENT_ID = process . env . GETBILL_CLIENT_ID ;
const CLIENT_SECRET = process . env . GETBILL_CLIENT_SECRET ;
const REDIRECT_URI = process . env . GETBILL_REDIRECT_URI ;
Step 5: Testing Your Setup
Test Client Credentials
curl -X POST https://getbill.io/oauth/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" \
-d "scope=company:read"
Test Authorization Code Flow
Open your browser to the authorization URL
Complete the authorization process
Verify you receive an authorization code
Exchange the code for an access token
Make a test API call
Troubleshooting
Cause : Incorrect client ID or secretSolution :
Double-check your client credentials
Ensure you’re using the correct environment (test vs production)
Verify the client is active in your dashboard
Cause : Requesting scopes not granted to your clientSolution :
Check your client configuration in the dashboard
Request only the scopes you need
Contact support if you need additional scopes
Cause : Invalid authorization code or expired refresh tokenSolution :
Ensure the authorization code is used immediately
Don’t reuse authorization codes
Handle refresh token expiration by re-authenticating
redirect_uri_mismatch Error
Cause : Redirect URI doesn’t match registered URISolution :
Ensure exact match including protocol, domain, and path
Register all redirect URIs you plan to use
Use localhost for development testing
Production Considerations
Scaling OAuth
Token Caching : Cache tokens in Redis or similar for multiple server instances
Database Storage : Store refresh tokens securely in your database
Rate Limiting : Implement rate limiting for token refresh requests
Monitoring : Monitor token usage and refresh patterns
Security Hardening
Certificate Pinning : Pin SSL certificates for additional security
Token Rotation : Regularly rotate client secrets
Audit Logging : Log all OAuth events for security monitoring
Scope Minimization : Use the minimum required scopes
Next Steps
Once OAuth is set up:
Make your first API call
Explore the API Reference documentation
Set up webhooks for real-time updates
Review best practices for production use
Need help? Contact our support team at contact@getbill.io