Skip to content

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.

Terminal window
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

FieldRequiredNotes
fileyesThe statement — CSV or XLSX (the carrier’s config declares which).
carrierIdyesWhich carrier this is; its config drives the mapping.
periodYear / periodMonthyesThe statement period (e.g. 2025 / 3). Scoring compares it to the prior period.
webhookUrlnoA one-off signed callback for this job (see below). Account-wide subscriptions use /v1/webhooks.
statedTotalnoThe carrier’s stated total commission for this statement (a single number). When supplied, it’s reconciled against the commission actually ingested — see below.
workspaceIdconditionalTarget 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.
replacenoSet 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".

Terminal window
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.

Terminal window
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.