Skip to main content

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?

Partner-level operations
  • 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)
  • 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

Token Format

All tokens are JSON Web Tokens (JWT) with the following characteristics:
PropertyValue
AlgorithmHS256
Expiration3600 seconds (1 hour)
FormatBearer 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:
  1. Token’s business_id matches the URL parameter
  2. Business belongs to the authenticated partner
  3. 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

Python
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