commissionsight 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,23 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ *.egg
6
+ .eggs/
7
+ build/
8
+ dist/
9
+ .pytest_cache/
10
+ .mypy_cache/
11
+ .ruff_cache/
12
+ .coverage
13
+ htmlcov/
14
+
15
+ # Virtual envs
16
+ .venv/
17
+ venv/
18
+ env/
19
+
20
+ # Editors / OS
21
+ .idea/
22
+ .vscode/
23
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CommissionSight
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,328 @@
1
+ Metadata-Version: 2.4
2
+ Name: commissionsight
3
+ Version: 0.1.0
4
+ Summary: Lightweight, zero-dependency Python client for the CommissionSight API.
5
+ Project-URL: Homepage, https://commissionsight.com
6
+ Project-URL: Documentation, https://docs.commissionsight.com
7
+ Project-URL: Repository, https://github.com/commissionsight/python-sdk
8
+ Project-URL: Issues, https://github.com/commissionsight/python-sdk/issues
9
+ Author: CommissionSight
10
+ License: MIT License
11
+
12
+ Copyright (c) 2026 CommissionSight
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+ License-File: LICENSE
32
+ Keywords: api-client,carrier,commission,commissionsight,insurance,sdk
33
+ Classifier: Development Status :: 4 - Beta
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3.8
38
+ Classifier: Programming Language :: Python :: 3.9
39
+ Classifier: Programming Language :: Python :: 3.10
40
+ Classifier: Programming Language :: Python :: 3.11
41
+ Classifier: Programming Language :: Python :: 3.12
42
+ Classifier: Typing :: Typed
43
+ Requires-Python: >=3.8
44
+ Provides-Extra: dev
45
+ Requires-Dist: pytest>=7; extra == 'dev'
46
+ Description-Content-Type: text/markdown
47
+
48
+ # commissionsight (Python)
49
+
50
+ [![PyPI](https://img.shields.io/pypi/v/commissionsight.svg)](https://pypi.org/project/commissionsight/)
51
+ [![license](https://img.shields.io/pypi/l/commissionsight.svg)](./LICENSE)
52
+
53
+ A lightweight, **zero-dependency** Python client for the [CommissionSight](https://commissionsight.com) API. It mirrors the surface area of the official [TypeScript SDK](https://github.com/commissionsight/sdk).
54
+
55
+ 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.
56
+
57
+ - **Zero runtime dependencies** — just the Python standard library (`urllib`).
58
+ - **Typed** — response shapes are exported as `TypedDict`s; the package ships `py.typed`.
59
+ - **Testable** — inject a custom transport (or your own `requests`/`httpx`) for tests and non-standard runtimes.
60
+ - **Python 3.8+**.
61
+
62
+ ---
63
+
64
+ ## Installation
65
+
66
+ ```bash
67
+ pip install commissionsight
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Quick start
73
+
74
+ ```python
75
+ from commissionsight import CommissionSightClient
76
+
77
+ cs = CommissionSightClient(
78
+ "https://api.commissionsight.com/v1",
79
+ token="...", # a per-account API token
80
+ )
81
+
82
+ carriers = cs.list_carriers()
83
+ print(carriers["data"]) # [{"id": ..., "name": ..., "slug": ...}, ...]
84
+ ```
85
+
86
+ ### Client options
87
+
88
+ ```python
89
+ CommissionSightClient(
90
+ base_url, # e.g. https://api.commissionsight.com/v1 (trailing slash optional)
91
+ token=None, # Bearer token; can also be set later via set_token()
92
+ transport=None, # custom transport (method, url, headers, body) -> (status, text)
93
+ )
94
+ ```
95
+
96
+ Set or rotate the token at any time:
97
+
98
+ ```python
99
+ cs.set_token(new_token)
100
+ cs.set_token(None) # clear it
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Authentication
106
+
107
+ 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>`.
108
+
109
+ ---
110
+
111
+ ## Uploading a statement & tracking the job
112
+
113
+ Uploading a file kicks off an asynchronous ingest **job**. Poll the job until it's `completed`, then read the scored results.
114
+
115
+ ```python
116
+ import time
117
+
118
+ # `file` can be a path, bytes, a file object, or a (filename, content) tuple.
119
+ res = cs.upload_file(
120
+ "statements/aetna-2026-05.csv",
121
+ carrier_id="car_123",
122
+ period_year=2026,
123
+ period_month=5,
124
+ webhook_url="https://acme.com/hooks/commissionsight", # optional
125
+ idempotency_key="acme-2026-05-aetna", # optional, safe retries
126
+ )
127
+ job_id = res["jobId"]
128
+
129
+ job = cs.get_job(job_id)
130
+ while job["status"] in ("queued", "processing"):
131
+ time.sleep(1.5)
132
+ job = cs.get_job(job_id)
133
+ if job["status"] == "failed":
134
+ raise RuntimeError(job.get("error") or "ingest failed")
135
+
136
+ results = cs.get_job_results(job_id, status="yellow")
137
+ for row in results["data"]:
138
+ print(row["memberRefId"], row["status"], row["flags"], row["commissionAmount"])
139
+ ```
140
+
141
+ ### Re-scoring after an out-of-order upload
142
+
143
+ 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:
144
+
145
+ ```python
146
+ files = cs.list_files(carrier_id="car_123")
147
+ for f in files["data"]:
148
+ if f.get("rescoreSuggested"):
149
+ cs.rescore_file(f["id"])
150
+ ```
151
+
152
+ ### Correcting or removing a statement
153
+
154
+ 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.
155
+
156
+ ```python
157
+ res = cs.upload_file(
158
+ corrected_file,
159
+ carrier_id="car_123",
160
+ period_year=2026,
161
+ period_month=4,
162
+ replace=True, # omit -> 409 period_exists if the period already exists
163
+ )
164
+ # res["mode"] == "replace"
165
+
166
+ # Or remove a period entirely (no re-upload), re-scoring the next month:
167
+ cs.retract_file(file_id)
168
+ ```
169
+
170
+ ---
171
+
172
+ ## Status & flags
173
+
174
+ | `status` | Meaning |
175
+ | --------- | ------- |
176
+ | 🟢 `green` | Present and unchanged vs. the prior period. |
177
+ | 🟡 `yellow` | Present but something tracked changed (see flags). |
178
+ | 🔴 `red` | Present in the prior period, **absent now** (dropped). |
179
+
180
+ | `flag` | Meaning |
181
+ | ------------------------- | ------- |
182
+ | `NEW` | First time this member is seen. |
183
+ | `COMMISSION_CHANGED` | Commission amount differs from the prior period. |
184
+ | `DATA_CHANGED` | A tracked non-commission field changed. |
185
+ | `DROPPED` | Was present before, missing now. |
186
+ | `REAPPEARED` | Returned after being absent. |
187
+ | `REAPPEARED_WITH_DELTA` | Returned **and** came back with a different commission. |
188
+ | `CHARGEBACK` | A negative-commission (clawback) record this period. |
189
+
190
+ ```python
191
+ from commissionsight import Status, Flag, ResultRow
192
+ ```
193
+
194
+ ---
195
+
196
+ ## Reading data
197
+
198
+ ```python
199
+ # Files & jobs
200
+ cs.list_files(carrier_id=carrier_id, limit=50)
201
+ cs.list_jobs(status="completed")
202
+ cs.get_job_results(job_id, status="red", limit=100, offset=0)
203
+ cs.get_job_deltas(job_id, change_type="COMMISSION_CHANGED")
204
+ cs.retry_job(job_id)
205
+
206
+ # Members & policies — status, timeline, and the full audit journey
207
+ cs.list_members(carrier_id=carrier_id, status="yellow")
208
+ cs.get_member_timeline(member_ref_id)
209
+ cs.get_member_journey(member_ref_id) # every period, source file, status + field changes
210
+ cs.get_policy_journey(policy_ref_id)
211
+
212
+ # Rejected rows from an ingest (exception file, as CSV text)
213
+ csv_text = cs.download_exceptions(job_id)
214
+
215
+ # Carriers & their mapping configs
216
+ cs.list_carriers(with_config=True)
217
+ cs.list_configs(carrier_id)
218
+ ```
219
+
220
+ ### Commission owed (expected vs. actual)
221
+
222
+ ```python
223
+ cs.upsert_expected_rate(carrier_id=carrier_id, rate_type="percent_of_premium", rate_value=0.2)
224
+ rollup = cs.rollup("2026-05", carrier_id)
225
+ print(rollup["totals"]["commissionOwed"], rollup["totals"]["owedEvaluated"])
226
+ ```
227
+
228
+ ### Chargebacks
229
+
230
+ ```python
231
+ cb = cs.list_chargebacks(carrier_id=carrier_id) # negative-commission events + original payout
232
+ ```
233
+
234
+ ### Webhooks
235
+
236
+ ```python
237
+ cs.create_webhook(url="https://acme.com/hooks/cs", events=["job.completed"])
238
+ ```
239
+
240
+ ### Compare any two periods
241
+
242
+ ```python
243
+ cmp = cs.compare(from_period="2026-04", to_period="2026-05", carrier_id=carrier_id)
244
+ print(cmp["summary"]) # {"green", "yellow", "red", "new", "reappeared", "total"}
245
+ ```
246
+
247
+ ### Reports
248
+
249
+ ```python
250
+ cs.rollup("2026-05", carrier_id) # period totals by status + by carrier
251
+ cs.attrition("2026-05", carrier_id) # attrition rate for a period
252
+ cs.attrition_series(months=12) # attrition trend
253
+ cs.data_quality("2026-05") # statement-quality signals (ok/watch/alert)
254
+ ```
255
+
256
+ ---
257
+
258
+ ## Admin
259
+
260
+ Admin endpoints (require an `admin`-role session) live under `cs.admin`:
261
+
262
+ ```python
263
+ cs.admin.list_accounts(status="pending")
264
+ cs.admin.account_overview(account_id)
265
+ cs.admin.metrics()
266
+ cs.admin.revenue()
267
+ ```
268
+
269
+ ---
270
+
271
+ ## Pagination
272
+
273
+ List endpoints return a `data` list plus an optional `pagination` object:
274
+
275
+ ```python
276
+ {
277
+ "data": [...],
278
+ "pagination": {"limit": 50, "offset": 0, "nextCursor": None, "hasMore": False},
279
+ }
280
+ ```
281
+
282
+ Offset-based endpoints accept `limit` / `offset`; cursor-based ones (e.g. `list_files`) accept `limit` / `cursor` and return `nextCursor`.
283
+
284
+ ---
285
+
286
+ ## Error handling
287
+
288
+ Any non-2xx response raises an `ApiError`, carrying the HTTP `status` and the parsed [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) `problem+json` `body` when present.
289
+
290
+ ```python
291
+ from commissionsight import ApiError
292
+
293
+ try:
294
+ cs.get_job("does-not-exist")
295
+ except ApiError as err:
296
+ print(err.status) # e.g. 404
297
+ print(str(err)) # problem `title`
298
+ print(err.body) # full problem+json payload
299
+ ```
300
+
301
+ ---
302
+
303
+ ## Custom transport
304
+
305
+ 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)`:
306
+
307
+ ```python
308
+ import requests
309
+
310
+ def transport(method, url, headers, body):
311
+ r = requests.request(method, url, headers=headers, data=body)
312
+ return r.status_code, r.text
313
+
314
+ cs = CommissionSightClient("https://api.commissionsight.com/v1", token="...", transport=transport)
315
+ ```
316
+
317
+ ---
318
+
319
+ ## Links
320
+
321
+ - **Website:** https://commissionsight.com
322
+ - **API docs:** https://docs.commissionsight.com
323
+ - **TypeScript SDK:** https://github.com/commissionsight/sdk
324
+ - **Issues:** https://github.com/commissionsight/python-sdk/issues
325
+
326
+ ## License
327
+
328
+ [MIT](./LICENSE) © CommissionSight
@@ -0,0 +1,281 @@
1
+ # commissionsight (Python)
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/commissionsight.svg)](https://pypi.org/project/commissionsight/)
4
+ [![license](https://img.shields.io/pypi/l/commissionsight.svg)](./LICENSE)
5
+
6
+ A lightweight, **zero-dependency** Python client for the [CommissionSight](https://commissionsight.com) API. It mirrors the surface area of the official [TypeScript SDK](https://github.com/commissionsight/sdk).
7
+
8
+ 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.
9
+
10
+ - **Zero runtime dependencies** — just the Python standard library (`urllib`).
11
+ - **Typed** — response shapes are exported as `TypedDict`s; the package ships `py.typed`.
12
+ - **Testable** — inject a custom transport (or your own `requests`/`httpx`) for tests and non-standard runtimes.
13
+ - **Python 3.8+**.
14
+
15
+ ---
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install commissionsight
21
+ ```
22
+
23
+ ---
24
+
25
+ ## Quick start
26
+
27
+ ```python
28
+ from commissionsight import CommissionSightClient
29
+
30
+ cs = CommissionSightClient(
31
+ "https://api.commissionsight.com/v1",
32
+ token="...", # a per-account API token
33
+ )
34
+
35
+ carriers = cs.list_carriers()
36
+ print(carriers["data"]) # [{"id": ..., "name": ..., "slug": ...}, ...]
37
+ ```
38
+
39
+ ### Client options
40
+
41
+ ```python
42
+ CommissionSightClient(
43
+ base_url, # e.g. https://api.commissionsight.com/v1 (trailing slash optional)
44
+ token=None, # Bearer token; can also be set later via set_token()
45
+ transport=None, # custom transport (method, url, headers, body) -> (status, text)
46
+ )
47
+ ```
48
+
49
+ Set or rotate the token at any time:
50
+
51
+ ```python
52
+ cs.set_token(new_token)
53
+ cs.set_token(None) # clear it
54
+ ```
55
+
56
+ ---
57
+
58
+ ## Authentication
59
+
60
+ 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>`.
61
+
62
+ ---
63
+
64
+ ## Uploading a statement & tracking the job
65
+
66
+ Uploading a file kicks off an asynchronous ingest **job**. Poll the job until it's `completed`, then read the scored results.
67
+
68
+ ```python
69
+ import time
70
+
71
+ # `file` can be a path, bytes, a file object, or a (filename, content) tuple.
72
+ res = cs.upload_file(
73
+ "statements/aetna-2026-05.csv",
74
+ carrier_id="car_123",
75
+ period_year=2026,
76
+ period_month=5,
77
+ webhook_url="https://acme.com/hooks/commissionsight", # optional
78
+ idempotency_key="acme-2026-05-aetna", # optional, safe retries
79
+ )
80
+ job_id = res["jobId"]
81
+
82
+ job = cs.get_job(job_id)
83
+ while job["status"] in ("queued", "processing"):
84
+ time.sleep(1.5)
85
+ job = cs.get_job(job_id)
86
+ if job["status"] == "failed":
87
+ raise RuntimeError(job.get("error") or "ingest failed")
88
+
89
+ results = cs.get_job_results(job_id, status="yellow")
90
+ for row in results["data"]:
91
+ print(row["memberRefId"], row["status"], row["flags"], row["commissionAmount"])
92
+ ```
93
+
94
+ ### Re-scoring after an out-of-order upload
95
+
96
+ 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:
97
+
98
+ ```python
99
+ files = cs.list_files(carrier_id="car_123")
100
+ for f in files["data"]:
101
+ if f.get("rescoreSuggested"):
102
+ cs.rescore_file(f["id"])
103
+ ```
104
+
105
+ ### Correcting or removing a statement
106
+
107
+ 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.
108
+
109
+ ```python
110
+ res = cs.upload_file(
111
+ corrected_file,
112
+ carrier_id="car_123",
113
+ period_year=2026,
114
+ period_month=4,
115
+ replace=True, # omit -> 409 period_exists if the period already exists
116
+ )
117
+ # res["mode"] == "replace"
118
+
119
+ # Or remove a period entirely (no re-upload), re-scoring the next month:
120
+ cs.retract_file(file_id)
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Status & flags
126
+
127
+ | `status` | Meaning |
128
+ | --------- | ------- |
129
+ | 🟢 `green` | Present and unchanged vs. the prior period. |
130
+ | 🟡 `yellow` | Present but something tracked changed (see flags). |
131
+ | 🔴 `red` | Present in the prior period, **absent now** (dropped). |
132
+
133
+ | `flag` | Meaning |
134
+ | ------------------------- | ------- |
135
+ | `NEW` | First time this member is seen. |
136
+ | `COMMISSION_CHANGED` | Commission amount differs from the prior period. |
137
+ | `DATA_CHANGED` | A tracked non-commission field changed. |
138
+ | `DROPPED` | Was present before, missing now. |
139
+ | `REAPPEARED` | Returned after being absent. |
140
+ | `REAPPEARED_WITH_DELTA` | Returned **and** came back with a different commission. |
141
+ | `CHARGEBACK` | A negative-commission (clawback) record this period. |
142
+
143
+ ```python
144
+ from commissionsight import Status, Flag, ResultRow
145
+ ```
146
+
147
+ ---
148
+
149
+ ## Reading data
150
+
151
+ ```python
152
+ # Files & jobs
153
+ cs.list_files(carrier_id=carrier_id, limit=50)
154
+ cs.list_jobs(status="completed")
155
+ cs.get_job_results(job_id, status="red", limit=100, offset=0)
156
+ cs.get_job_deltas(job_id, change_type="COMMISSION_CHANGED")
157
+ cs.retry_job(job_id)
158
+
159
+ # Members & policies — status, timeline, and the full audit journey
160
+ cs.list_members(carrier_id=carrier_id, status="yellow")
161
+ cs.get_member_timeline(member_ref_id)
162
+ cs.get_member_journey(member_ref_id) # every period, source file, status + field changes
163
+ cs.get_policy_journey(policy_ref_id)
164
+
165
+ # Rejected rows from an ingest (exception file, as CSV text)
166
+ csv_text = cs.download_exceptions(job_id)
167
+
168
+ # Carriers & their mapping configs
169
+ cs.list_carriers(with_config=True)
170
+ cs.list_configs(carrier_id)
171
+ ```
172
+
173
+ ### Commission owed (expected vs. actual)
174
+
175
+ ```python
176
+ cs.upsert_expected_rate(carrier_id=carrier_id, rate_type="percent_of_premium", rate_value=0.2)
177
+ rollup = cs.rollup("2026-05", carrier_id)
178
+ print(rollup["totals"]["commissionOwed"], rollup["totals"]["owedEvaluated"])
179
+ ```
180
+
181
+ ### Chargebacks
182
+
183
+ ```python
184
+ cb = cs.list_chargebacks(carrier_id=carrier_id) # negative-commission events + original payout
185
+ ```
186
+
187
+ ### Webhooks
188
+
189
+ ```python
190
+ cs.create_webhook(url="https://acme.com/hooks/cs", events=["job.completed"])
191
+ ```
192
+
193
+ ### Compare any two periods
194
+
195
+ ```python
196
+ cmp = cs.compare(from_period="2026-04", to_period="2026-05", carrier_id=carrier_id)
197
+ print(cmp["summary"]) # {"green", "yellow", "red", "new", "reappeared", "total"}
198
+ ```
199
+
200
+ ### Reports
201
+
202
+ ```python
203
+ cs.rollup("2026-05", carrier_id) # period totals by status + by carrier
204
+ cs.attrition("2026-05", carrier_id) # attrition rate for a period
205
+ cs.attrition_series(months=12) # attrition trend
206
+ cs.data_quality("2026-05") # statement-quality signals (ok/watch/alert)
207
+ ```
208
+
209
+ ---
210
+
211
+ ## Admin
212
+
213
+ Admin endpoints (require an `admin`-role session) live under `cs.admin`:
214
+
215
+ ```python
216
+ cs.admin.list_accounts(status="pending")
217
+ cs.admin.account_overview(account_id)
218
+ cs.admin.metrics()
219
+ cs.admin.revenue()
220
+ ```
221
+
222
+ ---
223
+
224
+ ## Pagination
225
+
226
+ List endpoints return a `data` list plus an optional `pagination` object:
227
+
228
+ ```python
229
+ {
230
+ "data": [...],
231
+ "pagination": {"limit": 50, "offset": 0, "nextCursor": None, "hasMore": False},
232
+ }
233
+ ```
234
+
235
+ Offset-based endpoints accept `limit` / `offset`; cursor-based ones (e.g. `list_files`) accept `limit` / `cursor` and return `nextCursor`.
236
+
237
+ ---
238
+
239
+ ## Error handling
240
+
241
+ Any non-2xx response raises an `ApiError`, carrying the HTTP `status` and the parsed [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) `problem+json` `body` when present.
242
+
243
+ ```python
244
+ from commissionsight import ApiError
245
+
246
+ try:
247
+ cs.get_job("does-not-exist")
248
+ except ApiError as err:
249
+ print(err.status) # e.g. 404
250
+ print(str(err)) # problem `title`
251
+ print(err.body) # full problem+json payload
252
+ ```
253
+
254
+ ---
255
+
256
+ ## Custom transport
257
+
258
+ 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)`:
259
+
260
+ ```python
261
+ import requests
262
+
263
+ def transport(method, url, headers, body):
264
+ r = requests.request(method, url, headers=headers, data=body)
265
+ return r.status_code, r.text
266
+
267
+ cs = CommissionSightClient("https://api.commissionsight.com/v1", token="...", transport=transport)
268
+ ```
269
+
270
+ ---
271
+
272
+ ## Links
273
+
274
+ - **Website:** https://commissionsight.com
275
+ - **API docs:** https://docs.commissionsight.com
276
+ - **TypeScript SDK:** https://github.com/commissionsight/sdk
277
+ - **Issues:** https://github.com/commissionsight/python-sdk/issues
278
+
279
+ ## License
280
+
281
+ [MIT](./LICENSE) © CommissionSight