Triggers
Webhook triggers that turn GitHub, Sentry, monitoring or custom events into agent sessions, plus the notification inbox.
A trigger is a signed inbound webhook bound to your account. Each delivery is verified with the trigger's own secret, normalised into an event card, run through the trigger policy (filters, budget, rate limit, dedupe) and then either opens a session, appends to an open one or only posts a notification.
Signing generic deliveries
Custom senders sign with the codai scheme:
x-codai-signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>
x-codai-delivery: <your delivery id>- The HMAC covers the timestamp, a literal
.and the raw request body, byte for byte — sign before any re-serialisation. tmust be within 5 minutes of the gateway clock; older or future timestamps are rejected asstale_timestamp.x-codai-deliveryis the idempotency key: a repeated delivery id is recorded once. Without it the gateway derives an id fromtand a hash of the body.- GitHub (
X-Hub-Signature-256) and Sentry (Sentry-Hook-Signature) sources use their native signatures instead.
Ingress
The trigger policy field ingress decides where deliveries are accepted:
public(default) — the internet-facingPOST /v1/hooks/{id}.local— LAN-only sources. The public URL answers 404; deliveries arrive through the codaid daemon or the desktop app broker on your network, which forward them with the same signature check.both— either path.
Use rotate-secret to mint a new generated secret, or PUT …/secret to set the secret a third-party service already signs with. POST …/test fires a signed sample delivery through the full pipeline.
bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
x-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128The caller's triggers.
application/json- response
data*array<>sources*array<>curl -X GET 'https://ai.codai.ro/v1/triggers'{ "data": [ { "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string", "source": "github", "enabled": true, "policy": { "mode": "notify", "smart": { "assistedAt": "info", "autopilotAt": "info", "keywords": { "property1": "notify", "property2": "notify" } }, "subjects": [ "string" ], "minSeverity": "info", "permissions": "read", "quietHours": { "from": "string", "to": "string", "tz": "Europe/Bucharest" }, "budgetMicroUsdPerEvent": 2000000, "budgetMicroUsdPerDay": 10000000, "maxSessionsPerHour": 6, "dedupeWindowMin": 1440, "promptTemplate": "string", "skill": "string", "target": { "kind": "any", "id": "string" }, "ttlMin": 240 }, "hook_url": "http://example.com", "last_event_at": "2019-08-24T14:15:22Z", "created_at": "2019-08-24T14:15:22Z" } ], "sources": [ "github" ]}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
x-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128application/json- body
name*string1 <= length <= 120source*TriggerSourceHow deliveries are authenticated and normalized (see POST /v1/hooks/{id}).
"github""sentry""gcp""generic""form""manual"policy?Partial TriggerPolicy; omitted keys take their defaults, invalid values are a 400.
enabled?booleantrueCreated; secret is shown once.
application/json- response
id*stringuuidname*stringsource*TriggerSourceHow deliveries are authenticated and normalized (see POST /v1/hooks/{id}).
"github""sentry""gcp""generic""form""manual"enabled*booleanpolicy*The trigger policy with every default filled in (as returned by the API).
hook_url*stringWhere the sender posts, https://api.codai.ro/v1/hooks/<id> in production.
urilast_event_at*|date-timecreated_at*stringdate-timesecret*stringSigning secret, whsec_ prefix. Shown only in this response.
curl -X POST 'https://ai.codai.ro/v1/triggers' \ -H 'Content-Type: application/json' \ -d '{ "name": "string", "source": "github"}'{ "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string", "source": "github", "enabled": true, "policy": { "mode": "notify", "smart": { "assistedAt": "info", "autopilotAt": "info", "keywords": { "property1": "notify", "property2": "notify" } }, "subjects": [ "string" ], "minSeverity": "info", "permissions": "read", "quietHours": { "from": "string", "to": "string", "tz": "Europe/Bucharest" }, "budgetMicroUsdPerEvent": 2000000, "budgetMicroUsdPerDay": 10000000, "maxSessionsPerHour": 6, "dedupeWindowMin": 1440, "promptTemplate": "string", "skill": "string", "target": { "kind": "any", "id": "string" }, "ttlMin": 240 }, "hook_url": "http://example.com", "last_event_at": "2019-08-24T14:15:22Z", "created_at": "2019-08-24T14:15:22Z", "secret": "string"}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
id*stringTrigger UUID. A malformed, unknown or foreign id is 404.
uuidx-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128application/json- body
name?string1 <= length <= 120policy?Replaces the stored policy (not merged); omitted keys take their defaults.
enabled?booleanThe updated trigger.
application/json- response
id*stringuuidname*stringsource*TriggerSourceHow deliveries are authenticated and normalized (see POST /v1/hooks/{id}).
"github""sentry""gcp""generic""form""manual"enabled*booleanpolicy*The trigger policy with every default filled in (as returned by the API).
hook_url*stringWhere the sender posts, https://api.codai.ro/v1/hooks/<id> in production.
urilast_event_at*|date-timecreated_at*stringdate-timecurl -X PATCH 'https://ai.codai.ro/v1/triggers/497f6eca-6276-4993-bfeb-53cbbbba6f08' \ -H 'Content-Type: application/json' \ -d '{}'{ "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "name": "string", "source": "github", "enabled": true, "policy": { "mode": "notify", "smart": { "assistedAt": "info", "autopilotAt": "info", "keywords": { "property1": "notify", "property2": "notify" } }, "subjects": [ "string" ], "minSeverity": "info", "permissions": "read", "quietHours": { "from": "string", "to": "string", "tz": "Europe/Bucharest" }, "budgetMicroUsdPerEvent": 2000000, "budgetMicroUsdPerDay": 10000000, "maxSessionsPerHour": 6, "dedupeWindowMin": 1440, "promptTemplate": "string", "skill": "string", "target": { "kind": "any", "id": "string" }, "ttlMin": 240 }, "hook_url": "http://example.com", "last_event_at": "2019-08-24T14:15:22Z", "created_at": "2019-08-24T14:15:22Z"}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
id*stringTrigger UUID. A malformed, unknown or foreign id is 404.
uuidx-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128Deleted.
application/json- response
id*stringuuiddeleted*truetruecurl -X DELETE 'https://ai.codai.ro/v1/triggers/497f6eca-6276-4993-bfeb-53cbbbba6f08'{ "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "deleted": true}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
id*stringTrigger UUID. A malformed, unknown or foreign id is 404.
uuidx-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128The new secret (shown once).
application/json- response
id*stringuuidsecret*stringNew signing secret, whsec_ prefix. Never returned again.
curl -X POST 'https://ai.codai.ro/v1/triggers/497f6eca-6276-4993-bfeb-53cbbbba6f08/rotate-secret'{ "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "secret": "string"}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
id*stringTrigger UUID. A malformed, unknown or foreign id is 404.
uuidx-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128application/json- body
secret*string16 <= length <= 512Secret replaced.
application/json- response
id*stringuuidreplaced*booleancurl -X PUT 'https://ai.codai.ro/v1/triggers/497f6eca-6276-4993-bfeb-53cbbbba6f08/secret' \ -H 'Content-Type: application/json' \ -d '{ "secret": "stringstringstri"}'{ "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "replaced": true}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
id*stringTrigger UUID. A malformed, unknown or foreign id is 404.
uuidx-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128application/json- body
title?stringlength <= 200message?stringlength <= 4000severity?TriggerSeverity"info""low""medium""high""critical"url?stringurisubject?stringlength <= 200The recorded event and the policy decision.
application/json- response
event_id*integerId of the recorded delivery (see GET /v1/triggers/{id}/events).
decision*What the policy decided for one event and why (reason is shown on the session).
session_id*|Session started or appended to; null for notify/ignore decisions.
uuidcard*A delivery normalized into one source-independent shape.
curl -X POST 'https://ai.codai.ro/v1/triggers/497f6eca-6276-4993-bfeb-53cbbbba6f08/test' \ -H 'Content-Type: application/json' \ -d '{}'{ "event_id": 0, "decision": { "action": "ignore", "reason": "string", "mode": "notify", "wanted": "notify", "sessionId": "f6567dd8-e069-418e-8893-7d22fcf12459" }, "session_id": "1ffd059c-17ea-40a8-8aef-70fd0307db82", "card": { "source": "github", "kind": "string", "title": "string", "summary": "string", "severity": "info", "url": "string", "subject": "string", "dedupeKey": "string", "labels": [ "string" ], "resolved": true }}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
id*stringTrigger UUID. A malformed, unknown or foreign id is 404.
uuidlimit?integerPage size, clamped to 1–200; anything non-numeric falls back to 50.
1 <= value <= 20050x-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128Deliveries ordered by receivedAt descending.
application/json- response
data*array<>curl -X GET 'https://ai.codai.ro/v1/triggers/497f6eca-6276-4993-bfeb-53cbbbba6f08/events'{ "data": [ { "id": 0, "deliveryId": "string", "status": "received", "card": { "source": "github", "kind": "string", "title": "string", "summary": "string", "severity": "info", "url": "string", "subject": "string", "dedupeKey": "string", "labels": [ "string" ], "resolved": true }, "decision": { "action": "ignore", "reason": "string", "mode": "notify", "wanted": "notify", "sessionId": "f6567dd8-e069-418e-8893-7d22fcf12459" }, "sessionId": "f6567dd8-e069-418e-8893-7d22fcf12459", "attempts": 0, "error": "string", "receivedAt": "2019-08-24T14:15:22Z", "processedAt": "2019-08-24T14:15:22Z" } ]}id*stringTrigger UUID (the last segment of hook_url).
uuidtoken?stringgcp source only: the trigger secret, when the sender cannot set an Authorization header.
x-hub-signature-256?stringgithub source: sha256= followed by the hex HMAC-SHA256 of the raw body.
x-github-delivery?stringgithub source (required there): the delivery GUID, used as the idempotency key.
x-github-event?stringgithub source: the event type (issues, pull_request, workflow_run, …).
sentry-hook-signature?stringsentry source: hex HMAC-SHA256 of the raw body with the trigger secret.
sentry-hook-resource?stringsentry source: the resource the event is about (issue, event_alert, metric_alert, …).
request-id?stringsentry source: delivery id used as the idempotency key.
x-codai-signature?stringgeneric / form / manual sources: t=<unix>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">.
x-codai-delivery?stringgeneric / form / manual sources: optional idempotency key for the delivery.
application/json- body
The sender's own JSON payload. generic / form / manual senders may use title, message,
severity (info…critical), url, subject and kind to shape the event card.
[key: string]?anyAccepted (recorded; processed after the response).
application/json- response
accepted*truetrueduplicate*booleantrue when this delivery id was already recorded; it is not processed again.
event_id*integerId of the recorded delivery.
curl -X POST 'https://ai.codai.ro/v1/hooks/497f6eca-6276-4993-bfeb-53cbbbba6f08' \ -H 'Content-Type: application/json' \ -d '{}'{ "accepted": true, "duplicate": true, "event_id": 0}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
unread?"1"1 to return only unread notifications.
"1"limit?integerPage size, clamped to 1–200; anything non-numeric falls back to 50.
1 <= value <= 20050x-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128Notifications ordered by createdAt descending.
application/json- response
data*array<>curl -X GET 'https://ai.codai.ro/v1/notifications'{ "data": [ { "id": 0, "userId": "2c4a230c-5085-4924-a3e1-25fb4fc5965b", "kind": "string", "severity": "info", "title": "string", "body": "string", "url": "string", "sessionId": "f6567dd8-e069-418e-8893-7d22fcf12459", "triggerEventId": 0, "readAt": "2019-08-24T14:15:22Z", "createdAt": "2019-08-24T14:15:22Z" } ]}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
x-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128application/json- body
ids?array<>items <= 500How many notifications were updated.
application/json- response
read*integer0 <= valuecurl -X POST 'https://ai.codai.ro/v1/notifications/read' \ -H 'Content-Type: application/json' \ -d '{}'{ "read": 0}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
id*integer1 <= valuex-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128How many notifications were deleted (0 or 1).
application/json- response
deleted*integer0 <= valuecurl -X DELETE 'https://ai.codai.ro/v1/notifications/1'{ "deleted": 0}bearerAuthAuthorizationBearer <codai_…>A codai API key (codai_ prefix). Ephemeral tokens from POST /v1/tokens are accepted only by the media surfaces matching their scope (/v1/realtime, /v1/audio/*, /v1/embeddings).
x-request-id?stringClient correlation id; echoed back as x-codai-trace-id and persisted on the usage row.
length <= 128application/json- body
ids*array<>1 <= items <= 200How many notifications were deleted.
application/json- response
deleted*integer0 <= valuecurl -X POST 'https://ai.codai.ro/v1/notifications/delete' \ -H 'Content-Type: application/json' \ -d '{ "ids": [ 1 ]}'{ "deleted": 0}Last updated: