Authentication
Thredfi uses a two-tier OAuth2 authentication system to provide secure, granular access control.
Authentication Architecture
Partner Credentials (UUID + API Key)
↓
Basic Auth
↓
POST /v1/platform/oauth2/token/
↓
Unscoped Token (Partner Level)
├─→ List Businesses
├─→ Create Business
└─→ Exchange for Business Token
↓
POST /v1/platform/{business_id}/oauth2/token/
↓
Business-Scoped Token
↓
Access Business Resources:
- Invoices & Payments
- Bills & Vendor Payments
- Customers & Vendors
- General Ledger
- Documents
Level 1: Partner-Level (Unscoped) Authentication
Getting an Unscoped Token
Exchange your partner credentials for an unscoped token using HTTP Basic Authentication:
curl -X POST https://dev-backend.thredfi.com/v1/platform/oauth2/token/ \
-H "Authorization: Basic $( echo -n 'PARTNER_UUID:API_KEY' | base64 )"
Response :
{
"access_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ,
"token_type" : "Bearer" ,
"expires_in" : 3600
}
What Can You Do With Unscoped Token?
List all businesses under your partner account
Create new businesses
Update business details
Archive/unarchive businesses
Unscoped tokens cannot access business-specific resources like invoices, bills, or customers. You need a business-scoped token for those operations.
Level 2: Business-Level (Scoped) Authentication
Getting a Business-Scoped Token
Exchange your unscoped token for a business-specific token:
curl -X POST https://dev-backend.thredfi.com/v1/platform/BUSINESS_ID/oauth2/token/ \
-H "Authorization: Bearer YOUR_UNSCOPED_TOKEN"
Response :
{
"access_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ,
"token_type" : "Bearer" ,
"expires_in" : 3600 ,
"scope" : "business_access"
}
What Can You Do With Business-Scoped Token?
Create, list, update, void invoices
Record invoice payments
Process refunds and refund payments
Record write-offs for bad debt
Create, list, update, void bills
Record bill payments
Process vendor refunds
Upload AP documents (AI extraction)
Customer & Vendor Management
CRUD operations for customers
CRUD operations for vendors
Archive/unarchive
Search and filtering
View chart of accounts
List journal entries
Export to CSV
Period-based filtering
Upload invoice PDFs (AR documents)
Upload bill PDFs (AP documents)
AI-powered data extraction
Automatic vendor/customer matching
Token Details
All tokens are JSON Web Tokens (JWT) with the following characteristics:
Property Value Algorithm HS256 Expiration 3600 seconds (1 hour) Format Bearer token
Token Payload
Unscoped Token :
{
"sub" : "partner-uuid" ,
"type" : "partner" ,
"iat" : 1234567890 ,
"exp" : 1234571490
}
Business-Scoped Token :
{
"sub" : "partner-uuid" ,
"business_id" : "business-uuid" ,
"scope" : "business_access" ,
"iat" : 1234567890 ,
"exp" : 1234571490
}
Token Validation
Business-scoped tokens are validated to ensure:
Token’s business_id matches the URL parameter
Business belongs to the authenticated partner
Token has not expired
Attempting to use a business-scoped token for a different business ID will result in 403 Forbidden .
Security Best Practices
Never expose API keys Store API keys server-side only. Never include them in client-side code, mobile apps, or version control.
Use HTTPS only All API requests must use HTTPS. HTTP requests will be rejected.
Implement token refresh Tokens expire after 1 hour. Implement refresh logic to avoid service interruptions.
Scope tokens appropriately Use business-scoped tokens only for business operations. Don’t request unnecessary scopes.
Token Refresh Pattern
import time
class ThredfiClient :
def __init__ ( self , partner_uuid , api_key ):
self .partner_uuid = partner_uuid
self .api_key = api_key
self .unscoped_token = None
self .business_tokens = {} # {business_id: {token, expires_at}}
def get_unscoped_token ( self ):
credentials = f " { self .partner_uuid } : { self .api_key } "
encoded = base64.b64encode(credentials.encode()).decode()
response = requests.post(
"https://dev-backend.thredfi.com/v1/platform/oauth2/token/" ,
headers = { "Authorization" : f "Basic { encoded } " }
)
token_data = response.json()
self .unscoped_token = {
"token" : token_data[ "access_token" ],
"expires_at" : time.time() + token_data[ "expires_in" ] - 300 # 5 min buffer
}
return self .unscoped_token[ "token" ]
def get_business_token ( self , business_id ):
# Check if we have a valid cached token
if business_id in self .business_tokens:
token_info = self .business_tokens[business_id]
if time.time() < token_info[ "expires_at" ]:
return token_info[ "token" ]
# Get fresh unscoped token if needed
if not self .unscoped_token or time.time() >= self .unscoped_token[ "expires_at" ]:
self .get_unscoped_token()
# Exchange for business token
response = requests.post(
f "https://dev-backend.thredfi.com/v1/platform/ { business_id } /oauth2/token/" ,
headers = { "Authorization" : f "Bearer { self .unscoped_token[ 'token' ] } " }
)
token_data = response.json()
self .business_tokens[business_id] = {
"token" : token_data[ "access_token" ],
"expires_at" : time.time() + token_data[ "expires_in" ] - 300
}
return self .business_tokens[business_id][ "token" ]
# Usage
client = ThredfiClient(partner_uuid, api_key)
business_token = client.get_business_token(business_id)
Partner Portal Authentication (Separate)
The Partner Portal (dashboard at portal.thredfi.com) uses a different authentication system . Portal authentication is separate from API authentication.
Partner Portal authentication:
Endpoint : POST /api/v1/partner-portal/auth/login/
Method : Email + password (not UUID + API key)
Returns : Partner Portal JWT
Use case : Accessing the web-based partner dashboard
Token type : partner_portal (different from API tokens)
See the Partner Portal API Reference for details.
Common Issues
401 Unauthorized
Cause : Invalid or expired token
Solution : Refresh your token or re-authenticate
Example Error :
{
"error" : "Authentication required - missing or invalid API key" ,
"error_code" : "authentication_required"
}
403 Forbidden
Cause : Token is valid but lacks permission (e.g., wrong business_id)
Solution : Verify business_id matches token scope
Example Error :
{
"error" : "Permission denied - insufficient privileges" ,
"error_code" : "permission_denied"
}
Invalid credentials
Cause : Incorrect partner UUID or API key
Solution : Verify credentials with Thredfi support
Example Error :
{
"error" : "Invalid credentials" ,
"error_code" : "invalid_credentials"
}
Next Steps