itsuki (Python SDK)
Python client for the Itsuki memory API. It is typed, synchronous, and supports Python 3.9 and newer.
pip install itsuki
import os
from itsuki import MemoryClient
memory = MemoryClient(api_key=os.environ["ITSUKI_API_KEY"])
receipt = memory.add(
"I started learning Kotlin this week.",
idempotency_key=MemoryClient.new_idempotency_key(),
)
print(memory.search("what am I learning?")["context"])
Keep API keys on the server. Itsuki sends them as Bearer credentials. Legacy
uml_live_... keys remain accepted by the API, while new keys use
itsuki_live_....
Custom base_url values must be bare HTTPS origins without credentials, a
path, query, or fragment. Plain HTTP is accepted only for loopback development
(localhost, 127.0.0.0/8, or ::1), so a Bearer key cannot be sent to an
arbitrary cleartext host.
Core operations
| Python | API | Result |
|---|---|---|
add(content) |
POST /v1/save |
Durable-write receipt |
add_conversation(messages) |
POST /v1/save |
Durable-write receipt |
turn(messages) |
POST /v1/turn |
Recall plus optional auto-capture |
search(query) / recall(query) |
POST /v1/recall |
Prompt-ready context |
ingest(messages) |
POST /v1/ingest |
Bounded bulk-ingest receipt |
packet_status(id) |
GET /v1/packets/:id/status |
One job snapshot |
jobs() |
GET /v1/jobs |
Accepted-write ledger |
wait_for(id) |
packet status polling | Terminal job snapshot |
delete(id) |
DELETE /v1/memories/:id |
One-memory deletion |
delete_by_source(...) |
DELETE /v1/memories |
Dry-run or confirmed bulk deletion |
graph(), status(), receipts(), usage(), get_rules(), set_rules(),
and export_all() expose the remaining account endpoints.
usage(range=...) accepts exactly 1d, 7d, 30d, or all.
Conversation messages are oldest first:
from itsuki import Message
messages: list[Message] = [
{"role": "user", "content": "My preferred editor is Helix."},
{"role": "assistant", "content": "Noted."},
]
receipt = memory.add_conversation(
messages,
conversation_id="onboarding-42",
idempotency_key=MemoryClient.new_idempotency_key(),
)
turn = memory.turn(messages, thread_id="support-42")
add() and search() require non-empty strings, and
add_conversation() requires at least one message. turn([]) is valid only
when accompanied by a non-empty query; ingest([]) remains valid so a caller
can flush held context. Method-owned fields such as content, messages,
mode, and query cannot be replaced through extra options.
Writes are not automatically retried unless they contain an
idempotency_key. Reads are retried for transient transport, rate-limit, and
server failures according to max_retries, which defaults to 2 and accepts
values from 0 through 10. The client's timeout bounds the complete
request, including retry backoff and every attempt, and request/poll timers
cannot exceed 2_147_483.647 seconds.
Tenant and project scope
Your API key selects an account. user_id optionally selects one isolated
end-user memory inside that account:
memory = MemoryClient(api_key=os.environ["ITSUKI_API_KEY"], user_id="ada")
memory.add("My depot is in Porto.")
# A per-call value overrides the constructor default.
memory.search("where is my depot?", user_id="grace")
# Explicit None selects the API-key owner's memory for this call.
memory.status(user_id=None)
Per-call user_id works on writes, recall, graph/status reads, packet/job
status, exports, and deletes. Capture rules are account-wide: get_rules() and
set_rules() govern every end-user memory under the API key even when a client
or call supplies user_id. An empty or whitespace-padded value is
rejected locally so it cannot silently fall back to another memory space.
Projects are metadata within a memory space, not tenants:
from itsuki import MemoryScope
scope: MemoryScope = {"project_id": "atlas", "project_name": "Atlas"}
memory.add("Atlas deploys from main.", memory_scope=scope)
result = memory.search(
"How does this deploy?",
memory_scope=scope,
recall_scope="project_then_global",
)
The default recall scope is global. project_only searches exactly one
project. project_then_global searches that project plus account-global rows.
Both project-specific modes require memory_scope.project_id (camel-case
projectId is also accepted).
Background status and deletion
Every accepted write exposes a source_packet_id for its asynchronous work:
receipt = memory.ingest(messages, flush=True)
packet_id = receipt["source_packet_id"]
current = memory.packet_status(packet_id)
terminal = memory.wait_for(packet_id, timeout=60, interval=1.5)
failed = memory.jobs(status="failed", limit=20)
Job status filters accept queued, staged, processing, enriched,
failed, or completed; since and limit must be positive finite values,
and limit must be an integer.
wait_for() performs one immediate check, then polls until enriched,
failed, or compatibility status completed. If its polling deadline expires,
it returns the last snapshot with timed_out=True; a polling timeout is not
reported as a processing failure.
Bulk deletion is a dry run unless confirm=True is explicit:
preview = memory.delete_by_source(source="ingest", after=started_at)
deleted = memory.delete_by_source(
source="ingest",
after=started_at,
confirm=True,
)
When supplied, source must be a non-empty identifier and before/after
must be finite numbers greater than or equal to 1.
Scoped delete vs erasure
The two shapes behave differently on purpose:
- Scoped (a
sourceand/or a time window) is curation. Saves already submitted and still processing are not cancelled — they finish, and their output lands after the delete. The preview reportspending_jobsso that count is never a surprise. - Unscoped and confirmed is an erasure. It records a barrier, sweeps to convergence, and cancels accepted work that has not yet committed, so nothing erased can reappear.
result = memory.delete_by_source(confirm=True, user_id="ada")
result["cancelled_runs"] # in-flight saves the erasure cancelled
result["convergence_passes"] # 1 means the first sweep was sufficient
result["deleted"] # runs, nodes, pages, slices, events, edges, candidates
A save your own erasure cancelled
A cancelled save is terminal, reported through the status you already poll:
terminal = memory.wait_for(receipt["source_packet_id"])
if terminal.get("cancelled_by_delete"):
terminal["status"] # "failed"
terminal["outcome_reason"] # "cancelled_by_delete"
terminal["error"] # "cancelled_by_delete: a confirmed delete erased this scope …"
The status stays failed rather than becoming a new word, because every
released client treats enriched, failed and completed as the complete
terminal set — a fourth value would make an older client poll a finished job
forever. The distinction is published as its own field instead.
Do not retry a cancelled save. It is not transient: the barrier applies to that original work, so a retry is cancelled the same way. If you still want the content stored, submit it as a new save — work accepted after the barrier is ordinary work and lands normally.
Triage — separate real failures from your own erasures by reading the field, which this version returns on every job:
failed = memory.jobs(status="failed", user_id="ada")
broken = [job for job in failed["jobs"] if not job.get("cancelled_by_delete")]
erased = [job for job in failed["jobs"] if job.get("cancelled_by_delete")]
The API also accepts a server-side filter, GET /v1/jobs?cancelled=true|false.
This client cannot send it: jobs() has a fixed signature and raises
TypeError on cancelled. That is a client limitation, not a server one —
filter locally as above, or call the endpoint directly with httpx when you
need the server to do it.
Memory scope
Sending no user_id writes to your account root. Sending one creates an
isolated sub-tenant derived from your account plus that string — two accounts
sending the same user_id never share memory.
Project scope narrows recall within a memory user. Memory that the Claude Code or Codex plugin captured in a project is reachable here only when this client carries the same project scope:
memory.recall(
"deployment decisions",
memory_scope={"project_id": "local_3f2a…"},
recall_scope="project_then_global", # or "project_only"
)
It does not become account-global on its own. Run the plugin's doctor to see
the project id a directory derives.
Errors and types
Non-success responses and transport failures raise MemoryAPIError with:
status: HTTP status, or0for a transport/local error;code: stable API/local error code when one is available;body: parsed response JSON, including non-object JSON;retry_after: parsedRetry-Afterdelay in seconds, including HTTP-date values.
The package includes a py.typed marker and exports inline types for messages,
memory scopes, option dictionaries, write/recall/turn/delete results, packets,
jobs, counts, source-event metadata, recall scopes, capture density, and job
statuses.
0.2.1 release preparation
The source and artifacts prepared from this directory are version 0.2.1.
PyPI 0.1.1 predates per-call read/delete tenant scope, packet/job helpers,
wait_for, and the typed surface. Confirm the installed version when upgrading:
from itsuki import VERSION
print(VERSION)
Full API documentation is available at https://itsuki.app/docs/.
Metadata
Release files for itsuki 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| itsuki-0.3.0.tar.gz | 21.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| itsuki-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 43.2 kB
Release files / itsuki-0.3.0.tar.gz
| Download URL | itsuki-0.3.0.tar.gz |
|---|---|
| Size | 21.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8ddd3c58182823c0f9dbc7dcae2b69cfa849d24689784c31ef9b2c1f8de9d751
|
|
BLAKE2b-256 checksum How to use checksums |
e28420294729b7cb78ef1885150598e4214b7c386e6ce6e01c655e4eec0bf71c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 15, 2026.
Transparency logRelease files / itsuki-0.3.0-py3-none-any.whl
| Download URL | itsuki-0.3.0-py3-none-any.whl |
|---|---|
| Size | 22.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8e3fadb42a1b19803dcc6f783f24d773753d7e1675114aa486912cd3b568a361
|
|
BLAKE2b-256 checksum How to use checksums |
d43d1d52fd6fec55942b1c7f0525a66f549aafa83da3093d40fbf374fc25b8fc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 15, 2026.
Transparency log