Imports
Bring benefits you granted before AbuseZero into your claim history, as JSON or as a validated CSV upload.
Imports load the benefits you granted before AbuseZero into your workspace's claim history, so protection starts with your full history on day one. Every imported outcome becomes history that later evaluations are checked against.
There are two ways in:
- JSON: send up to 1,000 granted outcomes in one request. They're queued and applied in the background, with no review step.
- CSV: upload up to 100,000 rows. AbuseZero validates every row and holds the import for your review. Nothing is applied until you confirm it.
For a walkthrough, see Import outcome history.
Examples on this page use a server client:
import { AbuseZero } from "abusezero/server";
const abusezero = new AbuseZero({ key: process.env.ABUSEZERO_SECRET_KEY!, outage: "throw" });The import object
{
"object": "import",
"id": "imp_2b8e4d6f-1a3c-4f5e-9b7d-8c0a2e4f6b13",
"state": "preview",
"failure": null,
"total": 1240,
"validated": 1240,
"processed": 0,
"imported": 0,
"skipped": 0,
"rejected": 2,
"created_at": "2026-10-29T11:20:05.000Z",
"completed_at": null,
"errors": [
{ "row": 18, "error": "Check this email address." },
{ "row": 907, "error": "Use the time the benefit was actually granted, not a future time." }
]
}| Field | Type | Description |
|---|---|---|
object | "import" | The object type. |
id | string | The import ID, starting with imp_. |
state | string | Where the import is. See Import states. |
failure | object or null | Why the import failed, with a code and a message. Present when state is failed, otherwise null. See Failure reasons. |
total | integer | Rows or outcomes in the import. For a CSV, this is known once validation finishes. |
validated | integer | CSV rows checked so far. |
processed | integer | Rows finished so far: imported, skipped and rejected together. |
imported | integer | Rows added to your claim history. |
skipped | integer | Rows that matched history your workspace already had. |
rejected | integer | Rows that couldn't be imported. |
created_at | string | When the import was created. |
completed_at | string or null | When the import finished, failed or was cancelled. |
errors | array | Up to the first 100 row problems, each with row (the row number) and error (what to fix). Returned when you retrieve or validate one import, not in lists. The error report has every row. |
Import states
| State | Meaning |
|---|---|
uploading | The CSV import is waiting for its file and the validate call. |
validating | AbuseZero is checking every row. |
preview | Validation finished. Review the counts and errors, then confirm or cancel. |
queued | The import was accepted and is waiting to be applied. |
processing | Rows are being applied. |
complete | Every row was applied. The counts are final. |
failed | The import couldn't finish. failure says why. Fix the file, then upload it to a new import. |
cancelled | The import was cancelled. Its uploaded file and unfinished rows were deleted. |
Failure reasons
failure.code | failure.message |
|---|---|
file_not_uploaded | The CSV file wasn't uploaded. Create a new import and upload the file again. |
file_too_large | The CSV is over the 25 MiB limit. Split it into smaller files and import each one. |
row_limit | The CSV needs 1 to 100,000 rows. Check the file, then upload it to a new import. |
csv_columns | Use unique CSV columns and include email, benefit_key, outcome, and occurred_at. |
csv_unreadable | The CSV could not be parsed. Check quoting, columns, and row size, then upload a new file. |
file_changed | The uploaded file changed during validation. Create a new import. |
import_failed | AbuseZero couldn't finish this import. Create a new import and upload the file again. |
New codes may be added within v1. Treat a code you don't recognize like import_failed.
Import outcomes as JSON
Adds up to 1,000 granted outcomes in one request. The import is queued and applied in the background. Retrieve it to follow its progress.
| Method | Path | Scope | Idempotency-Key |
|---|---|---|---|
POST | /v1/imports | imports:write | Required |
Also available as abusezero imports create in the AbuseZero CLI, abusezero_create_outcome_import on the AbuseZero MCP server and importOutcomes() in the AbuseZero Node.js SDK.
Request body
The body is at most 1 MiB.
| Field | Type | Required | Limits | Description |
|---|---|---|---|---|
outcomes | array of objects | Yes | 1 to 1,000 | The outcomes to import. |
outcomes[].outcome | "granted" | Yes | The benefit was granted. | |
outcomes[].benefit | string | Yes | A benefit key in this workspace | The benefit that was granted, such as pro_trial. Create the benefit first. |
outcomes[].subject.email | string | Yes | Up to 254 characters | The user's email address. |
outcomes[].subject.external_id | string | No | 1 to 128 characters | Your ID for the user. Send it whenever you have it. |
outcomes[].subject.email_verified | boolean | No | Whether your app verified the email address. | |
outcomes[].occurred_at | string | Yes | ISO 8601 with a timezone, not in the future | When the benefit was granted. |
outcomes[].external_outcome_id | string | No | 1 to 128 printable characters, no spaces | Your ID for this grant. Importing the same ID again doesn't create a second record. |
outcomes[].claim_id | string | No | 1 to 128 printable characters, no spaces | The claim_id you used for this grant in an evaluation, if there was one. |
Example
const job = await abusezero.importOutcomes(
[
{
outcome: "granted",
benefit: "pro_trial",
subject: { external_id: "user_4821", email: "jordan@example.com", email_verified: true },
occurred_at: "2026-03-14T09:30:00Z",
external_outcome_id: "grant_10293",
},
],
"history-batch-0001",
);Response
Returns 202.
{
"object": "import",
"id": "imp_5f1c7a3e-9d2b-4c6a-8e4f-1b3d5a7c9e20",
"state": "queued"
}Rows that can't be applied, for example because their benefit doesn't exist, are counted in rejected with a message in errors. The rest of the import still goes through.
Errors
| Code | Status | Meaning |
|---|---|---|
INVALID_FIELD | 422 | A field is missing or outside its limits. field names it. |
UNSUPPORTED_FIELD | 422 | The body has a field the import doesn't define. field names it. |
PAYLOAD_TOO_LARGE | 413 | The body is over 1 MiB. Split the outcomes into smaller requests. |
DELETION_IN_PROGRESS | 409 | A data deletion is running in this workspace. Wait for it to finish, then import. |
RATE_LIMITED | 429 | Too many import requests. Wait for the number of seconds in Retry-After. |
For every other code, see REST API errors.
Import a CSV file
A CSV import takes five steps:
- Create an upload to get a one-time upload URL.
- Upload the file to that URL.
- Validate the import. AbuseZero checks every row.
- Retrieve the import until it reaches
preview, then review its counts and errors. - Confirm the import to apply it, or cancel it.
File format
The first row names the columns. Each column may appear once, and no other columns are accepted.
| Column | Required | Description |
|---|---|---|
email | Yes | The user's email address. |
benefit_key | Yes | The benefit that was granted. Create the benefit first. |
outcome | Yes | Always granted. |
occurred_at | Yes | When the benefit was granted, as ISO 8601 with a timezone. |
external_user_id | No | Your ID for the user, 1 to 128 characters. |
external_outcome_id | No | Your ID for this grant, 1 to 128 printable characters with no spaces. |
email,external_user_id,benefit_key,outcome,occurred_at,external_outcome_id
jordan@example.com,user_4821,pro_trial,granted,2026-03-14T09:30:00Z,grant_10293
sam@example.com,user_5307,pro_trial,granted,2026-04-02T16:05:00+02:00,grant_10377A file holds 1 to 100,000 rows and is at most 25 MiB.
Create an upload
Creates a CSV import and returns the address to upload its file to.
| Method | Path | Scope | Idempotency-Key |
|---|---|---|---|
POST | /v1/imports/csv | imports:write | Required |
Also available as abusezero imports create-csv in the AbuseZero CLI, abusezero_create_csv_import on the AbuseZero MCP server and createCsvImport() in the AbuseZero Node.js SDK.
The request has no fields. Send an empty JSON object.
Example
const upload = await abusezero.createCsvImport("history-csv-2026-10-29") as {
id: string;
url: string;
upload_headers: Record<string, string>;
};Response
Returns 202.
{
"object": "import_upload",
"id": "imp_2b8e4d6f-1a3c-4f5e-9b7d-8c0a2e4f6b13",
"url": "https://api.abusezero.com/…",
"upload_headers": {
"Content-Type": "text/csv",
"If-None-Match": "*"
}
}| Field | Type | Description |
|---|---|---|
id | string | The new import's ID. |
url | string | Where to upload the file. It works for 5 minutes and accepts one upload. |
upload_headers | object | Headers to send with the upload, exactly as given. |
A retry with the same Idempotency-Key returns the same import with a fresh upload url, so a lost response never leaves an extra import behind.
Errors
| Code | Status | Meaning |
|---|---|---|
DELETION_IN_PROGRESS | 409 | A data deletion is running in this workspace. Wait for it to finish, then import. |
Upload the file
Send the file to url with a PUT request and every header in upload_headers. No API key is needed: the URL carries its own authorization. Send the whole file as one request body, as both samples do.
import { readFile } from "node:fs/promises";
const response = await fetch(upload.url, {
method: "PUT",
headers: upload.upload_headers,
body: await readFile("history.csv"),
});
if (!response.ok) throw new Error(`Upload failed with status ${response.status}`);A successful upload returns 204 with no body. A failed upload returns the standard error envelope:
| Code | Status | Meaning |
|---|---|---|
UPLOAD_HEADERS | 400 | The request didn't send the headers in upload_headers exactly. |
UPLOAD_EMPTY | 400 | The request had no file. |
IMPORT_SIZE | 413 | The file is larger than 25 MiB. Split it into smaller files. |
TRANSFER_EXPIRED | 403 | The 5 minutes have passed. Create a new upload. |
INVALID_TRANSFER | 403 | The URL was changed or isn't complete. Use it exactly as returned. |
UPLOAD_EXISTS | 409 | A file was already uploaded to this import. Create a new import to upload another file. |
Validate the import
Starts validation of the uploaded file. Call it once the upload succeeds.
| Method | Path | Scope | Idempotency-Key |
|---|---|---|---|
POST | /v1/imports/{id}/validate | imports:write | Required |
Also available as abusezero imports validate in the AbuseZero CLI, abusezero_validate_import on the AbuseZero MCP server and validateImport() in the AbuseZero Node.js SDK.
Path parameters
| Parameter | Type | Description |
|---|---|---|
id | string | The import ID, starting with imp_. |
Example
const job = await abusezero.validateImport(upload.id, "validate-imp-2b8e4d6f");Response
Returns 201 with the import object, usually in validating. Calling it again after validation has started returns the import as it is.
Retrieve the import until it reaches preview or failed. A file fails validation when its columns don't match the file format, it has no rows or more than 100,000, it's larger than 25 MiB, it can't be read as CSV, or it changed during validation.
Errors
| Code | Status | Meaning |
|---|---|---|
IMPORT_NOT_FOUND | 404 | No import with this ID exists in the workspace. |
IMPORT_NOT_UPLOADED | 409 | The CSV file hasn't been uploaded yet. The import stays in uploading; upload the file, then validate again. |
Retrieve an import
| Method | Path | Scope | Idempotency-Key |
|---|---|---|---|
GET | /v1/imports/{id} | imports:read | Not used |
Also available as abusezero imports get in the AbuseZero CLI, abusezero_get_import on the AbuseZero MCP server and getImport() in the AbuseZero Node.js SDK.
Path parameters
| Parameter | Type | Description |
|---|---|---|
id | string | The import ID, starting with imp_. |
Example
const job = await abusezero.getImport("imp_2b8e4d6f-1a3c-4f5e-9b7d-8c0a2e4f6b13");Response
Returns 200 with the import object, including up to the first 100 row errors.
Errors
| Code | Status | Meaning |
|---|---|---|
IMPORT_NOT_FOUND | 404 | No import with this ID exists in the workspace. |
Download the error report
Creates a CSV of every row problem in an import, with the columns row and error.
| Method | Path | Scope | Idempotency-Key |
|---|---|---|---|
GET | /v1/imports/{id}/errors | imports:read | Not used |
Also available as abusezero imports errors in the AbuseZero CLI, abusezero_get_import_errors on the AbuseZero MCP server and call() in the AbuseZero Node.js SDK.
Path parameters
| Parameter | Type | Description |
|---|---|---|
id | string | The import ID, starting with imp_. |
Example
const report = await abusezero.call("/v1/imports/imp_2b8e4d6f-1a3c-4f5e-9b7d-8c0a2e4f6b13/errors");Response
Returns 200. The link works for 5 minutes. Request the report again for a new link.
{
"object": "import_error_report",
"url": "https://api.abusezero.com/…"
}Errors
| Code | Status | Meaning |
|---|---|---|
IMPORT_NOT_FOUND | 404 | No import with this ID exists in the workspace. |
IMPORT_REPORT_EXPIRED | 410 | Error reports are available until 7 days after an import finishes. Upload the corrected CSV in a new import. |
Confirm the import
Applies an import that's in preview. Rows that were rejected during validation aren't applied.
| Method | Path | Scope | Idempotency-Key |
|---|---|---|---|
POST | /v1/imports/{id}/confirm | imports:write | Required |
Also available as abusezero imports confirm in the AbuseZero CLI, abusezero_confirm_import on the AbuseZero MCP server and confirmImport() in the AbuseZero Node.js SDK.
Path parameters
| Parameter | Type | Description |
|---|---|---|
id | string | The import ID, starting with imp_. |
Example
await abusezero.confirmImport("imp_2b8e4d6f-1a3c-4f5e-9b7d-8c0a2e4f6b13", "confirm-imp-2b8e4d6f");Response
Returns 201.
{
"object": "import",
"id": "imp_2b8e4d6f-1a3c-4f5e-9b7d-8c0a2e4f6b13",
"state": "queued"
}Errors
| Code | Status | Meaning |
|---|---|---|
IMPORT_STATE | 409 | The import isn't in preview. Validate it and review it before confirming. |
Cancel an import
Cancelling is permanent. AbuseZero stops the import and deletes its uploaded file and every row that hasn't been applied yet. Rows already imported stay in your claim history as outcomes.
| Method | Path | Scope | Idempotency-Key |
|---|---|---|---|
POST | /v1/imports/{id}/cancel | imports:write | Required |
Also available as abusezero imports cancel in the AbuseZero CLI, abusezero_cancel_import on the AbuseZero MCP server and cancelImport() in the AbuseZero Node.js SDK.
Path parameters
| Parameter | Type | Description |
|---|---|---|
id | string | The import ID, starting with imp_. |
Example
await abusezero.cancelImport("imp_2b8e4d6f-1a3c-4f5e-9b7d-8c0a2e4f6b13", "cancel-imp-2b8e4d6f");Response
Returns 201.
{
"object": "import",
"id": "imp_2b8e4d6f-1a3c-4f5e-9b7d-8c0a2e4f6b13",
"state": "cancelled"
}Errors
| Code | Status | Meaning |
|---|---|---|
IMPORT_NOT_FOUND | 404 | No import with this ID exists in the workspace. |
IMPORT_COMPLETE | 409 | The import already finished, so it can't be cancelled. To remove its history, delete the affected users' data. |
List imports
Returns imports newest first. List items don't include errors.
| Method | Path | Scope | Idempotency-Key |
|---|---|---|---|
GET | /v1/imports | imports:read | Not used |
Also available as abusezero imports list in the AbuseZero CLI, abusezero_list_imports on the AbuseZero MCP server and imports() in the AbuseZero Node.js SDK.
Query parameters
| Parameter | Type | Required | Limits | Description |
|---|---|---|---|---|
limit | integer | No | 1 to 100, default 50 | How many imports to return. |
cursor | string | No | The next_cursor from the previous page, unchanged. |
Example
const page = await abusezero.imports({ limit: 20 });Response
Returns 200.
{
"object": "list",
"data": [
{
"object": "import",
"id": "imp_2b8e4d6f-1a3c-4f5e-9b7d-8c0a2e4f6b13",
"state": "complete",
"total": 1240,
"validated": 1240,
"processed": 1240,
"imported": 1191,
"skipped": 47,
"rejected": 2,
"created_at": "2026-10-29T11:20:05.000Z",
"completed_at": "2026-10-29T11:26:48.000Z"
}
],
"next_cursor": null
}Related
How is this guide?
Last updated on