Skip to main content

kurumera

Drive a Kurumera store from Python — the platform's tools, the platform's permissions.

pip install kurumera
from kurumera import Kurumera

km = Kurumera()                                  # key from KURUMERA_API_KEY

km.create_product(title="Badam 500g", product_type="Dry Fruit")
for product in km.paginate(km.list_products, limit=100):
    print(product["title"])

Every method is one of the platform's tools, under the same name it has everywhere else. Anything you already know about create_product or apply_page_edits is still true here.


Signing in

Two ways in, the same two the MCP interface has.

A person, with a browser. No key to create, no key to paste, and the browser does not have to be on the same machine:

python -m kurumera login      # shows a short code; approve it in a browser
python -m kurumera status     # what is signed in, without showing the token
  Open   https://kurumera.com/oauth/device
  Code   WXYZ-2468
from kurumera import Kurumera
km = Kurumera.login()         # the same, from Python
km = Kurumera()               # afterwards, the saved token is found automatically

This is the OAuth 2.1 device authorization grant (RFC 8628), the same exchange an MCP client performs, discovered from the MCP endpoint itself. Nothing is pasted back: the process polls for its own token while you approve. It works identically on a laptop, in a container, over SSH and in CI. Tokens refresh silently before they expire.

Against a server too old to offer that grant, login() falls back to a browser-redirect flow that needs the browser on this machine, and login --loopback --manual falls back again to pasting the redirect URL.

A provisioned agent, with a key. This is what a sandbox with no browser uses:

km = Kurumera(api_key="tps_...")     # or KURUMERA_API_KEY in the environment

A key given deliberately always outranks a saved sign-in.

The store comes from your key

There is no tenant, store or shop argument. Not in the constructor, not per call, not as an environment variable. The server works out which store you mean from your credential and ignores anything a client claims.

That is a security property, not an omission: a client that could name a store is a client a prompt-injected agent could aim at someone else's data by inventing an id. This package refuses tenant-shaped arguments even on the dynamic call() path.

Credentials

First one found wins:

  1. Kurumera(api_key="tps_…")
  2. KURUMERA_API_KEY in the environment
  3. ~/.kurumera/config.json — written by kurumera login
export KURUMERA_API_KEY=tps_…
export KURUMERA_AGENT=store        # optional: 'store' or 'builder'

In a sandbox with an unwritable $HOME, point the config elsewhere with KURUMERA_CONFIG_DIR=/tmp/kurumera. The SDK only ever reads that file.

Mint a key with the scopes an agent should have:

python manage.py create_api_key --user you@example.com --name "sdk" \
  --scopes read_products write_products read_orders 

Personas

agent="store" or agent="builder" narrows the tool set to exactly what the platform's provisioned assistants get. It is a cap, never a grant — it can only take tools away from a key, never add them.

km = Kurumera(agent="builder")     # page-builder tools, read-only commerce

Safety you can switch on

km = Kurumera(read_only=True)             # refuses every writing tool, locally
km = Kurumera(allow_destructive=False)    # refuses deletes, publishes, overwrites
km = Kurumera(on_destructive=ask_a_human) # (tool, args, info) -> bool

read_only=True is the highest-value line in this file if your agent reads anything it did not write. It makes an over-scoped key harmless for the duration of a reading task, and it refuses before the request leaves the process. It fails closed: a tool this package has never heard of is refused too.

These are advisory. The server enforces its own rules regardless — they exist so a misdirected agent is stopped early and told which policy stopped it.

Saying why

with km.intent("Restocking after the spring sale"):
    km.bulk_update_products(product_ids=ids, action="set_status", value="ACTIVE")

The merchant sees that sentence beside the call in their activity feed. It is the difference between "something changed 40 products" and an explanation.

Finding a tool

265 is too many to remember and dir() gives a flat wall of names. Discovery is progressive, offline, and costs no round trip:

print(km.help())                   # the subjects
print(km.help("low stock"))        # a plain-English phrase works
print(km.help("adjust_inventory")) # the full signature, types and warnings
adjust_inventory(inventory_item_id: str, location_id: str, delta: int)
  [write]  module: inventory_tools

km.catalog() groups every tool by subject, km.search() returns the same ranking as objects, and km.has("name") says whether a typed method exists.

Use km.has() rather than hasattr, which is always True: an unknown attribute becomes a dynamic call so a tool newer than this package still works.

