Lightweight, zero-dependency Python client for the CommissionSight API.
Project description
commissionsight (Python)
A lightweight, zero-dependency Python client for the CommissionSight API. It mirrors the surface area of the official TypeScript SDK.
CommissionSight ingests carrier commission statements (CSV/XLSX), normalizes them across carriers, and scores every member period-over-period as 🟢 green / 🟡 yellow / 🔴 red with explicit change flags — so you can see new business, commission changes, and attrition at a glance.
- Zero runtime dependencies — just the Python standard library (
urllib). - Typed — response shapes are exported as
TypedDicts; the package shipspy.typed. - Testable — inject a custom transport (or your own
requests/httpx) for tests and non-standard runtimes. - Python 3.8+.
Installation
pip install commissionsight
Quick start
from commissionsight import CommissionSightClient
cs = CommissionSightClient(
"https://api.commissionsight.com/v1",
token="...", # a per-account API token
)
carriers = cs.list_carriers()
print(carriers["data"]) # [{"id": ..., "name": ..., "slug": ...}, ...]
Client options
CommissionSightClient(
base_url, # e.g. https://api.commissionsight.com/v1 (trailing slash optional)
token=None, # Bearer token; can also be set later via set_token()
transport=None, # custom transport (method, url, headers, body) -> (status, text)
)
Set or rotate the token at any time:
cs.set_token(new_token)
cs.set_token(None) # clear it
Authentication
The SDK is for server-to-server integrations, authenticated with a per-account API token issued to you by CommissionSight. Every request is sent as Authorization: Bearer <token>.
Uploading a statement & tracking the job
Uploading a file kicks off an asynchronous ingest job. Poll the job until it's completed, then read the scored results.
import time
# `file` can be a path, bytes, a file object, or a (filename, content) tuple.
res = cs.upload_file(
"statements/aetna-2026-05.csv",
carrier_id="car_123",
period_year=2026,
period_month=5,
webhook_url="https://acme.com/hooks/commissionsight", # optional
idempotency_key="acme-2026-05-aetna", # optional, safe retries
)
job_id = res["jobId"]
job = cs.get_job(job_id)
while job["status"] in ("queued", "processing"):
time.sleep(1.5)
job = cs.get_job(job_id)
if job["status"] == "failed":
raise RuntimeError(job.get("error") or "ingest failed")
results = cs.get_job_results(job_id, status="yellow")
for row in results["data"]:
print(row["memberRefId"], row["status"], row["flags"], row["commissionAmount"])
Re-scoring after an out-of-order upload
If you upload an earlier month after a later one, the later period's scoring becomes stale. list_files() flags this with rescoreSuggested; refresh it without re-uploading:
files = cs.list_files(carrier_id="car_123")
for f in files["data"]:
if f.get("rescoreSuggested"):
cs.rescore_file(f["id"])
Correcting or removing a statement
Uploading over a carrier+period that already has a file fails with 409 (period_exists). To apply a corrected file, pass replace=True: the existing data is retracted and the corrected file re-ingested atomically. The following month is re-scored automatically.
res = cs.upload_file(
corrected_file,
carrier_id="car_123",
period_year=2026,
period_month=4,
replace=True, # omit -> 409 period_exists if the period already exists
)
# res["mode"] == "replace"
# Or remove a period entirely (no re-upload), re-scoring the next month:
cs.retract_file(file_id)
Status & flags
status |
Meaning |
|---|---|
🟢 green |
Present and unchanged vs. the prior period. |
🟡 yellow |
Present but something tracked changed (see flags). |
🔴 red |
Present in the prior period, absent now (dropped). |
flag |
Meaning |
|---|---|
NEW |
First time this member is seen. |
COMMISSION_CHANGED |
Commission amount differs from the prior period. |
DATA_CHANGED |
A tracked non-commission field changed. |
DROPPED |
Was present before, missing now. |
REAPPEARED |
Returned after being absent. |
REAPPEARED_WITH_DELTA |
Returned and came back with a different commission. |
CHARGEBACK |
A negative-commission (clawback) record this period. |
from commissionsight import Status, Flag, ResultRow
Reading data
# Files & jobs
cs.list_files(carrier_id=carrier_id, limit=50)
cs.list_jobs(status="completed")
cs.get_job_results(job_id, status="red", limit=100, offset=0)
cs.get_job_deltas(job_id, change_type="COMMISSION_CHANGED")
cs.retry_job(job_id)
# Members & policies — status, timeline, and the full audit journey
cs.list_members(carrier_id=carrier_id, status="yellow")
cs.get_member_timeline(member_ref_id)
cs.get_member_journey(member_ref_id) # every period, source file, status + field changes
cs.get_policy_journey(policy_ref_id)
# Rejected rows from an ingest (exception file, as CSV text)
csv_text = cs.download_exceptions(job_id)
# Carriers & their mapping configs
cs.list_carriers(with_config=True)
cs.list_configs(carrier_id)
Commission owed (expected vs. actual)
cs.upsert_expected_rate(carrier_id=carrier_id, rate_type="percent_of_premium", rate_value=0.2)
rollup = cs.rollup("2026-05", carrier_id)
print(rollup["totals"]["commissionOwed"], rollup["totals"]["owedEvaluated"])
Chargebacks
cb = cs.list_chargebacks(carrier_id=carrier_id) # negative-commission events + original payout
Webhooks
cs.create_webhook(url="https://acme.com/hooks/cs", events=["job.completed"])
Compare any two periods
cmp = cs.compare(from_period="2026-04", to_period="2026-05", carrier_id=carrier_id)
print(cmp["summary"]) # {"green", "yellow", "red", "new", "reappeared", "total"}
Reports
cs.rollup("2026-05", carrier_id) # period totals by status + by carrier
cs.attrition("2026-05", carrier_id) # attrition rate for a period
cs.attrition_series(months=12) # attrition trend
cs.data_quality("2026-05") # statement-quality signals (ok/watch/alert)
Admin
Admin endpoints (require an admin-role session) live under cs.admin:
cs.admin.list_accounts(status="pending")
cs.admin.account_overview(account_id)
cs.admin.metrics()
cs.admin.revenue()
Pagination
List endpoints return a data list plus an optional pagination object:
{
"data": [...],
"pagination": {"limit": 50, "offset": 0, "nextCursor": None, "hasMore": False},
}
Offset-based endpoints accept limit / offset; cursor-based ones (e.g. list_files) accept limit / cursor and return nextCursor.
Error handling
Any non-2xx response raises an ApiError, carrying the HTTP status and the parsed RFC 9457 problem+json body when present.
from commissionsight import ApiError
try:
cs.get_job("does-not-exist")
except ApiError as err:
print(err.status) # e.g. 404
print(str(err)) # problem `title`
print(err.body) # full problem+json payload
Custom transport
Inject your own transport to use requests/httpx, add retries, or mock in tests. It takes (method, url, headers, body) and returns (status_code, response_text):
import requests
def transport(method, url, headers, body):
r = requests.request(method, url, headers=headers, data=body)
return r.status_code, r.text
cs = CommissionSightClient("https://api.commissionsight.com/v1", token="...", transport=transport)
Links
- Website: https://commissionsight.com
- API docs: https://docs.commissionsight.com
- TypeScript SDK: https://github.com/commissionsight/sdk
- Issues: https://github.com/commissionsight/python-sdk/issues
License
MIT © CommissionSight
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file commissionsight-0.1.0.tar.gz.
File metadata
- Download URL: commissionsight-0.1.0.tar.gz
- Upload date:
- Size: 15.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
deb458e047a3a0447d426c4e2a6edc8c1f933ae8fe1e24660da4c2a78750d471
|
|
| MD5 |
ca4087cc3b198620710ef14b7cf58a09
|
|
| BLAKE2b-256 |
de4d069f9e998d0ccbdd97f65305d62c82113a1adf1a7f3b6423e56460ee6f0d
|
File details
Details for the file commissionsight-0.1.0-py3-none-any.whl.
File metadata
- Download URL: commissionsight-0.1.0-py3-none-any.whl
- Upload date:
- Size: 16.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4914f8ae26a3e92f010660114a698bb973248fa24a9298c55609cf9975492b41
|
|
| MD5 |
e3c3620dc633f0fd948478035f328d53
|
|
| BLAKE2b-256 |
6ee46a83b524eaab0bef9b8f973ad4f4be948db78148c1b77659ab8b33ff1eaa
|