geki
Python client for the GEKI API.
pip install geki
Python 3.12 or newer.
Stability
The GEKI API has three levels. The model API (/v1/models,
/v1/chat/completions, /v1/embeddings) is stable. The bots API (bots,
templates, triggers, runs, previews) is beta and will become stable. Everything
else (agents, sessions, vaults, files, uploads, skills) is beta and may change
without a transition. This package stays 0.x while the bots API is beta.
Update it when a beta call breaks.
For the model API you can also use the OpenAI SDK with
base_url="https://api.geki.ai/v1".
Auth
Create an API key in the GEKI Console. Pass it or set GEKI_API_KEY.
GEKI_API_URL changes the base URL (default https://api.geki.ai/v1).
from geki import Geki
client = Geki() # reads GEKI_API_KEY
Bots
bot = client.bots.create(name="Invoices", model="glm-5.3-flash", instructions="Sort invoices.")
client.bots.update(bot.id, tags=["finance"])
caps = client.bots.capabilities(bot.id)
client.bots.publish(
bot.id,
acknowledgements=[{"id": e.id, "fingerprint": e.fingerprint} for e in caps.entries],
)
for row in client.bots.iter(status="live"):
print(row.id, row.name)
Triggers and runs:
trigger = client.triggers.create(bot.id, kind="manual", input_prompt="Summarize today.")
run = client.triggers.fire(bot.id, trigger.id)
print(client.runs.get(run.id).status)
Webhook triggers are signed with HMAC. The client signs for you:
hook = client.triggers.create(bot.id, kind="webhook", input_prompt="New ticket:")
client.hooks.send(hook.id, {"ticket": 42}, secret=hook.secret)
geki.webhooks.sign(secret, body) returns the headers if you send the request
yourself.
Personal bot
Each org has one Personal bot. Members tune it within what the org allows.
slot = client.personal_bot.create() # get() reads without creating
client.personal_bot.put_settings(instructions="Answer in German.", web_search=True)
client.personal_bot.put_token("jira", "my-token") # write-only; delete_token() removes it
session = client.sessions.create(
agent_id=slot.agent_id, personal_options={"workspace": False, "web_search": True}
)
client.sessions.update(session.id, personal_options={"web_search": False})
The owner edits the slot like any bot, with member_options in the draft and
owner_can_read as bot state: client.bots.update(slot.bot_id, owner_can_read=True).
Responses
Every call returns a Pydantic model from geki.models. Fields the client does
not know yet stay available in model_extra. to_dict() returns plain JSON.
Lists return one page (data, has_more, last_id). iter() walks all
pages:
page = client.runs.list(bot_id=bot.id, limit=20)
for run in client.runs.iter(bot_id=bot.id, max_pages=5):
...
client.request(method, path, ...) calls any route and returns the JSON.
Errors
Errors raise geki.GekiError with status, code, message, param,
fields, entries and request_id. Branch on the class or on code:
| Class | When |
|---|---|
BadRequest |
400, 413 and other 4xx |
AuthError |
401, 403 |
BudgetExhausted |
402 |
NotFound |
404 |
Conflict |
409 |
AcknowledgementRequired |
409 acknowledgement_required; entries lists what to acknowledge |
RateLimited |
429; retry_after in seconds |
ServerError |
5xx |
APIConnectionError, APITimeoutError |
no response |
Streaming
Session events, previews and chat completions stream as server-sent events:
session = client.sessions.create(agent_id=agent.id, environment={"type": "none"})
client.sessions.message(session.id, "Hello")
with client.sessions.stream_events(session.id) as stream:
for event in stream:
print(event.event, event.json())
for chunk in client.chat.completions.stream(
model="glm-5.3-flash", messages=[{"role": "user", "content": "Hi"}]
):
print(chunk["choices"][0]["delta"].get("content", ""), end="")
Files and skills
client.files.create("report.pdf", purpose="user_data")
content = client.files.content(file_id) # Download(content, content_type, filename)
client.skills.create("skill.zip", name="Mail")
client.uploads.upload("big.csv", purpose="file") # presigned upload for large files
Async
AsyncGeki has the same transport (request, stream, paginate,
download) and the calls a chat UI needs: models, chat completions, bots get,
previews, the Personal bot, sessions, uploads and file content.
from geki import AsyncGeki
async with AsyncGeki() as client:
bot = await client.bots.get(bot_id)
Key refresh
on_unauthorized is called once after a 401. Return a new key to retry the
request, or None to raise AuthError.
client = Geki(api_key, on_unauthorized=lambda: fetch_new_key())
CLI
The geki command ships as the extra geki[cli] (Typer, Rich, PyYAML):
uv tool install "geki[cli]" # or: pipx install "geki[cli]"
geki auth login # asks for the API key, checks it, saves it
The key is saved in ~/.config/geki/config.toml (mode 600) under a profile.
Flags win over the environment, the environment over the profile:
--api-key > GEKI_API_KEY > profile, --api-url > GEKI_API_URL > profile
https://api.geki.ai/v1.--profileorGEKI_PROFILEpicks the profile (defaultdefault).
geki bots list --status live
geki bots get BOT -o yaml > bot.yaml
geki bots update BOT -f bot.yaml # sends only the fields that changed
geki bots edit BOT # the draft in $EDITOR
geki bots publish BOT # asks to acknowledge new capabilities
geki triggers create BOT --kind cron --cron "0 9 * * 1" --timezone Europe/Berlin --prompt "Weekly report"
geki runs watch "$(geki triggers fire BOT TRIGGER -q)"
geki runs list --bot BOT --status failed --all -o json
Every command has --help. -o table|json|yaml sets the output; JSON and
YAML print the API object. -q prints only ids. Lists take --limit,
--after and --all. Errors go to stderr with the code and the request id;
the exit code is 1 for API errors and 2 for usage errors. --debug logs each
request without the key. Webhook secrets are printed once and never saved.
Admin
GekiAdmin calls the internal admin routes with a system key. They are not
reachable on api.geki.ai; only GEKI services use it.
License
MIT
Metadata
Release files for geki 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 | |
|---|---|---|---|
| geki-0.3.0.tar.gz | 55.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| geki-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 110.5 kB
Release files / geki-0.3.0.tar.gz
| Download URL | geki-0.3.0.tar.gz |
|---|---|
| Size | 55.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b7a0cc3a7b5bccdf2c32022595477ee435a33987012fe84d331f3be8b4ad7017
|
|
BLAKE2b-256 checksum How to use checksums |
f004641b8d67227f0a56fecac77854643fea522fdc8e9d7cb6f18f97f02bf488
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / geki-0.3.0-py3-none-any.whl
| Download URL | geki-0.3.0-py3-none-any.whl |
|---|---|
| Size | 54.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fec1b938f940db2ee172471314905c0088341107988ba5dfc78c892c2363566b
|
|
BLAKE2b-256 checksum How to use checksums |
9052e8967923af5476411e79c4638ce5467a6b633138ba71f1485f402642390d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|