Results

A result behaves as the payload itself, because that is what every existing doc assumes:

r = km.list_products(limit=5)
r["products"]          # the rows, under the tool's OWN noun
r["total"]             # how many exist in all
r.data                 # the same mapping
r.text                 # the first text block, verbatim
r.raw                  # the untouched envelope

There is no shared results envelope: list_products answers under products, list_content under pages, list_collections under collections. Use km.paginate rather than looping yourself and the difference stops mattering.

Ten tools can also return an image — screenshots and product photos:

shot = km.show_page_screenshot(page_ref="home", full_page=True)
shot.data["page_id"]
if shot.images:
    shot.images[0].save("home.png")

shot.images is empty when capture was skipped. That is a normal outcome, not an error — a screenshot must never fail a write that already succeeded.

Errors

from kurumera import RateLimited, PermissionDenied, ConfirmationRequired

try:
    km.delete_product(product_id=pid)
except ConfirmationRequired:
    km.delete_product(product_id=pid, confirm=True)
except PermissionDenied as e:
    print("this key lacks", e.capability)
except RateLimited as e:
    print("slow down", e.retry_after, "seconds")
Exception When
KurumeraConfigError no credential, bad URL, a tenant argument — nothing was sent
AuthRequired / AuthFailed 401
SubscriptionInactive 402 — the store's subscription will not serve API traffic
TenantInactive, NoTenant, … 403
PermissionDenied the key or role lacks the tool's capability; .capability names it
ConfirmationRequired pass confirm=True
InvalidArguments the arguments failed the tool's schema, server-side
RateLimited 240 reads / 60 writes a minute; .retry_after when the server said
ToolNotFound no such tool for this key
ReadOnlyModeError, DestructiveBlockedError this client's own policy refused

Rate limits are not retried for you. The window is a fixed minute and an agent sandbox gives a script two; sleeping blind would spend most of your budget hiding a signal you should act on. Pass retry_on_rate_limit=True if you are running a batch and mean it.

Finding tools

km.tools()                    # what THIS key may call — asks the server
km.describe("get_report")     # schema, hints, whether it needs confirm
km.search("invoice")          # offline, over names and descriptions
km.check()                    # is this package in step with the server?

km.tools() is the honest answer: the server has already filtered it by your key's capabilities and persona. The offline list (source="package") says nothing about what you are allowed to do.

Timeouts

The gateway in front of the platform closes a read at 60 seconds, so the default client timeout is 65 — just above it, so you get the server's honest error rather than a confusing client-side abort. Raising it accomplishes nothing. Uploads are capped around 18 MB of actual file once base64 inflation is counted.

Versions

kurumera.REGISTRY_TOOL_COUNT and kurumera.REGISTRY_HASH say which tool registry this build was generated from. A tool added to the platform since then is still callable:

km.call("a_tool_added_last_week", some_argument=1)

An SDK version lag is a typing gap, not an outage.

Releasing

The release procedure, the pre-flight gates and the three things about PyPI that cannot be undone are in PUBLISHING.md.

Release files for kurumera 0.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for kurumera 0.6.0
File Size Uploaded
kurumera-0.6.0.tar.gz 275.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for kurumera 0.6.0
File Interpreter ABI Platform
kurumera-0.6.0-py3-none-any.whl Python 3 none any Details

Total release size: 545.7 kB

Release files / kurumera-0.6.0.tar.gz

Download URL kurumera-0.6.0.tar.gz
Size 275.2 kB
Tags Source
SHA-256 checksum
How to use checksums
c5f0316957b0c64f11c6a4a9cbdcf0a13e148a64a37ada51f8171542c882627c
BLAKE2b-256 checksum
How to use checksums
527bd82c36d7ab43add323c03e36532ffa07befd5f301f360b43363177636540
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.2

Release files / kurumera-0.6.0-py3-none-any.whl

Download URL kurumera-0.6.0-py3-none-any.whl
Size 270.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
62e2b80ae69c33f538f36a2e2dac7aa9a1e03fa3de133d9f6ec79ffa5e25770f
BLAKE2b-256 checksum
How to use checksums
4fd2992e94ea5e268040d370b761160b85d25d13e3d8daf62ce6636f6284b81f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.2

Release history Release notifications | RSS feed

This release

0.6.0 This release

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page