Skip to main content
POST /meeting-notes Send a note-taker’s output — raw transcript, summary, AI-enriched recap, action items — against an existing meeting. Designed for webhooks from Fathom, Granola, Otter, Zoom AI Companion, or any in-house note-taker flow (Make / Zapier / custom). The note attaches to a calendar_event that already exists in Nexor. No orphan notes in v1 — if no meeting resolves, you get a 404.

Meeting resolution

The endpoint tries the following in order, stopping at the first match:
  1. calendar_event_id — direct.
  2. external_calendar_provider + external_calendar_event_id — matches meetings created via POST /meetings with those dedup keys.
  3. meeting_url — exact match on the calendar_event’s meeting URL.
  4. lead_email + meeting_date — lead’s meeting within ±60 minutes of meeting_date.
  5. lead_email alone — the lead’s most recent past meeting. No time-window floor.
If none of these resolve a meeting → 404. Create the meeting first via POST /meetings or pass a valid identifier.

Idempotency

If you supply external_meeting_id together with a provider, replays of the same (provider, external_meeting_id) under your client update the existing row instead of creating duplicates. Safe for webhook retries. The timeline note_added activity fires only on the first insert, not on replays.

Reserved providers

The following values are rejected to protect internal sync pipelines: fireflies, diio, gemini, retell. Pick any other lowercase alphanumeric string (e.g. fathom, granola, otter, manual, zoom_ai). Regex: ^[a-z0-9_-]{1,64}$.

Request Body

All fields are optional unless noted. At minimum you must provide:
  • one way to resolve a meeting (calendar_event_id, external_calendar_event_id+external_calendar_provider, meeting_url, or lead_email), and
  • one non-empty content field (notes, transcript_text, summary, manual_ai_summary, or action_items).
FieldTypeDescription
providerstringSource of the note. Default "manual". Reserved names rejected.
external_meeting_idstringStable id from the note-taker (e.g. Fathom Recording ID). Enables idempotent replay. ≤ 255 chars.
calendar_event_idstring (uuid)Direct link — tier 1 resolution.
external_calendar_providerstringPair with external_calendar_event_id — tier 2.
external_calendar_event_idstringProvider’s event id from POST /meetings.
meeting_urlstringMeet / Zoom / Fathom URL — tier 3.
lead_emailstringMatches the lead (client-scoped); also drives tiers 4 & 5.
meeting_datestring (ISO 8601)Used with lead_email for ±60 min fuzzy match.
meeting_titlestring
duration_secondsinteger
organizer_emailstringHost email.
notesstringRaw freeform text. Alias for transcript_text — if both are provided, transcript_text wins. ≤ 1 MB.
transcript_textstringFull transcript. ≤ 1 MB.
summarystringNotetaker’s native summary (Fathom / Granola / Otter recap).
manual_ai_summarystringAI-enriched custom summary (e.g. downstream ChatGPT step in Make). Stored separately from summary.
action_itemsarray["Send pricing", ...] or [{ text, assignee }, ...]. ≤ 512 KB.
questionsarray["What's the monthly cost?", ...]. ≤ 512 KB.
keywordsarray["pricing", "enterprise", ...]. ≤ 512 KB.
sentencesarraySpeaker-level utterances (Fireflies-compatible shape). ≤ 512 KB.
statusstringcompleted (default), pending, or failed.

Request Body — minimal freeform

Ideal for Make / Zapier scenarios that build a markdown blob and attach it to the lead’s most recent meeting:
{
  "lead_email": "tony.stark@gmail.com",
  "notes": "Revisamos propuesta. Cliente preguntó por plazos de implementación. Budget Q2 aprobado.",
  "manual_ai_summary": "Alta intención. Enterprise. Siguiente paso: propuesta formal antes del viernes."
}

Request Body — Fathom webhook via Make

Using Fathom’s “Watch New Recordings” trigger output mapped to API fields:
{
  "provider": "fathom",
  "external_meeting_id": "133148102",
  "meeting_url": "https://fathom.video/call/133148102",
  "meeting_title": "Discovery — Stark Industries",
  "meeting_date": "2026-04-15T17:18:02Z",
  "organizer_email": "pepper@starkindustries.com",
  "lead_email": "tony.stark@gmail.com",
  "summary": "## Deal Information\n### Prospect\n[...Fathom markdown summary...]",
  "manual_ai_summary": "ChatGPT-enriched recap: strong buying intent, Q2 budget confirmed.",
  "action_items": ["Send pricing by Friday", "Intro CTO for technical deep-dive"]
}
Resolving lead_email in Make: Fathom’s is_external flag is unreliable (the host sometimes gets flagged external). Filter Calendar Invitees[] by domain instead:
Array filter:
  Input:      Calendar Invitees[]
  Condition:  email_domain  ≠  {{Recorded By.email_domain}}

Then:
  lead_email = {{first(filtered).Email}}

Example Request

curl -X POST https://api.getnexor.ai/api/public/meeting-notes \
  -H "X-API-Key: nxr_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "lead_email": "tony.stark@gmail.com",
    "notes": "Revisamos propuesta. Budget Q2 aprobado. Interés Enterprise.",
    "manual_ai_summary": "Alta intención. Siguiente paso: propuesta formal antes del viernes."
  }'

Response — 201 Created (or 200 on replay upsert)

{
  "success": true,
  "upserted": false,
  "meeting_note": {
    "id": "0f9230e0-a5e3-4245-8995-afc61007c992",
    "client_id": "2c0911d1-25f9-47c2-8795-fe9407b8d67e",
    "calendar_event_id": "5a76ac77-523d-4100-843c-1105212de877",
    "lead_id": "0433aeee-44af-4148-9ca0-9e2ac41501a7",
    "provider": "manual",
    "external_meeting_id": null,
    "meeting_title": null,
    "meeting_date": "2025-12-29T17:00:00+00:00",
    "duration_seconds": null,
    "organizer_email": null,
    "summary": null,
    "manual_ai_summary": "Alta intención. Siguiente paso: propuesta formal antes del viernes.",
    "transcript_text": "Revisamos propuesta. Budget Q2 aprobado. Interés Enterprise.",
    "action_items": null,
    "questions": null,
    "keywords": null,
    "status": "completed",
    "created_at": "2026-04-15T20:52:35.426356+00:00",
    "updated_at": "2026-04-15T20:52:35.426356+00:00"
  }
}
upserted is true when a prior row with the same (provider, external_meeting_id) was updated instead of a new row being inserted.

Timeline

On first insert, a note_added activity is written to the lead’s timeline with a link to the created meeting_transcripts row. Replays do not emit duplicate activities.

Error Codes

CodeMeaning
400Missing or invalid field; reserved provider; payload over size limits; no content fields; nothing to resolve the meeting with
401Missing or invalid X-API-Key
404No calendar_event resolved via any of the 5 strategies
409(provider, external_meeting_id) already bound to a different calendar_event
500Unexpected server error
Last modified on June 18, 2026