Uploading statements
Upload is a single multipart request. It writes the raw bytes to object storage, creates a job, and
returns 202 Accepted immediately with a jobId. Ingest then runs asynchronously (map → score →
deltas); poll the job or get a webhook when it finishes.
curl -X POST https://api.commissionsight.com/v1/files \ -H "Authorization: Bearer $TOKEN" \ -H "Idempotency-Key: 2025-03-acme-health" \ -F "file=@march.csv" \ -F "carrierId=$CARRIER_ID" \ -F "periodYear=2025" \ -F "periodMonth=3" \ -F "webhookUrl=https://you.example.com/hooks/cs" # optional — see below{ "jobId": "…", "fileId": "…", "status": "queued", "_links": { "self": { "href": ".../files/…" }, "job": { "href": ".../jobs/…" }, "results": { "href": ".../jobs/…/results" } }}Fields
| Field | Required | Notes |
|---|---|---|
file | yes | The statement — CSV or XLSX (the carrier’s config declares which). |
carrierId | yes | Which carrier this is; its config drives the mapping. |
periodYear / periodMonth | yes | The statement period (e.g. 2025 / 3). Scoring compares it to the prior period. |
webhookUrl | no | A one-off signed callback for this job (see below). Account-wide subscriptions use /v1/webhooks. |
statedTotal | no | The carrier’s stated total commission for this statement (a single number). When supplied, it’s reconciled against the commission actually ingested — see below. |
workspaceId | conditional | Target workspace. Required when the account has multiple workspaces (omitting it returns 422 workspace_required); omit for single-workspace accounts. The period/replace gate is scoped per workspace. |
replace | no | Set to true to overwrite an existing statement for this carrier+period (see below). Or send the X-Replace-Period: true header. Default false. |
To ingest many files for a carrier, send one upload per file — each becomes its own job and processes independently. (To split one large period across files, see Scale.)
Correcting a statement (replace)
Uploading is never silently destructive. If a non-superseded file already exists for this exact
carrier and period, the upload is refused with 409 period_exists rather than overwriting it:
{ "title": "A statement for this carrier and 2025-03 already exists (1240 members on file)…", "status": 409, "code": "period_exists" }To replace it, re-send the request with replace=true (or the X-Replace-Period: true header). The
API retracts the existing period and re-ingests the corrected file as one atomic operation, so
members the corrected file drops leave no orphan rows, and the immediately-following month is
re-scored automatically. The 202 response then carries "mode": "replace".
curl -X POST https://api.commissionsight.com/v1/files \ -H "Authorization: Bearer $TOKEN" \ -F "file=@march-corrected.csv" \ -F "carrierId=$CARRIER_ID" -F "periodYear=2025" -F "periodMonth=3" \ -F "replace=true"Note this is per carrier+period: uploading a new period (e.g. April when only March exists) is
not a conflict and never warns — period_exists fires only when that exact period already has a file.
Removing a statement (retract)
DELETE /v1/files/{fileId} retracts a period entirely without re-uploading — it deletes the
period’s data (across the whole carrier+period, so a period split across files clears as a unit) and
re-scores the following month. Returns 202 with "mode": "retract", or 409 already_retracted if
the file was already retracted/replaced. Retracted and replaced files are hidden from GET /v1/files.
Statement-total reconciliation
Optionally pass statedTotal — the single total-commission figure the carrier prints on the
statement (the net payable). After ingest, CommissionSight compares it to the sum of the commission
line items it actually ingested. If they diverge beyond tolerance (the greater of $0.01 or 0.5% of
the stated total), the finished job carries a statement_total_variance warning and its stats
include statedTotal, ingestedTotal, and variance — the earliest signal that rows were silently
dropped or mis-parsed. Omit the field and nothing changes (no reconciliation). See Polling
jobs for where the warning and figures surface.
curl -X POST https://api.commissionsight.com/v1/files \ -H "Authorization: Bearer $TOKEN" \ -F "file=@march.csv" -F "carrierId=$CARRIER_ID" \ -F "periodYear=2025" -F "periodMonth=3" \ -F "statedTotal=128450.00"The figure to enter is the net total (after any same-period clawbacks), since the ingested sum nets same-period chargebacks. The total is reconciled per file (per job), so for a period split across several uploads, enter the total for this file.
Hands-off ingestion (no manual upload)
Uploading is one way in — you don’t have to. On Enterprise, statements can arrive on their own and land in the same pipeline (same mapping, scoring, and owed):
- SFTP — drop files into a secure folder; we map each filename to carrier + period and ingest on a sweep.
- Email — give each carrier a unique inbound address; forward their statement emails there (or have the carrier send them) and the attachment ingests automatically, routed to the right carrier by the address itself.
Get notified when it’s done
Pass webhookUrl to receive a one-off signed POST when this job completes or fails — same
payload and X-CommissionSight-Signature as account webhooks. Omit it and just
poll the job.
Idempotency
Pass an Idempotency-Key header. A repeat upload with the same key for the same account returns the
existing job (with "idempotent": true) instead of creating a new one — safe to retry on
network failures. This is distinct from period_exists above: idempotency replays the same upload,
whereas period_exists guards a different file landing on an already-occupied period.
Validation
carrierId must exist and periodYear/periodMonth must be valid. Rows missing a required field
(member_external_id, commission_amount) are recorded as failures in the job report, never
silently dropped.
Next: Polling jobs.