Data Imports & Validation
Earnest supports multiple ways to get your data in: CSV imports (via the admin UI) and a REST API for programmatic ingestion from systems like BigQuery, Salesforce, or your own data pipeline. This article covers CSV formatting rules, API basics, and the validation safeguards that protect your data.
Import Types
1. Compensation Data
Use this to bulk-load or update your team's roster — names, titles, quotas, OTE, and base salaries.
Required columns:
| Column | Description | Validation |
|---|---|---|
email | Rep's email address | Must be a valid email format. |
name | Display name | Sanitized and truncated for safety. |
title | Job title (e.g., "Senior AE") | Used for team title matching. |
teamName | Team assignment | Creates the team if it doesn't exist. |
month | Effective month (e.g., "2026-04-01") | Must be a valid date. Normalized to the 1st of the month. |
quota | Annual quota amount | Must be a positive number (> 0). |
baseSalary | Annual base salary | Must be non-negative (≥ 0). |
Optional columns:
| Column | Description | Default |
|---|---|---|
role | ADMIN, MANAGER, or REP | REP |
currency | ISO 4217 code (e.g., USD, EUR, GBP) | USD |
ote | On-Target Earnings (annual) | Required in OTE mode. |
commissionRate | Decimal rate (e.g., 0.10 for 10%) | Required in Commission Rate mode. |
Compensation Mode: You choose one of two modes during import:
- OTE mode — you provide
oteand Earnest derives the commission rate. - Commission Rate mode — you provide the explicit
commissionRateand Earnest derives OTE from it.
2. Activity / Order Data
Use this to import deals, bookings, meetings, or any event that counts toward quota.
Required columns:
| Column | Description | Validation |
|---|---|---|
referenceNumber | Unique identifier (e.g., ORD-001, MTG-042) | Must be non-empty. Duplicates update the existing record. |
userEmail | The rep this activity is credited to | Must match an existing user in your org. |
bookingDate | When the activity occurred | Must be a valid date. |
amount | Monetary value of the deal | Must be ≥ 0 for Revenue plans. Optional (defaults to 0) for Unit plans. |
Optional columns:
| Column | Description | Default |
|---|---|---|
currency | ISO 4217 currency code | USD |
quantity | Unit count (for Unit metric plans) | Required for Unit plans; optional for Revenue. |
unitLabel | Display label (e.g., "Meetings", "Demos") | Inherited from the plan if omitted. |
splitEmail | Email of a second rep to share credit with | No split. |
splitPercent | Percentage of credit for the split rep | Required if splitEmail is provided. Must be > 0. |
Validation Safeguards
Every row passes through a validation pipeline before any database writes occur. If a row fails validation, it's skipped with a detailed error message — passing rows are still processed.
Field-Level Validations
| Check | Rule | If It Fails |
|---|---|---|
| Email format | Must contain @ and a valid domain structure. | Row skipped: "Invalid email format." |
| Currency code | Must be a recognized ISO 4217 code (e.g., USD, EUR, JPY). | Row skipped: "Invalid currency." |
| Positive numbers | Quota must be > 0. | Row skipped: "Quota must be a positive number." |
| Non-negative numbers | Amount and base salary must be ≥ 0. No negative bookings. | Row skipped with field-specific error. |
| Date parsing | Dates must be parsable (ISO 8601, MM/DD/YYYY, etc.). | Row skipped: "Invalid date." |
| String sanitization | Names, titles, and reference numbers are stripped of control characters and truncated to safe lengths. | Silently sanitized. |
Row-Level Safeguards
| Check | What Happens |
|---|---|
| Duplicate reference numbers | If an activity with the same reference number already exists in your org, it's updated (upserted), not duplicated. |
| Unknown user email | For activity imports, the rep must already exist. If not, the row fails with "User not found." |
| Self-splits | You cannot split credit with the same user who owns the activity. |
| Commission rate ≤ 0 | In Commission Rate mode, the rate must be a positive number. |
Currency Mismatch Handling
If an imported activity's currency doesn't match the rep's quota currency, Earnest accepts the activity but logs an informational warning. The conversion is handled automatically at commission calculation time using the exchange rate for the booking date. No manual intervention is needed.
How Credit Splits Work
Credit splits let two reps share credit for a single activity. Here's how they work:
- The primary rep (the
userEmailon the row) always receives 100% credit for the activity. - The split rep (the
splitEmail) receives their own independent credit at the specifiedsplitPercent.
Important: Credit splits in Earnest are independent, not zero-sum. If Rep A gets 100% credit and Rep B gets 50% credit, that means the activity counts at full value toward Rep A's quota and at half value toward Rep B's quota. This is intentional — it reflects the common real-world practice of overlay credits, SE credits, and management roll-ups where total credited amounts may exceed 100%.
Split Rules
- The
splitPercentmust be greater than 0. - You cannot split credit with the same email as the primary rep.
- The split rep must already exist in your organization.
- Importing a new split on the same activity replaces any existing split configuration for that activity.
Tips for Clean Imports
- Use ISO 8601 dates (e.g.,
2026-04-15) for maximum compatibility. - Reference numbers must be unique within your organization — use a consistent naming scheme like
ORD-001,MTG-042, etc. - Import compensation data first, then activities. Activity imports require users to already exist.
- Preview before importing — the import UI shows you a column mapping step where you can verify which CSV columns map to which Earnest fields.
- Check the results summary — after import, Earnest shows you exactly how many rows succeeded and which ones failed, with per-row error messages.
API Ingestion
For teams that want to automate data loading from their data warehouse or CRM, Earnest offers a REST API endpoint for bulk ingestion.
How It Works
Your admin generates an API key from Admin → Settings. You then send a POST request to /api/ingest/bigquery with your compensation and activity data as JSON. The API accepts the same fields as CSV imports, with the same validation rules.
Key Features
- Upsert behavior — existing users are updated (not duplicated) based on email address. Activities are upserted by reference number.
- Auto-provisioning — new users, teams, and titles are created automatically if they don't already exist.
- Rate limiting — the API enforces request limits to prevent accidental overload.
- Domain enforcement — the API only accepts user emails that match your organization's verified domain(s).
When to Use the API vs. CSV
| Scenario | Recommendation |
|---|---|
| One-time roster setup | CSV import |
| Monthly deal sync from warehouse | API ingestion |
| Ad-hoc corrections or additions | CSV import |
| Automated pipeline (CI/CD, scheduled jobs) | API ingestion |
Frequently Asked Questions
Q: What happens if I import the same activity twice?
Earnest uses the referenceNumber as a unique key within your organization. Re-importing an activity with the same reference number updates the existing record (amount, date, currency, etc.) instead of creating a duplicate.
Q: Can I import activities with dates in the future? Yes, but be aware that the activity will only count toward the payout period that encompasses its booking date. A deal booked on April 15 only affects the April commission calculation.
Q: What's the maximum file size? There's no hard file-size limit, but imports are processed in the browser and sent to the server as JSON. For very large imports (10,000+ rows), consider splitting into multiple files.
Q: Can I delete imported data? Individual activities can be deleted from the admin panel. For compensation data, you can update positions by re-importing with new values — the old position is automatically closed and a new one is created.
Q: Does importing compensation data overwrite existing users? It uses an upsert strategy: if a user with that email already exists in your org, their name and currency are updated. A new position is created only if the compensation details (quota, OTE, base salary, team, or role) have changed.