ServiceTrade Python SDK
A Python client for the ServiceTrade REST API with OAuth2 authentication and automatic token refresh.
Installation
pip install servicetrade
Quick Start
Using Client Credentials
from servicetrade import ServicetradeClient
client = ServicetradeClient(
client_id="your-client-id",
client_secret="your-client-secret"
)
# No need to call login() — the SDK authenticates lazily on the first API call.
jobs = client.get("/job")
print(jobs)
Using Refresh Token
from servicetrade import ServicetradeClient
client = ServicetradeClient(
refresh_token="your-refresh-token"
)
jobs = client.get("/job")
Using a Pre-existing Token
from servicetrade import ServicetradeClient
client = ServicetradeClient(
token="your-bearer-token",
# Provide credentials for auto-refresh when the token expires
client_id="your-client-id",
client_secret="your-client-secret"
)
# No login needed if token is valid
jobs = client.get("/job")
API Methods
HTTP Methods
# GET request
result = client.get("/job/123")
# GET with query parameters
jobs = client.get("/job", params={"status": "scheduled", "locationId": 456})
# POST request
job = client.post("/job", {
"type": "inspection",
"description": "Quarterly HVAC Inspection",
"locationId": 123,
"vendorId": 456,
})
# PUT request
client.put("/job/123", {"description": "Updated Inspection Description"})
# DELETE request (returns None)
client.delete("/location/456")
Paginator
Iterate over all pages of a paginated endpoint automatically:
from servicetrade import Paginator, ServicetradeClient, ServicetradeAPIError
client = ServicetradeClient(
client_id="your-client-id",
client_secret="your-client-secret"
)
paginator = Paginator(client, "/job", "jobs", params={"status": "scheduled"})
try:
for job in paginator:
print(f"Job #{job['id']}: {job['description']}")
except ServicetradeAPIError as e:
print(f"Error during pagination: {e.message}")
The Paginator constructor takes:
client— aServicetradeClientinstancepath— the API endpoint path (e.g.,"/job")items_key— the key in the response that contains the list of items (e.g.,"jobs")params— optional dict of query parameters to include on every request
File Attachments
from servicetrade import ServicetradeClient, FileAttachment
client = ServicetradeClient(client_id="your-client-id", client_secret="your-client-secret")
# Upload a file
file = FileAttachment(
value=b"file contents here",
filename="document.pdf",
content_type="application/pdf"
)
result = client.attach(
{"entityType": 3, "entityId": 123, "purposeId": 7},
file
)
Reading from a file path
from pathlib import Path
from servicetrade import FileAttachment
file = FileAttachment(
value=Path("/path/to/document.pdf"),
filename="document.pdf",
content_type="application/pdf"
)
Response Handling
Return values
get(),post(),put()return thedatafield from the response when present, or the full response dict/list otherwise.delete()returnsNone.- When fetching a single resource by ID (e.g.,
/job/123), the response is adict. When querying a collection (e.g.,/job), the response may be adictcontaining a list under a resource-specific key (e.g.,"jobs").
Accessing the full response
Use get_last_response() to access status code, headers, and full body:
result = client.get("/job/123")
response = client.get_last_response()
print(response.status_code) # 200
print(response.is_success()) # True
print(response.body) # Full response body (dict)
print(response.headers) # Response headers (dict)
Accessing the auth token
token = client.get_auth_token()
Configuration Options
client = ServicetradeClient(
# API Configuration
base_url="https://api.servicetrade.com", # Default
api_prefix="/api", # Default
user_agent="My App/1.0", # Custom user agent
# Authentication (pick one)
client_id="your-client-id",
client_secret="your-client-secret",
# OR
refresh_token="your-refresh-token",
# OR
token="your-bearer-token",
# Options
auto_refresh_auth=True, # Auto-refresh tokens (default)
# Callbacks
on_set_auth=lambda token: save_token(token),
on_unset_auth=lambda: clear_token(),
)
Custom Headers
client.set_custom_header("X-Custom-Header", "value")
Error Handling
from servicetrade import (
ServicetradeClient,
ServicetradeAuthError,
ServicetradeAPIError,
)
client = ServicetradeClient(client_id="your-client-id", client_secret="your-client-secret")
try:
result = client.get("/nonexistent")
except ServicetradeAPIError as e:
print(f"API error: {e.message}")
print(f"Status code: {e.status_code}")
print(f"Response data: {e.response_data}")
# Structured errors from ServiceTrade API
if e.error_messages:
print(f"Errors: {e.error_messages}")
if e.validation:
print(f"Validation: {e.validation}")
Authentication Flow
The SDK supports two OAuth2 grant types, prioritized in this order:
- Refresh Token Grant — Most secure, only stores refresh token
- Client Credentials Grant — For service-to-service authentication
Lazy Authentication
The SDK automatically authenticates on the first API call if no token exists. You can also call login() explicitly for eager authentication:
client = ServicetradeClient(client_id="your-client-id", client_secret="your-client-secret")
# Eager authentication (optional)
client.login()
# Or just make API calls — login happens automatically
jobs = client.get("/job")
Automatic Token Refresh
By default, the SDK automatically refreshes tokens when:
- A request returns a 401 Unauthorized response
- The token is about to expire (within 5 minutes of expiry)
To disable automatic refresh:
client = ServicetradeClient(
client_id="id",
client_secret="secret",
auto_refresh_auth=False
)
Development
Setup
# Clone the repository
git clone https://github.com/servicetrade/servicetrade-python-sdk.git
cd servicetrade-python-sdk
# Install development dependencies
pip install -e ".[dev]"
Running Tests
pytest
Type Checking
mypy src
License
MIT License - see LICENSE file for details.
Metadata
Release files for servicetrade 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| servicetrade-1.0.0.tar.gz | 9.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| servicetrade-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 21.1 kB
Release files / servicetrade-1.0.0.tar.gz
| Download URL | servicetrade-1.0.0.tar.gz |
|---|---|
| Size | 9.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
708df3245209322e86749548592c4903799843946cb90ea2c96241da6e57b25f
|
|
BLAKE2b-256 checksum How to use checksums |
3c28d5a8694a46f379524abd73c0609284377fd0e8c08d07fe79eb4d4fe63f43
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 23, 2026.
Transparency logRelease files / servicetrade-1.0.0-py3-none-any.whl
| Download URL | servicetrade-1.0.0-py3-none-any.whl |
|---|---|
| Size | 11.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
02d93e85dd46af295797ea8a025d5d49a1e8894b62378623abd1e1f3334ca413
|
|
BLAKE2b-256 checksum How to use checksums |
6ae8a53fcfbc4a6f6ea4e408a064d845dded553ee817a6f18f1daca49f7c454c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 23, 2026.
Transparency log