Skip to main content

10-minute quickstart

By the end of this walkthrough you will have: a user account, a connected Google or Outlook calendar, an API key, a routine task created via curl, a public HTTPS tunnel, a signed webhook endpoint registered, and one reminder.fired webhook posted to your laptop.

1. Register an account

Create a user via the normal sign-up flow at /auth/register (or the existing TIMEMANAGER UI). Sign in. For the rest of this guide we assume the session cookie (or bearer token) is stored in the environment variable MYKAL_TOKEN.

2. Connect a calendar

Start the OAuth flow for your provider. This returns an auth_url you open in a browser; when consent completes the callback redirects back to /mykal/pages/integrations.html.

bash
# Replace with "microsoft" for Outlook/Graph
curl -X POST "$MYKAL_BASE/api/v1/mykal/integrations/google/oauth/start" \
    -H "Authorization: Bearer $MYKAL_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"return_url":"pages/integrations.html"}'

3. Create an API key

Status: a first-class API-key UI is not yet shipped. Until it lands, use your logged-in session cookie or a SHARED_AUTH bearer token directly. Contact support for a long-lived, agent-scoped key.

4. Create your first task

One HTTP call creates a RoutineTask:

curl
curl -X POST "$MYKAL_BASE/api/v1/mykal/tasks" \
    -H "Authorization: Bearer $MYKAL_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "title": "Write quickstart blog post",
      "raw_input": "Write quickstart blog post ~2h, prio 2",
      "priority": 2,
      "duration_min": 120,
      "reminder_channels": ["email","inapp","webhook"]
    }'
python
import os, httpx

base = os.environ["MYKAL_BASE"]
token = os.environ["MYKAL_TOKEN"]

r = httpx.post(
    f"{base}/api/v1/mykal/tasks",
    headers={"Authorization": f"Bearer {token}"},
    json={
        "title": "Write quickstart blog post",
        "raw_input": "Write quickstart blog post ~2h, prio 2",
        "priority": 2,
        "duration_min": 120,
        "reminder_channels": ["email", "inapp", "webhook"],
    },
    timeout=15.0,
)
r.raise_for_status()
print(r.json()["id"])
node
// Node 18+: native fetch
const base = process.env.MYKAL_BASE;
const token = process.env.MYKAL_TOKEN;

const res = await fetch(`${base}/api/v1/mykal/tasks`, {
    method: "POST",
    headers: {
        "Authorization": `Bearer ${token}`,
        "Content-Type": "application/json",
    },
    body: JSON.stringify({
        title: "Write quickstart blog post",
        raw_input: "Write quickstart blog post ~2h, prio 2",
        priority: 2,
        duration_min: 120,
        reminder_channels: ["email", "inapp", "webhook"],
    }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const task = await res.json();
console.log(task.id);

5. Open a public tunnel with ngrok

mykal only posts webhooks to public HTTPS URLs. Spin up a local listener (Python / Node) on port 4000 and tunnel it.

bash
ngrok http 4000
# Copy the https://<hash>.ngrok-free.app URL
python
# minimal receiver on port 4000 (no signature check yet -- see webhooks.html)
from fastapi import FastAPI, Request
import uvicorn
app = FastAPI()

@app.post("/hook")
async def hook(req: Request):
    print("headers:", dict(req.headers))
    print("body:", (await req.body()).decode("utf-8"))
    return {"ok": True}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=4000)

6. Register the webhook

POST your tunnel URL to create an endpoint. The response body contains the plaintext secret exactly once. Store it — you cannot fetch it again, only rotate.

curl
curl -X POST "$MYKAL_BASE/api/v1/mykal/webhooks" \
    -H "Authorization: Bearer $MYKAL_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "laptop-dev",
      "url":  "https://<hash>.ngrok-free.app/hook",
      "enabled": true,
      "max_attempts": 4,
      "fallback_to_email": true
    }'
# response includes "id": "...",  "secret": "...",  "secret_preview": "abcd...wxyz"

7. Fire a test + wait for a real reminder

First verify the plumbing with a synthetic ping (does NOT consume the retry budget):

curl
curl -X POST "$MYKAL_BASE/api/v1/mykal/webhooks/$ENDPOINT_ID/test" \
    -H "Authorization: Bearer $MYKAL_TOKEN"
# returns {"ok":true,"status_code":200,"latency_ms":...,"signature":"t=...,v1=..."}

Now create a reminder (or let a scheduled task's reminder fire naturally) and watch your receiver log the signed POST with headers X-Mykal-Signature, X-Mykal-Timestamp, X-Mykal-Delivery-Id, X-Mykal-Event: reminder.fired. Next step: verify the signature.

Done. You have now proven end-to-end: auth → calendar → API → webhook registration → signed delivery. From here, head to the AI agent guide to wire an LLM into your receiver, or to the API reference for the full surface.