Data Imports & Validation
Earnest takes data two ways: CSV upload in the admin UI, and a REST API for programmatic ingestion from a warehouse, CRM or scheduled job. Both share the same validation rules.
Everything below is available under Admin → Data → Imports & API, which also shows your live
endpoint URLs and a ready-made curl example.
CSV imports
Uploads are capped at 10 MB and 1,000 rows per file. Larger sets should be split, or sent through the API, which takes bigger batches.
The importer matches your header row case-insensitively and ignores spaces and underscores, so
Reference Number, reference_number and referenceNumber all map to the same field. You get a
column-mapping preview before anything is written.
1. Compensation
Loads or updates the roster with quotas, OTE and base salaries. Pick a compensation mode first:
- OTE mode — you supply
ote, Earnest derives the commission rate. - Commission Rate mode — you supply
commissionRate, Earnest derives OTE.
| OTE mode | Commission Rate mode | |
|---|---|---|
| Required | email, effectiveDate, quota, baseSalary, ote | email, effectiveDate, quota, baseSalary, commissionRate |
| Optional | name, title, role, teamName, commissionRate, currency, frequency | name, title, role, teamName, ote, currency, frequency |
| Column | Notes |
|---|---|
email | Must be a valid email address. Matches an existing user or creates one. |
effectiveDate | The date this compensation takes effect. Normalized to the 1st of the month. |
quota | Annual quota. Must be greater than 0. |
baseSalary | Annual base salary. Must be 0 or more. |
ote / commissionRate | Whichever your mode requires. commissionRate is a decimal — 0.10 for 10%. |
role | ADMIN, MANAGER or REP. Defaults to REP. |
teamName | Creates the team if it does not exist. |
currency | ISO 4217, three letters. Defaults to USD if blank. |
2. People
A lighter roster import for adding users without compensation details.
- Required:
email,name - Optional:
role,teamName,managerEmail,currency,ote,baseSalary,commissionRate,quota
managerEmail is what builds your reporting hierarchy — it matters for manager rollup quotas and
for RATIO plans, whose denominator is the manager's subtree.
3. Activities / orders
Deals, bookings, meetings — anything that counts toward quota. The required columns depend on whether you are importing against a Revenue or a Unit plan.
| Revenue | Unit | |
|---|---|---|
| Required | referenceNumber, userEmail, amount, currency, bookingDate | referenceNumber, userEmail, quantity, bookingDate |
| Optional | quantity, unitLabel, splitEmail, splitPercent, planName | unitLabel, amount, currency, splitEmail, splitPercent, planName |
| Column | Notes |
|---|---|
referenceNumber | Unique within your organization. Re-importing the same reference updates the existing record rather than duplicating it. |
userEmail | Must match a user who already exists. Import people before activities. |
bookingDate | Determines which period the activity falls into, and which FX rate applies. |
amount | Required for Revenue. Must be 0 or more. |
currency | Required for Revenue imports — three-letter ISO 4217. |
quantity | Required for Unit. May be fractional. |
planName | Tags the activity to a specific comp plan. Only relevant for reps on a team plan mix. Untagged activities fall to the mix's default plan. |
splitEmail / splitPercent | Share credit with a second rep — see below. |
Validation
Every row is validated before any write. A failing row is skipped with a specific message; passing rows still import. You get a per-row results summary at the end, and persistent import errors are recorded so they can be reviewed later.
| Check | Rule |
|---|---|
| Email format | Must be a valid address, or the row is skipped. |
| Currency code | Three letters. Invalid currency code "X" — must be 3 letters (e.g. USD, EUR). Blank defaults to USD. |
| Positive numbers | quota must be greater than 0. |
| Non-negative numbers | amount and baseSalary must be 0 or more. |
| Whole numbers | Fields that must be integers are rejected with the value that failed. |
| Dates | Must be parsable. ISO 8601 (2026-04-15) is safest. |
| String sanitization | Names, titles and reference numbers are stripped of control characters and truncated to safe lengths. |
| Unknown user | Activity rows whose userEmail has no matching user fail. |
| Published periods | A row landing in a period that already has a published payout is rejected — published periods are frozen. |
| Position windows | An activity outside the rep's position dates can never be paid. Import will take it, but it will hold up publishing until the position is extended or the activity removed. |
Currency mismatches
If an activity's currency differs from the rep's quota currency, the row is accepted and converted at calculation time using the rate for its booking date. Nothing needs pre-converting. See Multi-Currency Explained.
Credit splits
Splits let two reps share credit for one activity.
- The primary rep (
userEmail) always receives 100% credit. - The split rep (
splitEmail) receives their own independent credit atsplitPercent.
Splits are independent, not zero-sum. Credits across one activity can total more than 100%. That is intentional — it reflects overlay credit, SE credit and management rollups, where the same deal legitimately counts for more than one person at full or partial value.
Rules: splitPercent must be greater than 0; you cannot split with the activity's own owner; the
split rep must already exist; and re-importing a split replaces the existing split configuration for
that activity.
Splits can also be edited directly from Admin → Data → Sales Data without re-importing.
Changing splits on a period that already has a draft payout makes that draft stale — the amounts no longer match what the reps earned. Regenerate before publishing.
API ingestion
Generate an API key under Admin → Data → Imports & API. Authenticate with the x-api-key
header.
| Endpoint | Purpose |
|---|---|
/api/ingest/bigquery | Users & compensation |
/api/ingest/bigquery/activities | Sales / activity data |
/api/ingest/bigquery/orders | Legacy alias for /activities — existing pipelines keep working |
| Method | Body |
|---|---|
| POST | A single record. |
| PUT | A batch, up to 5,000 rows per request. |
curl -X POST https://your-org.example.com/api/ingest/bigquery/activities \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_KEY" \
-d '{"referenceNumber":"DEAL-001","userEmail":"rep@co.com","amount":25000,"currency":"USD","bookingDate":"2026-03-15"}'
The compensation endpoint takes email, effectiveDate, quota, baseSalary, and either ote or
commissionRate — plus periodDivisor (12 for monthly, 4 for quarterly) to say how the annual
figures split across periods.
Behaviour
- Upsert — users match on email, activities on reference number. Nothing duplicates.
- Auto-provisioning — unknown users, teams and titles are created as needed.
- Domain enforcement — user emails must match your organization's verified domain(s).
- Rate limiting — requests are throttled to prevent accidental overload.
- Import errors are logged — failures are recorded per row and summarized in a periodic digest.
CSV or API?
| Scenario | Use |
|---|---|
| One-time roster setup | CSV |
| Recurring deal sync from a warehouse | API (PUT) |
| Ad-hoc corrections | CSV, or the Sales Data screen |
| Scheduled pipeline | API |
| More than 1,000 rows at once | API |
Tips
- Import people before activities — activity rows need their rep to exist.
- Use ISO 8601 dates.
- Keep reference numbers stable — they are the update key. Changing one creates a second record.
- Check the results summary — it names every skipped row and why.
- Regenerate affected drafts after a corrective import, or publishing will be held.
Frequently asked questions
Q: What happens if I import the same activity twice?
It updates in place. referenceNumber is the unique key within your org.
Q: What is the file size limit? 10 MB and 1,000 rows per CSV upload. The API takes 5,000 rows per batch request.
Q: Can I import activities dated in the future? Yes, but they only count toward the period containing their booking date, and you cannot draft payouts for a future month.
Q: Can I delete imported data? Individual activities can be deleted from Sales Data. Deleting an activity that a draft payout already used marks that draft stale so it cannot be published with out-of-date figures.
Q: Does importing compensation overwrite existing users? It upserts. Names and currencies update in place; a new position is created only when the compensation actually changed — so your history stays intact and date-scoped. See Scheduled Changes & History.
Q: My import failed on a published period. That is the published-period guard. Published periods are frozen; post the change forward as a correction instead — see Corrections & Clawbacks.