Korely

Platform

Data & governance

Where your agents' memory lives, who can touch it, and how to erase it.

EU data residency

Your stored memory lives on our own infrastructure in the EU. It is stored in the EU, never replicated to the US. EU data residency applies on every tier, including the free Hobby plan, and requires no configuration on your part.

Storage is in the EU, and so are the embeddings: an open embedding model (EmbeddingGemma) runs on our server in Helsinki and turns memories, facts and search queries into vectors there. The language model that reads a memory into facts depends on the project's region: the Gemini API (Google, United States, under the EU Standard Contractual Clauses and a DPA) in the default global region, gpt-oss-120b on Scaleway in Paris in the eu region. Either way it sees text: the memory you write, to extract its facts; the earlier facts a new one is compared with, for the contradiction check; and a new relation phrase, the first time your project uses it. A search or context query never reaches a language model. Neither provider trains its models on that text. The derived outputs, facts, embeddings and graph edges, are stored only in your account's namespace, in the EU. The full list is on the Security page.

EU residency applies to every namespace by construction, your facts, embeddings, and graph edges are written only to EU-hosted storage. A GET /v1/ping probe confirms the API is reachable and echoes your key's tier, scopes, storage region (region) and the region of its project (processing_region):

Terminal window
curl https://api.korely.ai/v1/ping \
-H "Authorization: Bearer kor_live_..."
# {"ok":true,"tier":"hobby","region":"eu-hel1","processing_region":"global","scopes":["memories:read","memories:write"]}

Read-only and write-only keys

A key reads and writes unless you choose otherwise. On the API Keys page of the dashboard, Access in Create API key gives a key only memories:read (search, context, lists and the audit log: a frontend or a support tool) or only memories:write (add, change and forget: an import pipeline that never reads the memory back). A call outside the key's scopes is refused and reads or writes nothing: 403 forbidden on REST, a forbidden: error from the MCP tool. A key keeps the scopes it was made with: for different ones, make a new key and revoke the old.

Regions

Each project has a region. You choose it on the Projects page of the dashboard, when you create a project or later. An account made with POST /v1/agents/init has no dashboard login: it picks the region of its Default project at signup, with "processing_region": "eu" in the body (Sign up as an agent), or later, once you connect its key to a login.

RegionModel that reads your memoriesWhere it runs
global (default)GeminiGoogle, United States
eugpt-oss-120bScaleway, Paris, France
  • Storage and embeddings are in the EU in both regions.
  • In the eu region no memory text reaches Google. When the EU model is unavailable, new memories are stored at once and their facts follow when it is back. They are never sent to the global region.
  • A change of region applies to the next writes, including memories still waiting for their facts. Facts already written stay as they are.
  • The region says where your memories are read, not where they are stored. GET /v1/ping reports both: region is the storage location, processing_region the region of the key's project.

Namespaces & scoping

Every call is scoped by your account and by the project of the key first, then by the namespace parameters you pass:

  • user_id, your end user. The unit of isolation; unlimited on every tier. Choose any opaque string (a database ID, a hashed email, a UUID) of 1 to 255 characters, without / or control characters and other than . or ..: the erase call addresses it as one URL path segment. Other values are refused with 422 invalid_request.
  • agent_id, one of your apps or agents. Plans cap the number of distinct agent IDs (Hobby: 2, Developer: 10, Team: 100, Scale: 500). Passing an unknown agent_id past your plan's limit returns 403 agent_cap_exceeded.
  • run_id, a single session or conversation turn. Useful for isolating memories that should not persist across runs.

The scope is enforced server-side on every query. One customer's memories can never surface in another's results, by construction, not by prompt discipline. The GET /v1/users endpoint lists every user_id held in the key's project, so you always know exactly which end-user data you hold.

Terminal window
# List every end user in the key's project, with memory and fact counts
curl https://api.korely.ai/v1/users \
-H "Authorization: Bearer kor_live_..."
# {"users": [
# {"user_id": "customer-4812", "memories": 18, "facts": 9, "last_active": "2026-06-15T..."},
# {"user_id": "customer-7091", "memories": 4, "facts": 2, "last_active": "2026-06-14T..."}
# ], "total": 2}

Erasure (GDPR Article 17)

