Meeting Summaries API

Read-only JSON API over the SingularityNET Ambassador Program meeting archive. Every endpoint supports filtering, sorting and pagination.

Base URL: https://archives-dashboard.netlify.app/api/v1

Authentication

Every request needs the server API key. Send it in one of these ways:

x-api-key: <key>
Authorization: Bearer <key>

The legacy api_key header is also accepted. The key is the value of the SERVER_API_KEY environment variable on the server. It is a secret: never embed it in client-side code.

StatusCodeMeaning
401missing_api_keyNo key was sent
403invalid_api_keyThe key does not match
500server_misconfiguredSERVER_API_KEY is not set on the server

Note that the dashboard pages (/search, /charts) render the same data without a key. The key protects the API surface, not the dataset itself.

The dashboard's own /api/search endpoint is internal: it is unversioned, same-origin only, needs no key, and is not part of the /api/v1 contract.

Response shape

Successful responses:

{
  "data": [ ... ],
  "meta": {
    "total": 1234,
    "limit": 50,
    "offset": 0,
    "hasMore": true,
    "loadedAt": "2026-09-25T10:15:00.000Z"
  }
}

loadedAt is when the server last loaded the dataset from the database. The server caches the dataset in memory for five minutes.

Errors:

{ "error": { "code": "invalid_param", "message": "limit must be an integer between 1 and 500" } }
StatusCode
400invalid_param
404not_found
405method_not_allowed
500internal_error

Only GET and OPTIONS are allowed. CORS is open (*). Responses are sent with Cache-Control: private, no-store.

Meeting summary object

/meetings returns rows in this shape. Fields inside summary are only present when the documenter filled them in, so treat everything except workgroup, workgroup_id, meetingInfo, type, canceledSummary and noSummaryGiven as optional. Even agendaItems is absent on cancelled or no-summary meetings. The example below shows every field observed across the archive; a typical row has far fewer.

{
  "meeting_id": "ff4d5ef9-3354-4192-86b9-e5e66601cc8f",
  "created_at": "2026-01-12T19:51:16.20138+00:00",
  "updated_at": "2026-02-10T13:29:25.625+00:00",
  "confirmed": true,
  "workgroup_id": "649a933f-3067-468f-891e-dbe47adcb88a",
  "name": "Monthly",
  "date": "2026-01-12T00:00:00",
  "summary": {
    "workgroup": "AI Ethics WG",
    "workgroup_id": "649a933f-3067-468f-891e-dbe47adcb88a",
    "type": "custom",
    "meetingInfo": {
      "name": "Monthly",
      "date": "2026-01-12",
      "host": "CallyFromAuron",
      "documenter": "CallyFromAuron",
      "peoplePresent": "CallyFromAuron, UKnowZork, PeterE",
      "purpose": "Monthly meeting of the AI Ethics Workgroup",
      "workingDocs": [
        { "title": "AI Ethics WorkGroup Github board", "link": "https://github.com/..." }
      ],
      "meetingVideoLink": "https://...",
      "miroBoardLink": "https://...",
      "otherMediaLink": "https://...",
      "transcriptLink": "https://...",
      "mediaLink": "https://...",
      "googleSlides": "https://...",
      "townHallNumber": "42",
      "timestampedVideo": {
        "url": "https://...",
        "intro": "Opening remarks",
        "timestamps": "00:00 Intro\n05:12 Updates",
        "sections": [ { "title": "Intro", "content": "..." } ]
      }
    },
    "agendaItems": [
      {
        "agenda": "Unconference recordings",
        "status": "carry over",
        "narrative": "Free-text summary of the discussion, may contain markdown",
        "discussion": "Older free-text field, rarely used",
        "discussionPoints": [ "Point one", "Point two" ],
        "meetingTopics": [ "Topic" ],
        "issues": [ "Issue" ],
        "learningPoints": [ "Lesson" ],
        "townHallUpdates": "...",
        "townHallSummary": "...",
        "gameRules": "...",
        "actionItems": [
          {
            "text": "Vani and Tina to work on the AI And Ecology session",
            "assignee": "CallyFromAuron, Tina Shriver",
            "dueDate": "26 January 2026",
            "status": "in progress"
          }
        ],
        "decisionItems": [
          {
            "decision": "Introduction to qualitative coding session with Esther",
            "rationale": "Why the decision was taken",
            "opposing": "Arguments raised against it, if any",
            "effect": "mayAffectOtherPeople"
          }
        ]
      }
    ],
    "tags": {
      "topicsCovered": "interview transcription, interviews, transcription backlog",
      "emotions": "forward-looking, organised, quiet",
      "gamesPlayed": "Only used by the gaming workgroups",
      "other": "..."
    },
    "noSummaryGiven": false,
    "noSummaryGivenText": "No Summary Given",
    "canceledSummary": false,
    "canceledSummaryText": "Meeting was cancelled"
  }
}

