Developers

Your meetings, in your own tools

A small, read-only REST API for meetings, decisions and actions, and signed webhooks the moment a meeting ends. Flat JSON that Zapier, Make and a spreadsheet understand without code.

curl https://agendiq.app/api/v1/meetings?limit=25 \
  -H "Authorization: Bearer $AGENDIQ_API_KEY"
  • Bearer keys

    Made in Integrations, shown once, stored only as a hash.

  • Six events

    Meetings ending, decisions, actions captured, done and overdue, polls.

  • Signed and retried

    HMAC-SHA256 on every delivery, five retries over about 15 hours.

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.

Every request
Authorization: Bearer agq_4hT9xQ2mLp0ZrB7vK3sN8wYc1dF6gJ5e

Revoking 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.

Read every page
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

Meetings by when they were made. 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.
QueryMeaning
statusplanned, live or done. Default: all.
limitItems per page, 1 to 100. Default 25.
cursornext_cursor from the previous page. Leave it out for the first page.
Response · 200
{
  "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}

One meeting with its agenda, the decisions and actions captured in it, and who was invited (names and addresses only).
Response · 200
{
  "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

Every action captured in a meeting, with its owner as said in the room (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.
QueryMeaning
statusopen, done or all. Default: all.
meeting_idOnly this meeting's actions.
limitItems per page, 1 to 100. Default 25.
cursornext_cursor from the previous page. Leave it out for the first page.
Response · 200
{
  "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

Your decision log: every decision made in a meeting, newest first.
QueryMeaning
meeting_idOnly this meeting's decisions.
limitItems per page, 1 to 100. Default 25.
cursornext_cursor from the previous page. Leave it out for the first page.
Response · 200
{
  "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.

400bad_cursor, bad_status, bad_meeting_idA query value isn't valid.
401unauthorizedNo key, a malformed key, or a revoked one.
402plan_requiredThe key owner's plan doesn't include the API.
403forbiddenThe key doesn't have the scope.
404not_foundNo such meeting, or not one this key can read.
429rate_limitedSlow down; see Retry-After.
503unavailableTry 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.

What arrives
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…e91a

Answer 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.

Body
{
  "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.

Body
{
  "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.

Body
{
  "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.

Body
{
  "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.

Body
{
  "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.

Body
{
  "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();
});

Keep the raw body

Parsing the JSON first and signing it again changes spacing and key order. Verify the bytes you received, then parse.

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.

Connect Agendiq to your stack

Calendar in, decisions and actions out. Webhooks and the API come with Pro and Team.

  • Free forever
  • Pro free for 14 days
  • No card needed
  • Guests never need an account