Forget everything Korely holds about one end user in the key's project, every memory and every fact, in a single call (one call per project):

Terminal window
curl -X DELETE "https://api.korely.ai/v1/users/customer-4812/memories" \
-H "Authorization: Bearer kor_live_..."
# 200 OK
# {"user_id":"customer-4812","memories_forgotten":18,"facts_invalidated":9,
# "memories_deleted":18,"facts_deleted":9,"erasure":"permanent","audit_id":"aud_..."}

The call returns a receipt, memories_deleted, facts_deleted, erasure: "permanent" and an audit_id (memories_forgotten and facts_invalidated are deprecated aliases with the same numbers), so the erasure is itself provable. Erasure is not the bi-temporal supersede: every memory and every typed fact about the user, superseded ones included, is physically deleted, so no query surface (REST, SDK, CLI, or MCP) and no include_invalidated flag can bring it back. What stays is the audit record, with the counts and the user_id but none of the erased content, for your own Article 5(2) accountability. The endpoint is idempotent: erasing an unknown or already-erased user returns 200 with zero counts, never an error.

To forget a single memory (rather than an entire user), use DELETE /v1/memories/{id}, or korely.delete(id) in the SDK. It returns {"id":"mem_...","status":"forgotten","facts_invalidated":N,"audit_id":"aud_..."}. A repeat delete of the same memory, or a delete of an id that was never created, returns 404 not_found. This is not an erasure: the memory leaves every read, but its row stays and its facts stay as history (include_invalidated still shows them). When a person asks for their data to be gone, use the end-user erasure above, which deletes the rows.

If you track which user_id values map to real people in your own system, a one-call erasure is the complete Korely side of an Article 17 workflow, no support ticket, no waiting period.

API keys

Requests authenticate with a kor_live_ key in the Authorization: Bearer header. Korely stores only a SHA-256 hash of the key, the plaintext is shown exactly once, at creation, and cannot be retrieved afterward. If a key is lost or suspected compromised, revoke it in the dashboard and create a new one; the old key stops working instantly across every surface (REST, SDK, CLI, MCP).

An invalid or missing key returns 401 invalid_key. Every Korely error uses the same flat envelope, {"code": "<slug>", "message": "<text>"} , so one catch block handles all of them:

{
"code": "invalid_key",
"message": "Invalid or missing API key: no live key matches it (a revoked key stops at once); your keys are at https://agent.korely.ai/keys"
}

Best practice: store your key in an environment variable (KORELY_API_KEY) rather than hard-coding it. The SDK reads this variable automatically. Never commit a kor_live_ key to source control.

No training on your data

Your stored memories and facts are never used to train or fine-tune any model, Korely's own or any third party's. Embeddings are computed on our own server. The rest of the AI inference on the write path (entity extraction, relation typing) runs through DPA-covered sub-processors in data-processor mode, where they are contractually prohibited from using your data for their own model training. The processed outputs belong exclusively to your account namespace.

There is no opt-out toggle because there is no opt-in: training on customer data is not part of the data pipeline, by design.

Quota & rate limits

Plans carry monthly write and query quotas (see pricing), each with a 10% grace on top. A write that would take you past the plan's figure (with the month's top-ups) plus the grace returns 429 quota_exceeded and writes nothing; a read past the queries quota gets the same answer. There is no surprise overage charge: on a paid plan you can top up the month from the dashboard's Usage page, or upgrade at any time. The quota 429 carries no Retry-After header; its body says the month's figure, top-ups included (limit), what the month has used (used) and when the count starts again (resets_at, 00:00 UTC on the 1st):

{
"code": "quota_exceeded",
"message": "Monthly write limit reached: 250 writes on the Hobby plan this month, plus a 10% grace. It starts again on 2026-11-01. To go on now, move to a paid plan at https://agent.korely.ai/usage (an account made with korely init connects its key to a login there first: https://korely.ai/docs/agent-signup/#connect)",
"limit": 250,
"used": 275,
"resets_at": "2026-11-01T00:00:00Z"
}

Short-term request bursts are handled separately. If you exceed the per-minute, per-hour or per-day rate limit, you get a 429 that does carry a Retry-After header, an integer number of seconds to wait before retrying.

For the full security posture, encryption in transit, database access, sub-processor list, and compliance documentation, see the security page.

Related