Field notes:

FieldNotes
meeting_idStable unique id. Use it with /meetings/{meeting_id}.
confirmedtrue for archived summaries. false rows are recent drafts with no archived version yet.
dateRow-level timestamp of the meeting date. Prefer summary.meetingInfo.date, which is a plain YYYY-MM-DD.
summary.typeSummary template. Currently custom, though casing varies (Custom also occurs). The type filter is case-insensitive.
summary.meetingInfo.peoplePresentComma-separated names in one string.
summary.meetingInfo.workingDocsPresent on most rows. Links, slides and video fields are present only when provided.
summary.agendaItemsAbsent when canceledSummary or noSummaryGiven is true.
summary.agendaItems[].statuscarry over or resolved.
summary.agendaItems[].actionItems[].textOccasionally missing. /action-items skips items with no text.
summary.agendaItems[].actionItems[].assigneeComma-separated names in one string. The assignee filter matches any one of them.
summary.agendaItems[].actionItems[].dueDateHuman-readable, for example 26 January 2026. Not ISO. Use the due* filters on /action-items for date comparisons.
summary.agendaItems[].actionItems[].statusLower-case, for example todo, in progress, done, cancelled. /facets lists the values in use.
summary.agendaItems[].decisionItems[].effectaffectsOnlyThisWorkgroup or mayAffectOtherPeople, or absent.
summary.agendaItems[].decisionItems[].rationale / opposingOptional free text.
summary.tagsAbsent on some rows. Each value is a comma-separated string, not an array. topicsCovered and emotions are the common ones; gamesPlayed and other are rare. The tag filter matches any single entry across all of them.
summary.canceledSummary / noSummaryGivenWhen true, the meeting was cancelled or no summary was written and agendaItems will be empty.

Templates vary by workgroup

Each workgroup writes its summaries from its own meeting template, so which agendaItems and meetingInfo fields are filled in depends on the workgroup more than on the meeting. Do not assume a field is present because another workgroup's rows have it. The main patterns in the archive:

FieldsUsed by
actionItems, decisionItems, discussionPoints, tags.topicsCovered, tags.emotions, workingDocsMost workgroups. This is the common core.
agendaItems[].agenda, meetingInfo.miroBoardLinkTreasury Guild, Treasury Automation WG, Treasury Policy WG, Knowledge Base Workgroup, Dework PBL, GitHub PBL WG. These groups structure the summary by agenda item title.
agendaItems[].narrativeAfrican Guild, AI Ethics WG, Governance Workgroup, LatAm Guild, Strategy Guild, Writers Workgroup. A free-text write-up instead of, or alongside, bullet points.
agendaItems[].townHallSummary, meetingInfo.townHallNumber, meetingInfo.timestampedVideoAmbassador Town Hall and Deep Funding Town Hall. Town halls are recorded, so most rows carry a video URL with timestamped sections.
agendaItems[].gameRules, tags.gamesPlayedGamers Guild only.
agendaItems[].meetingTopicsResearch and Development Guild, Education Workgroup, WG Sync Call.
agendaItems[].issues, agendaItems[].learningPoints, agendaItems[].townHallUpdatesOnboarding Workgroup, occasionally Writers Workgroup.
agendaItems[].discussionWG Sync Call only. An older free-text field.
meetingInfo.meetingVideoLinkArchives Workgroup on almost every row; scattered elsewhere.
tags.otherRare, mostly LatAm Guild.

