Korely

Context & facts

Write a fact

Write a typed (subject, predicate, object) triple directly. Korely skips LLM extraction but still runs the contradiction check when the install has a model key, so a conflicting older fact is superseded, not duplicated.

POST /v1/facts

SDK: korely.add_fact_triple(subject, predicate, object, ...). Use this when you already have a structured fact and want to skip extraction (model_used is recorded as direct). With a model key configured, the two-stage contradiction check still runs, which is why the response carries invalidated, the ids of any earlier facts this write closed. Only a write answers invalidated; a later read shows the same link as invalidated_by on the closed fact.

Authentication

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

Request body

FieldTypeRequiredDescription
subjectstringRequiredSubject side of the triple. 1-200 characters; stripped before write.
predicatestringRequiredRaw predicate; canonicalized server-side. 1-200 characters; stripped before write.
objectstringRequiredObject side of the triple. 1-500 characters; stripped before write.
user_idstringOptionalThe end user this fact is about. 1 to 255 characters, no /, no control characters, not . or ..; omit it for the shared namespace. Pass it: with a user_id the check also compares the fact with facts written under another name of the same person, and without one it compares only facts with the same subject.
agent_idstringOptionalYour application's namespace (agent_id). Same rule as user_id. A new agent_id counts against your plan's agent cap on the Cloud.
run_idstringOptionalOne session or run. Max 255 chars.
subject_typestringOptionalSubject type label, e.g. self or person. Default unknown. Max 20 chars.
object_is_literalbooleanOptionalWhether the object is a literal value rather than an entity. Default false.
confidencenumberOptionalConfidence score between 0.0 and 1.0. Default 0.9.
valid_fromstringOptionalISO 8601 date or datetime the fact becomes valid. Sets the valid time start, valid_from; use it when backfilling so the timeline stays correct. When omitted, the fact is valid from the moment of the write.
tensestringOptionalcurrent, past or planned. Default current. A past fact closes the open fact it restates; with no open twin it is stored as history, already closed. You say the tense; Korely does not infer it from the verb. A past fact with no valid_from begins and ends when it was written, an empty interval, so no as_of read returns it as true. With a valid_from it is true from that date until it was told (both since 1.0.0).

Any other field is refused with 422 invalid_request: field names are snake_case.

Example request

Terminal window
curl -X POST https://api.korely.ai/v1/facts \
-H "Authorization: Bearer kor_live_..." \
-H "Content-Type: application/json" \
-d '{
"subject": "customer-giulia-4812",
"predicate": "prefers",
"object": "dark mode",
"user_id": "customer-giulia-4812",
"agent_id": "support-bot",
"subject_type": "person",
"object_is_literal": true,
"confidence": 0.95
}'

Response

201 Created. The newly written fact, plus the ids of any earlier facts it superseded.

{
"id": "fct_a1",
"subject": "customer-giulia-4812",
"predicate": "prefers",
"object": "dark mode",
"predicate_family": "preferences",
"valid_from": "2026-06-17T00:00:00+00:00",
"invalidated": ["fct_a0"],
"tense": "current",
"invalid_at": null,
"observation_count": 1,
"user_id": "customer-giulia-4812",
"agent_id": "support-bot"
}
FieldTypeDescription
idstringPublic id of the newly written fact, e.g. fct_a1 (fct_ prefix).
subjectstringStored subject label.
predicatestringThe predicate as stored: your phrase in snake_case form (prefers, works_at). A direct write keeps the name you chose, or the relation that name was already learned as in this vocabulary; the model never merges it into another relation.
objectstringStored object label.
predicate_familystring · nullPredicate family derived from the canonical predicate.
valid_fromstring · nullISO 8601 timestamp the fact is valid from, or null.
invalidatedarray<string>Public ids of pre-existing facts this write superseded via the contradiction check. Empty list if none.
invalid_atstring · nullISO 8601 timestamp the fact stopped being true. Set when you write a past fact; null otherwise.
tensestringThe tense the fact was stored with: current, past or planned.
observation_countintegerHow many times this fact has been stated. Writing a triple that is already open confirms it and raises the count instead of adding a duplicate.
user_idstring or nullThe end user the fact belongs to, as in GET /v1/facts.
agent_idstring or nullThe agent namespace it was written under, as in GET /v1/facts.

Contradictions resolve on write. When the install has a model key, as the Cloud does, a current fact goes through the two-stage contradiction check even on a direct write: open facts of the same subject that could conflict are gathered, and a model judges whether the new fact ends them, reading whether the relation holds one value at a time. A fact it ends is closed, not deleted, and its id appears in invalidated. Superseded facts stay queryable via as_of. A Self-hosted install with no model key runs no judge and closes nothing this way; a past fact still closes the open fact it restates. The judge can be wrong: it can miss a contradiction or close the wrong fact. Read the facts back, and close one yourself with Close a fact.

Errors

StatusCodeCause
401invalid_keyMissing or invalid kor_live_ key in the Authorization header.
403forbiddenThe API key lacks the memories:write scope.
413payload_too_largeThe body is larger than 2,097,152 bytes. Refused before it is read; nothing is written or counted. On the Cloud a body over 1 MB gets a bare 413 first, without this code.
429agent_cap_exceededWriting with a new agent_id would exceed your plan's named agents (the body adds limit and used). Reuse an existing agent_id, delete one with DELETE /v1/agents/{agent_id}, or upgrade. Checked before the write quota, so a capped write doesn't burn a quota count.
429rate_limit_exceededPer-plan rate limit exceeded. Retry after the Retry-After header.
429quota_exceededThe month's writes are used up, 10% grace included; nothing is written. The body adds limit, used and resets_at (00:00 UTC on the 1st) to code and message. Top up the month on a paid plan, or upgrade.
422invalid_requestBody validation failed: subject/predicate/object missing or over their length limits, confidence outside [0, 1], a non-ISO valid_from, a tense other than the three values, a user_id or agent_id breaking the id rule, or an unknown field.
503model_unavailableThe model that resolves the relation against your learned vocabulary, or the contradiction check, gave no usable answer. Nothing was written; retry the request.
503service_pausedThe service itself does not call the models now (for example a spending limit, or a problem at the model provider). Carries Retry-After: 3600: a retry within the hour cannot succeed. Nothing was written.
503unavailableThe key could not be checked because the database did not answer, or the request waited past one of the store's deadlines. Nothing was written; retry after the seconds in Retry-After.
408request_timeoutThe body did not arrive whole within 30 seconds. Nothing was written; send it again.

Notes

  • Skips extraction, not the check. A direct write bypasses LLM extraction (model_used is direct) but still runs the two-stage contradiction check when the install has a model key, that's why the response carries invalidated.
  • Idempotent by triple. If an active fact already exists with the same subject, canonical predicate, and object for the same end user and agent_id, Korely reconfirms it instead of writing a duplicate: the answer is still 201, with the same id, invalidated: [] and observation_count one higher.
  • It counts as one write. A fact written directly uses one write of your monthly quota, because a model reads it. A call that writes nothing, such as a 503, uses none.
  • Values are stripped. subject, predicate, and object are trimmed before write.
  • Agent cap before quota. When you pass a new agent_id, the agent-cap check runs before the write-quota check, so a cap failure never consumes a quota slot.
  • Webhooks and audit. The write commits, sends a fact.invalidated webhook for each fact it closed, and is logged as a facts:create request.

Related