REST API
Overview
The base address is https://agendiq.app/api/v1. Every response is JSON with snake_case keys, ISO 8601 times in UTC and absolute links back into Agendiq. The API reads; it never changes anything, and it never returns join tokens, private host notes or how anyone voted.
A key reads what its owner can see: their own meetings, and their team's when the key was made inside a team by an owner or admin. The API and webhooks come with Pro and Team; Pro includes 2 keys and Team 10.
Authentication
Make a key in Integrations. Keys look like agq_ followed by 32 letters and digits, and are shown once: Agendiq keeps only a SHA-256 hash. Send the key as a bearer token on every request.
Authorization: Bearer agq_4hT9xQ2mLp0ZrB7vK3sN8wYc1dF6gJ5eRevoking a key in Integrations stops it at once. Keys stop working when the owner's plan no longer includes the API, and start again when it does.
Pagination
Lists come newest first, a page at a time, as { data, has_more, next_cursor }. Pass next_cursor back as cursor for the next page; when it is null you have everything. Cursors are opaque and stable: new items never shift a page you are in the middle of.
let cursor = null;
const all = [];
do {
const url = new URL("https://agendiq.app/api/v1/decisions");
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, { headers: { Authorization: `Bearer ${key}` } });
const page = await res.json();
all.push(...page.data);
cursor = page.next_cursor;
} while (cursor);Meetings
GET/api/v1/meetings
starts_at is the planned start in UTC (null until a date and time are set); start_date, start_time and time_zone are as the host set them.| Query | Meaning |
|---|---|
| status | planned, live or done. Default: all. |
| limit | Items per page, 1 to 100. Default 25. |
| cursor | next_cursor from the previous page. Leave it out for the first page. |
{
"data": [
{
"id": "Vx3kQ9aB2mPz",
"title": "Weekly product sync",
"status": "done",
"starts_at": "2026-10-06T06:30:00Z",
"start_date": "2026-10-06",
"start_time": "09:30",
"time_zone": "Europe/Riga",
"planned_minutes": 45,
"blocks_count": 6,
"score": 86,
"ended_on_time": true,
"series_id": "Vx3kQ9aB2mPz",
"team_id": "6f1c2b8e-3a4d-4c55-9e21-0b7a1d2c3e4f",
"source": null,
"url": "https://agendiq.app/m/Vx3kQ9aB2mPz",
"summary_url": "https://agendiq.app/m/Vx3kQ9aB2mPz/summary",
"created_at": "2026-10-01T09:12:44Z",
"updated_at": "2026-10-06T08:14:40Z"
}
],
"has_more": true,
"next_cursor": "WyIyMDI2LTEwLTAxVDA5OjEyOjQ0LjUxMiswMDowMCIsIlZ4M2tROWFCMm1QeiJd"
}GET/api/v1/meetings/{id}
{
"data": {
"id": "Vx3kQ9aB2mPz",
"title": "Weekly product sync",
"status": "done",
"starts_at": "2026-10-06T06:30:00Z",
"start_date": "2026-10-06",
"start_time": "09:30",
"time_zone": "Europe/Riga",
"planned_minutes": 45,
"blocks_count": 6,
"score": 86,
"ended_on_time": true,
"series_id": "Vx3kQ9aB2mPz",
"team_id": "6f1c2b8e-3a4d-4c55-9e21-0b7a1d2c3e4f",
"source": null,
"url": "https://agendiq.app/m/Vx3kQ9aB2mPz",
"summary_url": "https://agendiq.app/m/Vx3kQ9aB2mPz/summary",
"created_at": "2026-10-01T09:12:44Z",
"updated_at": "2026-10-06T08:14:40Z",
"goal": "Agree what ships next week",
"started_at": "2026-10-06T06:30:12Z",
"ended_at": "2026-10-06T07:14:40Z",
"in_short": "We ship the new onboarding on 14 October and pause the pricing test.",
"join_code": "K7QM4P",
"agenda": [
{
"id": "b1",
"kind": "checkin",
"title": "Check-in",
"minutes": 5,
"question": "One word for your week?"
},
{
"id": "b2",
"kind": "decide",
"title": "Onboarding: ship or hold?",
"minutes": 15,
"question": null
}
],
"decisions": [
{
"id": "c_4f1a9e",
"text": "Ship the new onboarding on 14 October",
"decided_at": "2026-10-06T06:52:03Z"
}
],
"actions": [
{
"id": "c_8b2d17",
"text": "Send the release notes to support",
"assignee": "Priya Shah",
"due": "Friday",
"created_at": "2026-10-06T06:58:44Z"
}
],
"invitees": [
{
"name": "Priya Shah",
"email": "priya@example.com"
}
]
}
}Actions
GET/api/v1/actions
assignee), the address it was matched to, and the due date as written (due) and as a day (due_on). status is open, done or dropped.| Query | Meaning |
|---|---|
| status | open, done or all. Default: all. |
| meeting_id | Only this meeting's actions. |
| limit | Items per page, 1 to 100. Default 25. |
| cursor | next_cursor from the previous page. Leave it out for the first page. |
{
"data": [
{
"id": "c_8b2d17",
"text": "Send the release notes to support",
"status": "open",
"assignee": "Priya Shah",
"assignee_email": "priya@example.com",
"due": "Friday",
"due_on": "2026-10-09",
"done_at": null,
"meeting_id": "Vx3kQ9aB2mPz",
"meeting_title": "Weekly product sync",
"meeting_url": "https://agendiq.app/m/Vx3kQ9aB2mPz/summary",
"created_at": "2026-10-06T07:58:44Z"
}
],
"has_more": true,
"next_cursor": "WyIyMDI2LTEwLTAxVDA5OjEyOjQ0LjUxMiswMDowMCIsIlZ4M2tROWFCMm1QeiJd"
}Decisions
GET/api/v1/decisions
| Query | Meaning |
|---|---|
| meeting_id | Only this meeting's decisions. |
| limit | Items per page, 1 to 100. Default 25. |
| cursor | next_cursor from the previous page. Leave it out for the first page. |
{
"data": [
{
"id": "c_4f1a9e",
"text": "Ship the new onboarding on 14 October",
"meeting_id": "Vx3kQ9aB2mPz",
"meeting_title": "Weekly product sync",
"meeting_url": "https://agendiq.app/m/Vx3kQ9aB2mPz/summary",
"decided_at": "2026-10-06T07:52:03Z"
}
],
"has_more": true,
"next_cursor": "WyIyMDI2LTEwLTAxVDA5OjEyOjQ0LjUxMiswMDowMCIsIlZ4M2tROWFCMm1QeiJd"
}Errors and limits
Errors come as { "error": { "code", "message" } } with the HTTP status. Each key may make 120 requests a minute; past that the API answers 429 with a Retry-After header in seconds.
| 400 | bad_cursor, bad_status, bad_meeting_id | A query value isn't valid. |
| 401 | unauthorized | No key, a malformed key, or a revoked one. |
| 402 | plan_required | The key owner's plan doesn't include the API. |
| 403 | forbidden | The key doesn't have the scope. |
| 404 | not_found | No such meeting, or not one this key can read. |
| 429 | rate_limited | Slow down; see Retry-After. |
| 503 | unavailable | Try again in a moment. |
Events
Webhooks
Add an endpoint in Integrations: a public https:// address and the events it should get. Agendiq sends each event as a JSON POST, signed with the endpoint's secret (shown once when you add it; make a new one any time). Use Send test event to see a real, signed delivery arrive.
POST /hooks/agendiq HTTP/1.1
Content-Type: application/json
User-Agent: Agendiq/1.0 (+https://agendiq.app/developers)
X-Agendiq-Event: decision.created
X-Agendiq-Delivery: 2b7c9d1e-5f60-4a8b-9c2d-3e4f5a6b7c8d
X-Agendiq-Signature: t=1791274481,v1=5f0c…e91aAnswer with any 2xx within 10 seconds. Anything else, or no answer, is retried after 1 min, 5 min, 30 min, 2 h, 12 h; after 6 attempts the delivery stops, and a 410 Gone stops it at once. Each event has a stable id: a retry carries the same one, so you can ignore repeats. Every endpoint hears its owner's meetings; one made inside a team by an owner or admin also hears the team's.
Event reference
Every body is an envelope with flat data, so each field maps straight into a spreadsheet column or a Zap step.
meeting.endedWhen a meeting ends, with its counts and the summary link.
{
"id": "evt_5d0c1f3a8b9e4d21",
"type": "meeting.ended",
"created_at": "2026-10-06T08:14:41Z",
"data": {
"meeting_id": "Vx3kQ9aB2mPz",
"meeting_title": "Weekly product sync",
"meeting_url": "https://agendiq.app/m/Vx3kQ9aB2mPz/summary",
"started_at": "2026-10-06T07:30:12Z",
"ended_at": "2026-10-06T08:14:40Z",
"duration_minutes": 44,
"planned_minutes": 45,
"ended_on_time": true,
"decisions_count": 2,
"actions_count": 3,
"polls_count": 1,
"in_short": "We ship the new onboarding on 14 October and pause the pricing test."
}
}decision.createdFor each decision captured in a meeting, once the meeting ends.
{
"id": "evt_5d0c1f3a8b9e4d21",
"type": "decision.created",
"created_at": "2026-10-06T08:14:41Z",
"data": {
"id": "c_4f1a9e",
"meeting_id": "Vx3kQ9aB2mPz",
"meeting_title": "Weekly product sync",
"meeting_url": "https://agendiq.app/m/Vx3kQ9aB2mPz/summary",
"text": "Ship the new onboarding on 14 October",
"decided_at": "2026-10-06T07:52:03Z"
}
}action.createdFor each action captured in a meeting, once the meeting ends.
{
"id": "evt_5d0c1f3a8b9e4d21",
"type": "action.created",
"created_at": "2026-10-06T08:14:41Z",
"data": {
"id": "c_8b2d17",
"meeting_id": "Vx3kQ9aB2mPz",
"meeting_title": "Weekly product sync",
"meeting_url": "https://agendiq.app/m/Vx3kQ9aB2mPz/summary",
"text": "Send the release notes to support",
"assignee": "Priya Shah",
"assignee_email": "priya@example.com",
"due": "Friday",
"due_on": "2026-10-09",
"created_at": "2026-10-06T07:58:44Z"
}
}action.doneWhen an action is marked done, in Agendiq or from a reminder email.
{
"id": "evt_5d0c1f3a8b9e4d21",
"type": "action.done",
"created_at": "2026-10-06T08:14:41Z",
"data": {
"id": "c_8b2d17",
"meeting_id": "Vx3kQ9aB2mPz",
"meeting_title": "Weekly product sync",
"meeting_url": "https://agendiq.app/m/Vx3kQ9aB2mPz/summary",
"text": "Send the release notes to support",
"assignee": "Priya Shah",
"assignee_email": "priya@example.com",
"due_on": "2026-10-09",
"done_at": "2026-10-08T15:02:10Z"
}
}action.overdueOnce, the day after an open action's due date.
{
"id": "evt_5d0c1f3a8b9e4d21",
"type": "action.overdue",
"created_at": "2026-10-06T08:14:41Z",
"data": {
"id": "c_8b2d17",
"meeting_id": "Vx3kQ9aB2mPz",
"meeting_title": "Weekly product sync",
"meeting_url": "https://agendiq.app/m/Vx3kQ9aB2mPz/summary",
"text": "Send the release notes to support",
"assignee": "Priya Shah",
"assignee_email": "priya@example.com",
"due_on": "2026-10-09",
"days_late": 1
}
}poll.closedFor each poll run in a meeting, once the meeting ends, with its results.
{
"id": "evt_5d0c1f3a8b9e4d21",
"type": "poll.closed",
"created_at": "2026-10-06T08:14:41Z",
"data": {
"id": "p_31c0aa",
"meeting_id": "Vx3kQ9aB2mPz",
"meeting_title": "Weekly product sync",
"meeting_url": "https://agendiq.app/m/Vx3kQ9aB2mPz/summary",
"question": "Are we ready to ship on 14 October?",
"kind": "yesno",
"total_answers": 9,
"top_option": "Yes",
"results": "Yes: 7; No: 2",
"closed_at": "2026-10-06T08:01:30Z"
}
}Verifying signatures
The X-Agendiq-Signature header is t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of t, a full stop and the raw request body, keyed with your endpoint's secret. Compute it over the body exactly as received, compare in constant time, and refuse timestamps older than five minutes.
import crypto from "node:crypto";
// rawBody: the request body exactly as received (a string or Buffer, not parsed JSON).
export function verifyAgendiq(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(",").map((kv) => kv.trim().split("=")));
const t = Number(parts.t);
if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1 ?? "");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Express: keep the raw body for the signature.
app.post("/hooks/agendiq", express.raw({ type: "application/json" }), (req, res) => {
const ok = verifyAgendiq(req.body.toString("utf8"), req.get("X-Agendiq-Signature") ?? "", process.env.AGENDIQ_WEBHOOK_SECRET);
if (!ok) return res.status(400).end();
const event = JSON.parse(req.body);
// event.type, event.data …
res.status(200).end();
});import hashlib, hmac, time
def verify_agendiq(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(kv.strip().split("=", 1) for kv in header.split(",") if "=" in kv)
try:
t = int(parts.get("t", ""))
except ValueError:
return False
if abs(time.time() - t) > tolerance:
return False
signed = f"{t}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))
# Flask
@app.post("/hooks/agendiq")
def agendiq_hook():
if not verify_agendiq(request.get_data(), request.headers.get("X-Agendiq-Signature", ""), os.environ["AGENDIQ_WEBHOOK_SECRET"]):
abort(400)
event = request.get_json()
return "", 200Keep the raw body
Zapier and Make
Zapier: use Webhooks by Zapier with Catch Hook, paste its address as an endpoint in Agendiq, and press Send test event. Every field of data appears as its own value to map, for example a new row in Google Sheets per decision.
Make: add a Custom webhook module, paste its address into Agendiq, and send a test event so Make learns the structure. To pull instead of push, use an HTTP module with your key in the Authorization header against /api/v1/actions?status=open.