meetingInfo.name is the meeting cadence rather than a title: Weekly, Biweekly, Monthly or One-off event. A few workgroups use their own labels such as Think-Tank or Sandbox.

When writing a client, read the fields you need with a fallback for absence, and use the q search (which looks across every text field) rather than targeting one field if you want results from all workgroups.

/action-items and /decisions return the nested items flattened into one row each, with workgroup, workgroup_id, meeting_id and the meeting date added. See those sections for their fields.

Common parameters

These apply to /meetings, /action-items and /decisions.

ParamTypeDescription
qstringCase-insensitive substring search. See each endpoint for the fields searched.
workgroupstringMatches the workgroup_id exactly, or the workgroup name case-insensitively. Call /api/v1/facets for the list of valid ids and names.
dateYYYY-MM-DDExact meeting date.
dateFromYYYY-MM-DDInclusive lower bound on the meeting date.
dateToYYYY-MM-DDInclusive upper bound on the meeting date.
sortenumSort field. Options differ per endpoint.
orderasc | descSort direction. Default desc.
limitintegerPage size, 1 to 500. Default 50.
offsetintegerNumber of items to skip. Default 0.

Unknown parameters are ignored. Invalid values return 400.

GET /api/v1/meetings

Returns full meeting summary rows (meeting_id, created_at, updated_at, confirmed, workgroup_id, name, date, and the summary JSON with agenda items, action items, decisions, meeting info and tags).

ParamDescription
qSearches tags and content: workgroup, meeting name, purpose, host, documenter, people present, agenda narratives, discussion points, topics, issues, learning points, action item text, decisions, and timestamped video notes. When set, each result includes matches: { tags, content } with the number of hits.
tagCase-insensitive match against any tag in topicsCovered, emotions or other.
typeCase-insensitive match on summary.type.
confirmedtrue or false. Confirmed rows are archived summaries. Unconfirmed rows are drafts from the last three months with no archived version.
hostCase-insensitive match on the meeting host.
assigneeMeetings containing at least one action item assigned to this person.
sortdate (default) or updated_at.
curl -H "x-api-key: $KEY" \
  "https://archives-dashboard.netlify.app/api/v1/meetings?q=treasury&workgroup=Archives%20Workgroup&dateFrom=2025-01-01&limit=10"

GET /api/v1/meetings/{meeting_id}

Returns a single meeting summary in data. 404 if the id is unknown.

curl -H "x-api-key: $KEY" "https://archives-dashboard.netlify.app/api/v1/meetings/<meeting_id>"

GET /api/v1/action-items

Returns action items flattened out of every meeting. Each item has text, assignee, dueDate, status, workgroup, workgroup_id, meeting_id and meetingDate.

ParamDescription
qSearches text, assignee, workgroup and status.
statusCase-insensitive match, for example in progress.
assigneeCase-insensitive exact match against any name in a comma-separated assignee list.
dueExact due date, YYYY-MM-DD.
dueFromInclusive lower bound on the due date.
dueToInclusive upper bound on the due date.
sortdueDate (default) or date (meeting date).

Items with no due date are excluded whenever a due* filter is set.

curl -H "x-api-key: $KEY" \
  "https://archives-dashboard.netlify.app/api/v1/action-items?status=in%20progress&dueFrom=2025-01-01&sort=dueDate&order=asc"

GET /api/v1/decisions

Returns decisions flattened out of every meeting. Each has decision, effect, rationale, opposing, workgroup, workgroup_id, date and meeting_id. meta.stats carries totals over the whole filtered set:

