|
1 | | -# CueAPI Python SDK |
| 1 | +# cueapi-sdk |
2 | 2 |
|
3 | | -The official Python SDK for [CueAPI](https://cueapi.ai) — scheduling infrastructure for agents. |
| 3 | +Your agents are failing silently. CueAPI tells you when and why. |
| 4 | + |
| 5 | +**Cron has no concept of success. Cue does.** |
| 6 | + |
| 7 | +[](https://pypi.org/project/cueapi-sdk/) |
| 8 | +[](https://pypi.org/project/cueapi-sdk/) |
| 9 | +[](LICENSE) |
| 10 | +[](.github/workflows) |
| 11 | +[](https://docs.cueapi.ai) |
| 12 | + |
| 13 | +The official Python SDK for [CueAPI](https://cueapi.ai) - open source execution accountability for AI agents. |
| 14 | + |
| 15 | +--- |
4 | 16 |
|
5 | 17 | ## Install |
6 | 18 |
|
7 | 19 | ```bash |
8 | 20 | pip install cueapi-sdk |
9 | 21 | ``` |
10 | 22 |
|
| 23 | +--- |
| 24 | + |
11 | 25 | ## Quickstart |
12 | 26 |
|
13 | 27 | ```python |
14 | 28 | from cueapi import CueAPI |
15 | 29 |
|
16 | 30 | client = CueAPI("cue_sk_your_key") |
| 31 | + |
| 32 | +# Schedule an agent to run every morning |
17 | 33 | cue = client.cues.create( |
18 | | - name="daily-report", |
| 34 | + name="morning-agent-brief", |
19 | 35 | cron="0 9 * * *", |
20 | | - callback="https://my-app.com/webhook", |
21 | | - payload={"task": "generate_report"}, |
| 36 | + timezone="America/Los_Angeles", |
| 37 | + callback="https://your-agent.com/run", |
| 38 | + payload={"task": "daily_brief"}, |
22 | 39 | ) |
23 | | -print(f"Next run: {cue.next_run}") |
24 | | -``` |
25 | 40 |
|
26 | | -## Why CueAPI? |
| 41 | +print(f"Scheduled. Next run: {cue.next_run}") |
| 42 | +# Scheduled. Next run: 2026-03-28T09:00:00-08:00 |
| 43 | +``` |
27 | 44 |
|
28 | | -- **Replace fragile cron jobs** — managed scheduling with automatic retries, execution logs, and failure alerts. No servers to maintain. |
29 | | -- **Built for AI agents** — schedule agent tasks, coordinate multi-agent pipelines, and retry failed workflows with exponential backoff. |
30 | | -- **Two transport modes** — webhook delivery to your public URL, or worker pull for agents behind firewalls. |
| 45 | +--- |
31 | 46 |
|
32 | | -## Transport Modes |
| 47 | +## Why CueAPI over cron? |
33 | 48 |
|
34 | | -### Webhook |
| 49 | +Your agent ran at 3am. Did it succeed? Cron does not know. |
35 | 50 |
|
36 | | -CueAPI POSTs a signed payload to your callback URL when a cue fires: |
| 51 | +CueAPI tracks every execution separately from delivery, so you always know what happened. |
37 | 52 |
|
38 | 53 | ```python |
39 | | -cue = client.cues.create( |
40 | | - name="webhook-task", |
41 | | - cron="0 9 * * *", |
42 | | - callback="https://my-app.com/webhook", |
43 | | - payload={"action": "sync"}, |
44 | | -) |
| 54 | +# Check what actually happened |
| 55 | +execution = client.executions.get("exec_01HX...") |
| 56 | +print(execution.outcome) # "success" or "failure" -- reported by your handler |
| 57 | +print(execution.attempts) # 1 (or 2, 3 if it had to retry) |
| 58 | +print(execution.delivered_at) # exactly when it was delivered |
| 59 | +print(execution.status) # "delivered", "failed", "retrying" |
45 | 60 | ``` |
46 | 61 |
|
47 | | -### Worker |
| 62 | +**What you get that cron cannot give you:** |
| 63 | + |
| 64 | +- Execution history with outcome tracking |
| 65 | +- Automatic retries with exponential backoff (1, 5, 15 min) |
| 66 | +- Email + webhook alerts when all retries exhaust |
| 67 | +- Worker transport with no public URL needed |
| 68 | +- Signed webhook payloads |
48 | 69 |
|
49 | | -Your local daemon polls CueAPI for executions. No public URL needed: |
| 70 | +--- |
| 71 | + |
| 72 | +## Two transport modes |
| 73 | + |
| 74 | +### Webhook (default) |
| 75 | + |
| 76 | +CueAPI POSTs a signed payload to your URL when a cue fires: |
50 | 77 |
|
51 | 78 | ```python |
52 | 79 | cue = client.cues.create( |
53 | | - name="worker-task", |
| 80 | + name="data-sync", |
54 | 81 | cron="0 */6 * * *", |
55 | | - transport="worker", |
56 | | - payload={"pipeline": "etl"}, |
| 82 | + callback="https://your-app.com/webhook", |
| 83 | + payload={"pipeline": "sync"}, |
57 | 84 | ) |
58 | 85 | ``` |
59 | 86 |
|
60 | | -Install the worker: `pip install cueapi-worker` |
| 87 | +Your handler receives the payload, processes it, and CueAPI records the outcome. |
61 | 88 |
|
62 | | -## Method Reference |
| 89 | +### Worker (no public URL needed) |
63 | 90 |
|
64 | | -### `CueAPI(api_key, *, base_url, timeout)` |
| 91 | +For agents running locally, in private networks, or behind firewalls. Your daemon polls CueAPI instead of waiting for inbound requests. |
65 | 92 |
|
66 | | -Create a client. `api_key` starts with `cue_sk_`. |
67 | | - |
68 | | -### `client.cues.create(...)` |
69 | | - |
70 | | -| Parameter | Type | Description | |
71 | | -|---|---|---| |
72 | | -| `name` | `str` | **Required.** Unique name for the cue. | |
73 | | -| `cron` | `str` | Cron expression for recurring schedules. | |
74 | | -| `at` | `str \| datetime` | ISO 8601 datetime for one-time schedules. | |
75 | | -| `timezone` | `str` | IANA timezone (default `"UTC"`). | |
76 | | -| `callback` | `str` | Webhook URL for execution delivery. | |
77 | | -| `transport` | `str` | `"webhook"` (default) or `"worker"`. | |
78 | | -| `payload` | `dict` | JSON payload included in each execution. | |
79 | | -| `description` | `str` | Optional description. | |
80 | | -| `retry` | `dict` | `{"max_attempts": 3, "backoff_minutes": [1, 5, 15]}` | |
81 | | -| `on_failure` | `dict` | `{"email": true, "webhook": null, "pause": false}` | |
82 | | - |
83 | | -Returns a `Cue` object. |
84 | | - |
85 | | -### `client.cues.list(*, limit, offset, status)` |
86 | | - |
87 | | -Returns a `CueList` with `.cues`, `.total`, `.limit`, `.offset`. |
88 | | - |
89 | | -### `client.cues.get(cue_id)` |
90 | | - |
91 | | -Returns a `Cue` object. |
92 | | - |
93 | | -### `client.cues.update(cue_id, **fields)` |
| 93 | +```bash |
| 94 | +pip install cueapi-worker |
| 95 | +``` |
94 | 96 |
|
95 | | -Update any field. Only provided fields are changed. |
| 97 | +```python |
| 98 | +from cueapi_worker import Worker |
96 | 99 |
|
97 | | -### `client.cues.pause(cue_id)` |
| 100 | +worker = Worker(api_key="cue_sk_your_key") |
98 | 101 |
|
99 | | -Pause a cue. Returns the updated `Cue`. |
| 102 | +@worker.handler("run-agent") |
| 103 | +def handle(payload): |
| 104 | + result = run_my_agent(payload["task"]) |
| 105 | + return {"status": "success", "summary": result} |
100 | 106 |
|
101 | | -### `client.cues.resume(cue_id)` |
| 107 | +worker.start() # polls continuously, no inbound firewall rules needed |
| 108 | +``` |
102 | 109 |
|
103 | | -Resume a paused cue. Returns the updated `Cue`. |
| 110 | +Create the cue with `transport="worker"`: |
104 | 111 |
|
105 | | -### `client.cues.delete(cue_id)` |
| 112 | +```python |
| 113 | +cue = client.cues.create( |
| 114 | + name="nightly-pipeline", |
| 115 | + cron="0 2 * * *", |
| 116 | + transport="worker", |
| 117 | + payload={"pipeline": "etl"}, |
| 118 | +) |
| 119 | +``` |
106 | 120 |
|
107 | | -Delete a cue. Returns `None`. |
| 121 | +--- |
108 | 122 |
|
109 | | -## Webhook Verification |
| 123 | +## Webhook verification |
110 | 124 |
|
111 | | -Verify incoming webhook signatures in your handler: |
| 125 | +Always verify incoming webhook signatures before processing: |
112 | 126 |
|
113 | 127 | ```python |
114 | 128 | from cueapi import verify_webhook |
115 | 129 |
|
116 | | -is_valid = verify_webhook( |
117 | | - payload=request.body, |
118 | | - signature=request.headers["X-CueAPI-Signature"], |
119 | | - timestamp=request.headers["X-CueAPI-Timestamp"], |
120 | | - secret="whsec_your_secret", |
121 | | -) |
| 130 | +@app.post("/webhook") |
| 131 | +def handle_cue(request: Request): |
| 132 | + is_valid = verify_webhook( |
| 133 | + payload=request.body, |
| 134 | + signature=request.headers["X-CueAPI-Signature"], |
| 135 | + timestamp=request.headers["X-CueAPI-Timestamp"], |
| 136 | + secret="whsec_your_secret", |
| 137 | + tolerance=300, # seconds, default |
| 138 | + ) |
| 139 | + |
| 140 | + if not is_valid: |
| 141 | + return {"error": "invalid signature"}, 401 |
| 142 | + |
| 143 | + data = request.json() |
| 144 | + run_task(data["payload"]) |
| 145 | + return {"outcome": "success"} |
122 | 146 | ``` |
123 | 147 |
|
124 | | -## Error Handling |
| 148 | +Signatures use HMAC-SHA256 in `v1={hex}` format. The `tolerance` parameter rejects replayed requests older than 5 minutes. |
| 149 | + |
| 150 | +--- |
| 151 | + |
| 152 | +## Error handling |
125 | 153 |
|
126 | 154 | ```python |
127 | | -from cueapi import CueAPI, AuthenticationError, RateLimitError, CueNotFoundError |
| 155 | +from cueapi import ( |
| 156 | + CueAPI, |
| 157 | + AuthenticationError, |
| 158 | + CueLimitExceededError, |
| 159 | + CueNotFoundError, |
| 160 | + InvalidScheduleError, |
| 161 | + RateLimitError, |
| 162 | + CueAPIServerError, |
| 163 | +) |
128 | 164 |
|
129 | 165 | try: |
130 | | - cue = client.cues.get("cue_abc123") |
131 | | -except CueNotFoundError: |
132 | | - print("Cue not found") |
| 166 | + cue = client.cues.create( |
| 167 | + name="my-agent", |
| 168 | + cron="0 9 * * *", |
| 169 | + callback="https://example.com/run", |
| 170 | + ) |
| 171 | +except CueLimitExceededError: |
| 172 | + print("Plan limit reached. Upgrade at cueapi.ai") |
| 173 | +except InvalidScheduleError as e: |
| 174 | + print(f"Bad cron expression: {e}") |
133 | 175 | except AuthenticationError: |
134 | 176 | print("Invalid API key") |
135 | 177 | except RateLimitError as e: |
136 | 178 | print(f"Rate limited. Retry after {e.retry_after}s") |
| 179 | +except CueAPIServerError: |
| 180 | + print("Server error. CueAPI status at status.cueapi.ai") |
137 | 181 | ``` |
138 | 182 |
|
139 | | -| Exception | HTTP Status | When | |
| 183 | +| Exception | HTTP | When | |
140 | 184 | |---|---|---| |
141 | 185 | | `AuthenticationError` | 401 | Invalid or missing API key | |
142 | 186 | | `CueLimitExceededError` | 403 | Plan cue limit reached | |
143 | | -| `CueNotFoundError` | 404 | Cue ID doesn't exist | |
| 187 | +| `CueNotFoundError` | 404 | Cue ID does not exist | |
144 | 188 | | `InvalidScheduleError` | 400/422 | Bad cron expression or request body | |
145 | 189 | | `RateLimitError` | 429 | Too many requests | |
146 | 190 | | `CueAPIServerError` | 5xx | Server error | |
147 | 191 |
|
| 192 | +--- |
| 193 | + |
| 194 | +## Full method reference |
| 195 | + |
| 196 | +### `CueAPI(api_key, *, base_url, timeout)` |
| 197 | + |
| 198 | +```python |
| 199 | +client = CueAPI( |
| 200 | + api_key="cue_sk_...", # or set CUEAPI_API_KEY env var |
| 201 | + base_url="https://api.cueapi.ai", # default |
| 202 | + timeout=30, # seconds, default |
| 203 | +) |
| 204 | +``` |
| 205 | + |
| 206 | +### `client.cues.create(**fields)` |
| 207 | + |
| 208 | +| Parameter | Type | Description | |
| 209 | +|---|---|---| |
| 210 | +| `name` | `str` | Required. Unique name. | |
| 211 | +| `cron` | `str` | Cron expression for recurring schedules. | |
| 212 | +| `at` | `str or datetime` | ISO 8601 for one-time schedules. | |
| 213 | +| `timezone` | `str` | IANA timezone (default `"UTC"`). | |
| 214 | +| `callback` | `str` | Webhook URL (required if `transport="webhook"`). | |
| 215 | +| `transport` | `str` | `"webhook"` (default) or `"worker"`. | |
| 216 | +| `payload` | `dict` | JSON payload included in each execution. | |
| 217 | +| `description` | `str` | Optional description. | |
| 218 | +| `retry` | `dict` | `{"max_attempts": 3, "backoff_minutes": [1, 5, 15]}` | |
| 219 | +| `on_failure` | `dict` | `{"email": true, "webhook": null, "pause": false}` | |
| 220 | + |
| 221 | +Returns a `Cue` object. |
| 222 | + |
| 223 | +### Other cue methods |
| 224 | + |
| 225 | +```python |
| 226 | +client.cues.list(limit=20, offset=0, status="active") # CueList |
| 227 | +client.cues.get("cue_abc123") # Cue |
| 228 | +client.cues.update("cue_abc123", cron="0 10 * * *") # Cue |
| 229 | +client.cues.pause("cue_abc123") # Cue |
| 230 | +client.cues.resume("cue_abc123") # Cue |
| 231 | +client.cues.delete("cue_abc123") # None |
| 232 | +``` |
| 233 | + |
| 234 | +### Executions |
| 235 | + |
| 236 | +```python |
| 237 | +client.executions.list(cue_id="cue_abc123", limit=20) # ExecutionList |
| 238 | +client.executions.get("exec_01HX...") # Execution |
| 239 | +``` |
| 240 | + |
| 241 | +--- |
| 242 | + |
| 243 | +## Examples |
| 244 | + |
| 245 | +See [`/examples`](examples/) for working code: |
| 246 | + |
| 247 | +- [`basic_usage.py`](examples/basic_usage.py) - create, list, pause, delete cues |
| 248 | +- [`webhook_handler.py`](examples/webhook_handler.py) - FastAPI handler with signature verification |
| 249 | +- [`worker_setup.py`](examples/worker_setup.py) - worker daemon for private network agents |
| 250 | + |
| 251 | +--- |
| 252 | + |
148 | 253 | ## Links |
149 | 254 |
|
150 | | -- [Documentation](https://docs.cueapi.ai) |
151 | | -- [API Reference](https://docs.cueapi.ai/api-reference/overview/) |
152 | | -- [Dashboard](https://dashboard.cueapi.ai) |
153 | | -- [CueAPI](https://cueapi.ai) |
| 255 | +- [Dashboard](https://dashboard.cueapi.ai) - manage cues and view executions |
| 256 | +- [Documentation](https://docs.cueapi.ai) - full guides and API reference |
| 257 | +- [API Reference](https://docs.cueapi.ai/api-reference/overview) - all endpoints |
| 258 | +- [cueapi-core](https://github.com/cueapi/cueapi-core) - open source server |
| 259 | +- [cueapi.ai](https://cueapi.ai) - hosted service, free tier available |
| 260 | + |
| 261 | +--- |
| 262 | + |
| 263 | +## License |
| 264 | + |
| 265 | +MIT. See [LICENSE](LICENSE). |
| 266 | + |
| 267 | +--- |
| 268 | + |
| 269 | +*Built by [Vector Apps](https://cueapi.ai/about)* |
0 commit comments