---
title: "Bulk create records"
method: POST
path: "/records/bulk"
---

# Bulk create records

`POST /records/bulk`

Submit up to 1,000 records for asynchronous processing. Returns immediately with a bulk request ID. Each record is validated and inserted independently, so some records can succeed while others fail. Use the `GET /bulk_requests/{id}` endpoint to poll for results.

## Headers

- `Idempotency-Key` string

## Request body

- object
  - `on_conflict` 'skip' | 'error' — How to handle records whose `external_id` already exists in the target stream. `skip` (default) silently skips duplicates and counts them in `skipped_items`. `error` surfaces duplicates as per-row failures. Only applies to records with `external_id` set.
  - `records` object[], required
    - `date` string, date, required — The date of the record
    - `data_stream_key` string, required — Key of the data stream this record belongs to
    - `amount` integer, required — The monetary amount in cents (e.g., 1234 = $12.34)
    - `currency` string — Three-letter ISO 4217 currency code (e.g. USD, EUR). **Optional — defaults to `USD` when omitted.** Case-insensitive on input.
    - `direction` 'credit' | 'debit', required — Whether this is a credit or debit
    - `description` string — Optional description
    - `external_id` string — Optional external identifier
    - `metadata` object — Arbitrary key-value metadata

## Response `202`

Bulk request accepted for processing

- BulkRequestSummary — Summary returned when a bulk request is created or retrieved via idempotency key
  - `id` integer — Unique identifier for the bulk request
  - `status` 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled' — Current processing status
  - `total_items` integer — Number of records submitted
  - `processed_items` integer — Number of records processed so far
  - `successful_items` integer — Number of records successfully created
  - `failed_items` integer — Number of records that failed
  - `skipped_items` integer — Number of records skipped due to `on_conflict: skip` matching an existing `external_id`
  - `url` string — Polling URL to check progress (e.g. /v1/bulk_requests/123)

## Other responses

- `400` — Invalid `on_conflict` value
- `401` — Unauthorized
- `422` — Invalid request (e.g. records is not an array, empty, or exceeds 1,000 items)

---

[API](https://skmtc.net/endclose/apis/end-close-api.md) · [All operations](https://skmtc.net/endclose/apis/end-close-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/endclose/end-close-api/revisions/613ceff68dc7/schema)