"stats": { "total": 42, "withRationale": 30, "withEffect": 40, "rationalePercentage": 71, "effectPercentage": 95 }
ParamDescription
qSearches decision, effect, rationale and workgroup.
effectExact match, usually affectsOnlyThisWorkgroup or mayAffectOtherPeople.
sortdate only.
curl -H "x-api-key: $KEY" \
  "https://archives-dashboard.netlify.app/api/v1/decisions?effect=mayAffectOtherPeople&q=budget"

GET /api/v1/facets

Returns the distinct values available for each filter, with counts, so a client can build dropdowns. workgroups lists every workgroup with its id and display name; either can be used as the workgroup filter on the other endpoints, and names are matched case-insensitively. statuses, assignees, effects, tags and types give the valid values for those filters.

{
  "data": {
    "workgroups": [ { "id": "...", "name": "Archives Workgroup", "count": 120 } ],
    "statuses":   [ { "value": "in progress", "label": "In Progress", "count": 15 } ],
    "assignees":  [ { "value": "jane", "label": "Jane", "count": 8 } ],
    "effects":    [ { "value": "mayAffectOtherPeople", "label": "mayAffectOtherPeople", "count": 40 } ],
    "tags":       [ { "value": "governance", "label": "governance", "count": 33 } ],
    "types":      [ { "value": "custom", "label": "Custom", "count": 200 } ]
  },
  "meta": { "loadedAt": "..." }
}
# list the workgroup names, then filter by one (URL-encode spaces)
curl -H "x-api-key: $KEY" "https://archives-dashboard.netlify.app/api/v1/facets"
curl -H "x-api-key: $KEY" "https://archives-dashboard.netlify.app/api/v1/meetings?workgroup=Archives%20Workgroup"

Fetching everything

Page size is capped at 500. To download the whole archive, step offset by limit until meta.hasMore is false:

curl -H "x-api-key: $KEY" "https://archives-dashboard.netlify.app/api/v1/meetings?limit=500&offset=0"
curl -H "x-api-key: $KEY" "https://archives-dashboard.netlify.app/api/v1/meetings?limit=500&offset=500"
# continue while meta.hasMore is true

meta.total on the first response tells you how many pages to expect. Add confirmed=true to fetch only archived summaries.

With 1003 meetings in the archive, the first page looks like this (summary objects abbreviated):

{
  "data": [
    {
      "meeting_id": "ff4d5ef9-3354-4192-86b9-e5e66601cc8f",
      "created_at": "2026-01-12T19:51:16.20138+00:00",
      "updated_at": "2026-02-10T13:29:25.625+00:00",
      "confirmed": true,
      "workgroup_id": "649a933f-3067-468f-891e-dbe47adcb88a",
      "name": "Monthly",
      "date": "2026-01-12T00:00:00",
      "summary": { "workgroup": "AI Ethics WG", "meetingInfo": { "date": "2026-01-12" }, "agendaItems": [ ... ] }
    },
    {
      "meeting_id": "0a1b2c3d-...",
      "confirmed": true,
      "workgroup_id": "2d56e7d8-52ba-4be0-837f-a9a14ae4dc6e",
      "name": "Weekly",
      "date": "2026-01-10T00:00:00",
      "summary": { "workgroup": "African Guild", "meetingInfo": { "date": "2026-01-10" }, "agendaItems": [ ... ] }
    }
    // ... 498 more
  ],
  "meta": { "total": 1003, "limit": 500, "offset": 0, "hasMore": true, "loadedAt": "2026-09-25T06:55:47.075Z" }
}

The second page (offset=500) has the same shape with "offset": 500 and "hasMore": true. The third (offset=1000) returns the last three rows:

{
  "data": [ { ... }, { ... }, { ... } ],
  "meta": { "total": 1003, "limit": 500, "offset": 1000, "hasMore": false, "loadedAt": "2026-09-25T06:55:47.075Z" }
}

Concatenating the three data arrays gives all 1003 meetings, ordered by meeting date, newest first. Each item is the object described under "Meeting summary object" above. The same applies to /action-items and /decisions.