Metadata-Version: 2.5
Name: geki
Version: 0.3.0
Summary: Python client for the GEKI API
Project-URL: Homepage, https://geki.ai
Project-URL: Documentation, https://api.geki.ai/docs
Author: GEKI.AI
License-Expression: MIT
License-File: LICENSE
Keywords: agents,api,bots,client,geki
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: httpx<1,>=0.28
Requires-Dist: pydantic<3,>=2.7
Provides-Extra: cli
Requires-Dist: pyyaml<7,>=6.0; extra == 'cli'
Requires-Dist: rich>=13.7; extra == 'cli'
Requires-Dist: typer<1,>=0.27; extra == 'cli'
Description-Content-Type: text/markdown

# geki

Python client for the [GEKI API](https://api.geki.ai/docs).

```bash
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`).

```python
from geki import Geki

client = Geki()  # reads GEKI_API_KEY
```

## Bots

```python
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:

```python
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:

```python
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.

```python
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:

```python
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:

```python
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

```python
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.

```python
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`.

```python
client = Geki(api_key, on_unauthorized=lambda: fetch_new_key())
```

## CLI

The `geki` command ships as the extra `geki[cli]` (Typer, Rich, PyYAML):

```bash
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`. `--profile` or `GEKI_PROFILE` picks the profile
(default `default`).

```bash
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
