Korely

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. On the Hobby plan a batch can hold fewer items than that (see Notes).

POST /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, computing its embedding, extracting its facts (extraction) and checking them for contradictions, exactly as a single add would. Poll GET /v1/batch/{job_id} for progress.

Authentication

HTTP header, required: Authorization: Bearer kor_live_..., your API key. The key must carry the memories:write scope.

Request body

FieldTypeRequiredDescription
memoriesarray<object>RequiredThe memory objects to import. 1-500 items. Each element is a BatchMemory (see fields below).

Each BatchMemory element has these fields:

FieldTypeRequiredDescription
contentstringRequiredThe memory text. 1-16,000 characters.
user_idstring · nullOne of user_id and agent_idThe end-user namespace this memory belongs to. Every memory of the batch needs a user_id or an agent_id: a memory with neither refuses the whole batch with 422 identity_required, naming the item (why). 1 to 255 characters, no /, no control characters, not . or .., not starting with erased:; an empty string is refused, omit the field when you send an agent_id.
agent_idstring · nullOne of user_id and agent_idYour application's namespace (glossary): the agent writing. Same rule as user_id.
run_idstring · nullOptionalOne session or conversation (glossary). Default null. 1 to 255 characters; an empty string is refused, omit the field instead.
metadataobject · nullOptionalArbitrary JSON stored alongside the memory and echoed back on reads (glossary). At most 8,192 bytes of UTF-8, counted on the JSON as the server writes it, spaces included, 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.
timestampstring · nullOptionalISO 8601 date or datetime when the events happened. The facts extracted from the item take it as valid_from (valid time), so a migration keeps its real dates (a timestamp later than now is read as now: for a fact that begins later, write it with POST /v1/facts and its own valid_from); 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.
kindstringOptionalSet per item (glossary): note (the default), what somebody said or decided, or session, the log of a work session. Reads with exclude_sessions leave the sessions out, as for Add a memory. The SDKs send it on a batch item from Python 0.1.27 and Node 0.1.17 (see Work sessions in a batch).

Example request

Terminal window
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" }
}
]
}'

Work sessions in a batch

from korely_memory import Korely
korely = Korely() # reads KORELY_API_KEY
job = korely.batch([
{"content": "Session: added retries to the invoice sender.", "user_id": "dev-team", "kind": "session"},
{"content": "Invoices go out on the first working day.", "user_id": "dev-team"},
])
print(job.received) # 2

An item without kind is a note. Each item is imported with its own kind, so a migration can bring session logs and decisions in one batch. Python 0.1.27 and Node 0.1.17 or later.

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": "pending",
"received": 3
}
FieldTypeDescription
idstringOpaque batch job id, prefix job_ followed by the UUID hex, e.g. job_8f2c1aab4d7e4f0c9a1b2c3d4e5f6a7b. Use it to poll GET /v1/batch/{job_id}.
statusstringThe job's stored status: pending when the POST answers (queued, no worker on it yet), then processing, completed or failed, as GET /v1/batch/{job_id} reports.
receivedintegerNumber of memory objects accepted into the job, equals the length of the memories array you sent.

Errors

StatusCodeCause
401invalid_keyMissing or invalid API key. No Bearer credential was supplied, or the kor_live_ key does not resolve to a live key.
403forbiddenThe API key lacks the memories:write scope, or it is confined to some end users and a user_id is outside its list (or no user_id is given and the key does not name exactly one end user to default to). One such row refuses the whole batch.
408request_timeoutThe body did not arrive whole within 30 seconds, or within 120 seconds when it is larger than 2 MiB. Nothing is stored; send it again, or in smaller batches. An account sends one body larger than 2 MiB at a time: a second one waits for the first.
413payload_too_largeThe body is larger than 78,245,152 bytes (about 75 MiB), the most this route takes from a key that may write; from any other caller the limit is 2,097,152 bytes. Refused before it is read; nothing is stored. A body that holds more data than 500 valid items could (far more objects, strings or characters) is refused with 413 too; a valid batch never is.
400bad_requestThe body is not well formed, for example JSON that is not UTF-8. Nothing is stored.
422identity_requiredA memory of the batch names neither user_id nor agent_id (or only ids of spaces, invisible characters or combining marks). The message names the item, such as memories[3].user_id or memories[3].agent_id is required. The whole batch is refused and nothing is queued.
422invalid_requestRequest 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 is empty or exceeds 255 chars, a timestamp cannot be read, a kind is other than note or session, or a field is unknown (e.g. userId). One bad item refuses the whole batch.
429rate_limit_exceededPer-minute, per-hour or per-day request limit. Comes back with a Retry-After header.
429quota_exceededThe batch's writes do not fit in this month's writes left, with a 10% allowance on top of the plan's number (batches already queued count as used). Nothing is stored. No Retry-After. On Hobby this happens to any batch that needs more than 275 writes, even in a fresh month.
429agent_cap_exceededThe batch's new agent_id names would take the account past its plan's named agents. Nothing is stored. detail carries limit and used.
500internal_errorThe server failed on this request; the message holds no detail.
503unavailableThe database did not answer, or the request waited more than 5 seconds for another change to the same records, or the server is reading as many large bodies as it can at once. Nothing is stored; retry after the Retry-After header.

Notes

  • It's asynchronous. The POST only enqueues the job and returns immediately (202). A background worker imports each item later through the same steps as POST /v1/memories: the memory is stored, then its facts are extracted.
  • 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.
  • Counted once, at POST time. The batch's writes and its new agent names are counted when the job is queued; a batch that does not fit the writes left this month, or would pass the agent cap, is refused whole (429) and nothing is stored. The worker does not count the items again.
  • Paused, not lost. When Korely has stopped calling language models for the day to cap its spending, the import still stores the memories and the facts are extracted after the pause.
  • Webhooks. The worker emits a memory.created webhook for each successful import. A quota.warning webhook fires when the POST's admission crosses 80% of the month's writes.
  • Crash-safe and idempotent. The worker claims jobs atomically (SELECT ... FOR UPDATE SKIP LOCKED), uses bounded attempts with exponential backoff, and resumes from the imported + failed offset after a crash, so a restart never double-imports.
  • Hard ceiling. A single batch accepts at most 500 items.
  • Hobby fits fewer. A batch is counted whole against the month's writes. Hobby has 250 writes a month, 275 with the 10% allowance, so a batch that needs more than 275 writes is always refused there, and 500 items never fit. From Developer upward a 500-item batch fits when the month has room. (Read from the code; not tried on a Hobby account.)

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.