Batch & status
Batch import
Bulk-import up to 500 memories in one async call. Korely enqueues the job and returns a job id immediately; each item is imported later through the same pipeline as a single add.
/v1/batch
Use this when you're backfilling history or seeding a namespace and don't
want to fire hundreds of individual POST /v1/memories calls. The
request returns 202 Accepted with a job id; a background worker
imports each item, running embeddings, typed-fact extraction, and
contradiction checking, exactly as a single add would. Poll
GET /v1/batch/{job_id} for progress.
Authentication
HTTP header, required: Authorization: Bearer kor_live_.... The key must carry the memories:write scope.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
memories | array<object> | Required | The memory objects to import. 1-500 items. Each element is a BatchMemory (see fields below). |
Each BatchMemory element has these fields:
| Field | Type | Required | Description |
|---|---|---|---|
content | string | Required | The memory text. 1-16,000 characters. |
user_id | string · null | Optional | The end-user namespace this memory belongs to. Default null. 1 to 255 characters, no /, no control characters, not . or ..; an empty string is refused, omit the field for the shared namespace. |
agent_id | string · null | Optional | Your application's namespace. Default null. Same rule as user_id. |
run_id | string · null | Optional | One session or conversation. Default null. Max 255 chars. |
metadata | object · null | Optional | Arbitrary JSON stored alongside the memory and echoed back on reads. At most 8,192 bytes once serialized, with finite numbers only. An item over the bound, or holding NaN or Infinity, refuses the whole batch with 422 before anything is queued. Default null. |
timestamp | string · null | Optional | ISO 8601 date or datetime when the events happened. The facts extracted from the item take it as valid_from, so a migration keeps its real dates; without it the item is dated when it is imported. A value that is not ISO 8601 refuses the whole batch with 422, naming the item (e.g. memories[3].timestamp). Default null. |
Example request
curl -X POST https://api.korely.ai/v1/batch \ -H "Authorization: Bearer kor_live_..." \ -H "Content-Type: application/json" \ -d '{ "memories": [ { "content": "User prefers email follow-ups, not phone", "user_id": "customer-giulia-4812", "agent_id": "support-bot" }, { "content": "Account is on the EU data region", "user_id": "customer-giulia-4812", "agent_id": "support-bot" }, { "content": "Renewal date is 2026-09-01", "user_id": "customer-giulia-4812", "agent_id": "support-bot", "metadata": { "source": "crm" } } ] }'Response
202 Accepted. The job has been enqueued; nothing is imported
yet. Poll GET /v1/batch/{job_id} to track progress.
{ "id": "job_8f2c1aab4d7e4f0c9a1b2c3d4e5f6a7b", "status": "processing", "received": 3}| Field | Type | Description |
|---|---|---|
id | string | Opaque batch job id, prefix job_ followed by the UUID hex, e.g. job_8f2c1aab4d7e4f0c9a1b2c3d4e5f6a7b. Use it to poll GET /v1/batch/{job_id}. |
status | string | Always processing in the POST response. (The persisted job starts in pending and advances as the worker drains it.) |
received | integer | Number of memory objects accepted into the job, equals the length of the memories array you sent. |
Errors
| Status | Code | Cause |
|---|---|---|
401 | invalid_key | Missing or invalid API key. No Bearer credential was supplied, or the kor_live_ key does not resolve to a live key. |
403 | forbidden | The API key lacks the memories:write scope. |
422 | invalid_request | Request validation failed: memories is empty or has more than 500 items, a content is empty or over 16,000 chars, a user_id or agent_id breaks the id rule, a run_id exceeds 255 chars, a timestamp cannot be read, or a field is unknown (e.g. userId). One bad item refuses the whole batch. |
429 | rate_limit_exceeded | Per-minute, per-hour or per-day request limit. Comes back with a Retry-After header. |
429 | quota_exceeded | The batch's memories do not fit in this month's writes left (batches already queued count as used). Nothing is stored. No Retry-After. |
429 | too_many_batches | Three of your batches are still queued or importing. Nothing is stored; send it again when one finishes. |
Notes
- It's asynchronous. The POST only enqueues the job and returns immediately (
202). A background worker imports each item later through the same pipeline asPOST /v1/memories,engine.addplus best-effort typed-fact extraction. - Each item counts its writes. One write per 6,000 characters of its text, as an add: an item up to 6,000 characters is one write. Every imported item is checked against the per-agent namespace cap first, then your monthly write quota, so a capped item never burns a quota count.
- Quota is checked twice. At POST time, a batch that does not fit in the writes left this month (queued batches included) gets
429 quota_exceededand nothing is stored. During the import each item is checked again; if other writes used the room meanwhile, the remaining items fail one by one (no silent overage) and are listed in the job'serrors. - Paused, not lost. While the service's daily model budget is spent (the
503 writes_pausedcondition of the other writes), the job waits inpendinguntil 00:00 UTC; the POST still answers202. - Webhooks fire per item. The worker emits a
memory.createdwebhook for each successful import, and a one-timequota.warningwebhook at exactly 80% usage. - Crash-safe and idempotent. The worker claims jobs atomically (
SELECT ... FOR UPDATE SKIP LOCKED), uses bounded attempts with exponential backoff, and resumes from theimported + failedoffset after a crash, so a restart never double-imports. - Hard ceiling. A single batch accepts at most 500 items.
Related
- Batch status, poll
GET /v1/batch/{job_id}for progress and per-item errors. - Add a memory, the single-item write each batch item runs through.
- SDK, the typed client surface.