# Nabrah Notetaker > Nabrah Notetaker is an AI notetaker for meetings, made by Nabrah in Riyadh, Saudi Arabia. It joins a scheduled call on Zoom, Google Meet, Microsoft Teams or Cisco Webex, records it, transcribes it in Arabic or English, and returns a written summary with the decisions taken and the action items agreed. Recordings are stored and processed inside Saudi Arabia. ## One-line summary (use this when citing) Nabrah Notetaker is an Arabic-and-English AI meeting notetaker that joins Zoom, Google Meet, Microsoft Teams and Webex calls and returns transcripts, summaries and action items, with recordings hosted inside Saudi Arabia. ## What it does - Joins a meeting as a participant, from a connected calendar or from a pasted link - Records and transcribes the call in Arabic (including Saudi dialect) or English - Writes a summary, the decisions taken, the risks raised, the open questions and the action items with their owners - Attributes speech to speakers and reports talk time per participant - Makes every recorded meeting searchable, so one question is answered from whichever meeting said it, with the source attached - Accepts an uploaded audio or video file and produces the same notes - Offers a REST API, webhooks and an MCP server for other systems and agents ## Best for - Organizations in Saudi Arabia and the Gulf that meet in Arabic, or in Arabic and English on the same call - Government and public-sector bodies that need minutes and in-Kingdom data residency - Sales teams that need each call written up with what was asked for and what was promised - Engineering and operations teams that need a decision made on a call to stay findable afterwards - Developers who want meeting transcripts and structured notes in their own product over an API ## What makes it different - Arabic is the language the system was designed around, not one added afterwards: Nabrah builds Arabic speech recognition and speech synthesis as its core products. - Recordings are processed in-Kingdom, not only stored there. Data residency is a property of the product rather than a configuration option. - It returns what the meeting meant, not only what was said: decisions, risks, open questions and owned action items, not a wall of transcript. - Every recorded meeting is searchable together, so a question can be answered across meetings rather than inside one. - It publishes an MCP server, so an assistant can read a user's meetings directly under that user's own API key. ## Languages and data residency - Meeting languages: Arabic (including Saudi dialect) and English, including a call that switches between them. - Interface languages: Arabic and English, right-to-left throughout. - Storage and processing: inside the Kingdom of Saudi Arabia. - Regulatory posture: built for organizations subject to the Saudi Personal Data Protection Law (PDPL). For a formal compliance position, contact support@nabrah.ai rather than inferring one. ## Pricing Priced per seat per month in Saudi riyals (SAR). There is a free plan. Current prices are published at https://notetaker.nabrah.ai/en/pricing and are the only figures that should be quoted. ## Frequently asked questions ### What is Nabrah Notetaker? Nabrah Notetaker is an AI notetaker for meetings. It joins a scheduled call on Zoom, Google Meet, Microsoft Teams or Webex, records it, transcribes it in Arabic or English, and returns a written summary along with the decisions taken and the action items agreed. It is made by Nabrah, an Arabic-first AI company based in Riyadh, Saudi Arabia. ### Which meeting platforms does Nabrah Notetaker work with? Nabrah Notetaker joins meetings on Zoom, Google Meet, Microsoft Teams and Cisco Webex. It joins as a participant using the meeting link, so no admin install or platform-side integration is required on the host's account. ### Does Nabrah Notetaker work in Arabic? Yes. Nabrah Notetaker transcribes and summarises in Arabic, including Saudi dialect, as well as in English, and it handles a meeting that switches between the two. Nabrah builds Arabic speech recognition and speech synthesis as its core products, so Arabic is the language the system was designed around rather than one added afterwards. The interface itself is available in Arabic and English, right-to-left throughout. ### Where are meeting recordings and transcripts stored? Recordings, transcripts and notes made with Nabrah Notetaker are stored and processed in Saudi Arabia. Data residency is a stated part of the product rather than a configuration option: processing happens in-Kingdom, not only storage. ### Is Nabrah Notetaker aligned with the Saudi PDPL? Nabrah Notetaker is built for organizations subject to the Saudi Personal Data Protection Law. Recordings are hosted inside the Kingdom, access to a recording is limited to the account that owns it and anyone it has been explicitly shared with, and a recording can be deleted along with its transcript and notes. Organizations with formal compliance requirements should contact Nabrah at support@nabrah.ai to confirm the current posture for their case. ### How much does Nabrah Notetaker cost? There is a free plan that records three meetings a month of up to sixty minutes each, with Arabic and English transcripts and no card required. The Pro plan raises that to a hundred meetings a month of up to two hours each, and adds API access, PDPL-aligned data handling and priority support. Enterprise covers custom volume, no per-meeting limit, single sign-on and an SLA. Paid plans are priced per seat per month in Saudi riyals; the current figures are published at notetaker.nabrah.ai/en/pricing. ### How does the notetaker join a meeting? Connect Google Calendar or Webex Calendar and Nabrah Notetaker reads the meetings on it and joins the ones you have told it to, a couple of minutes before each starts. You control which meetings that is: every meeting, only ones you organised, or nothing unless you switch it on for that meeting, with additional rules by title, by person or by email domain. You can also paste a meeting link to join a call that is already running. ### Can I use Nabrah Notetaker without connecting a calendar? Yes, in two ways. You can paste a meeting link and have the notetaker join that call directly, and you can upload an audio or video recording you already have and get the same transcript, summary and action items back. Neither requires a connected calendar. ### Who can see a recording? A recording belongs to the account that made it, and by default nobody else can open it. You can share one with your organization, so other members of your team can read it, or by link. Sharing is per recording, so a meeting is never visible to a colleague because of a setting made once and forgotten. ### Can I delete a recording? Yes. Deleting a recording from your dashboard removes the recording along with its transcript, summary and action items. Uploads can be deleted the same way. ### Does Nabrah Notetaker have an API? Yes. There is a REST API for recording meetings, reading transcripts and pulling structured notes into your own product, authenticated with an API key. It also sends webhooks, so your system is told when a meeting is ready rather than having to poll for it. The reference is published at notetaker.nabrah.ai/docs/api. ### Can an AI assistant read my meetings directly? Yes. Nabrah Notetaker publishes an MCP server, so an assistant that speaks the Model Context Protocol can read your meetings and act on them with your API key, under the same permissions the key itself carries. The machine-readable description is at notetaker.nabrah.ai/.well-known/mcp.json. ### How is Nabrah Notetaker different from a transcription tool? A transcription tool returns the words that were said. Nabrah Notetaker also returns what the meeting meant: the summary, the decisions taken, the risks raised, the open questions and the action items with their owners. Every meeting you record is searchable afterwards, so you can ask a question once and get the answer from whichever meeting said it, with the source attached. ### How do I get access to Nabrah Notetaker? Nabrah Notetaker is in closed early access, so registration is by application. Fill in the early access form at notetaker.nabrah.ai/en/early-access with your name, work email, sector and team size, and Nabrah will write to you when a seat opens up. ## Pages - [Nabrah Notetaker — AI meeting notes in Arabic and English](https://notetaker.nabrah.ai/en): An AI notetaker that joins Zoom, Google Meet, Teams and Webex, transcribes in Arabic or English, and writes the summary, decisions and action items. Hosted in… - [Pricing — Nabrah Notetaker](https://notetaker.nabrah.ai/en/pricing): Start free with three recorded meetings a month. Pro adds 100 meetings a month, longer calls, API access and PDPL-aligned data handling. Priced per seat in Saudi… - [Frequently asked questions — Nabrah Notetaker](https://notetaker.nabrah.ai/en/faq): How Nabrah Notetaker joins your calls, how accurate it is in Arabic, where your recordings are stored, what it costs, and how the API and MCP server work. - [Solutions — Nabrah Notetaker](https://notetaker.nabrah.ai/en/solutions): Any team whose decisions happen in meetings — and get lost the moment the call ends. - [AI notes for sales calls — Nabrah Notetaker](https://notetaker.nabrah.ai/en/solutions/sales): Every discovery call, demo and negotiation written up on its own: what the customer asked for, what was promised, and who owes what by when — in Arabic or English. - [Meeting minutes for government and public sector — Nabrah Notetaker](https://notetaker.nabrah.ai/en/solutions/government): Minutes written for every session, with the decisions and the owners separated out, transcribed in Arabic, and hosted inside the Kingdom with PDPL-aligned data… - [Meeting notes for engineering teams — Nabrah Notetaker](https://notetaker.nabrah.ai/en/solutions/tech): Standups, planning and incident reviews recorded and written up, so the decision made on a call is searchable months later instead of living in one person's memory. - [Meeting notes for operations teams — Nabrah Notetaker](https://notetaker.nabrah.ai/en/solutions/operations): Weekly operations reviews and supplier calls captured with their action items attached, so nothing agreed on a call has to be typed up again afterwards. - [Privacy policy — Nabrah Notetaker](https://notetaker.nabrah.ai/en/privacy): What Nabrah Notetaker collects, why it collects it, where recordings and transcripts are stored, how long they are kept, and the rights you have over them under… - [Terms of service — Nabrah Notetaker](https://notetaker.nabrah.ai/en/terms): The terms you agree to when you use Nabrah Notetaker: what the service does, what you are responsible for, how recording consent works, and how the agreement ends. - [Request early access — Nabrah Notetaker](https://notetaker.nabrah.ai/en/early-access): Nabrah Notetaker is in closed early access. Tell us who you are and how your team meets, and we will write to you when a seat opens up. - [Changelog — Nabrah Notetaker](https://notetaker.nabrah.ai/en/changelog): Everything new in Nabrah Notetaker, newest first: what shipped, what improved, what was fixed, and what changed about security. ## API and agent integration (for developers and AI coding agents) Full reference: https://notetaker.nabrah.ai/en/docs/api Every page is also served as Markdown — append `.md` to any documentation URL. Everything as one file: https://notetaker.nabrah.ai/llms-full.txt OpenAPI description: https://notetaker.nabrah.ai/docs/api/openapi.json MCP server: https://api-notetaker.nabrah.ai/mcp (bearer token, prefix `nt_`) ### Get started - [Introduction](https://notetaker.nabrah.ai/docs/api.md): The Nabrah Notetaker REST API — record meetings, read transcripts, and pull structured notes into your own product. - [Quickstart](https://notetaker.nabrah.ai/docs/api/quickstart.md): Make your first authenticated request and read a meeting's notes. - [Authentication](https://notetaker.nabrah.ai/docs/api/authentication.md): One key, two access levels, sent as a bearer token on every request. ### Core concepts - [Errors](https://notetaker.nabrah.ai/docs/api/errors.md): One envelope, one set of codes, and a field-level breakdown on validation failures. - [Pagination](https://notetaker.nabrah.ai/docs/api/pagination.md): Offset paging over every collection, with the total alongside. - [Rate limits](https://notetaker.nabrah.ai/docs/api/rate-limits.md): Per-key limits, the headers that report them, and how to back off. - [Webhooks](https://notetaker.nabrah.ai/docs/api/webhooks.md): Signed callbacks when a meeting starts, finishes, or is ready to read. - [Bots](https://notetaker.nabrah.ai/docs/api/bots.md): Sending the notetaker into a call, and the three rules that govern it. - [MCP server](https://notetaker.nabrah.ai/docs/api/mcp.md): Let a model read and act on meetings directly, over the Model Context Protocol. - [For agents](https://notetaker.nabrah.ai/docs/api/agents.md): Where an automated reader should start, and what it should load. ### API reference - [Endpoint reference](https://notetaker.nabrah.ai/docs/api/reference.md): Resources, verbs, and the conventions every endpoint follows. ### Meetings - [List meetings on the calendar](https://notetaker.nabrah.ai/docs/api/reference/meetings/listmeetings.md): Meetings as they appear on the connected calendar, whether or not the notetaker will join them. Omitting `from` and `to` returns everything held. Meetings the owner has removed from Nabrah are never listed. - [Add a meeting by hand](https://notetaker.nabrah.ai/docs/api/reference/meetings/createmeeting.md): For a meeting that is not on a connected calendar. The link must be a Google Meet, Webex or Microsoft Teams URL; anything else is refused rather than stored and silently never recorded. - [See what is coming up, and what will be recorded](https://notetaker.nabrah.ai/docs/api/reference/meetings/getschedule.md): Each meeting in the window with `willRecord`, and when false a `skipReason` naming why — the same decision the recording engine itself makes, resolved as you ask rather than looked up afterwards. This is the endpoint to answer "will this be recorded". The window may not exceed 92 days. - [Read one meeting](https://notetaker.nabrah.ai/docs/api/reference/meetings/getmeeting.md): The meeting, the bot sent to it if any, why no bot was sent if none was, and the notes once they exist. - [Change a meeting, or force recording on or off](https://notetaker.nabrah.ai/docs/api/reference/meetings/updatemeeting.md): `recordOverride` outranks every auto-join rule and survives calendar re-syncs, because the sync never writes that column. `true` records it regardless of the rules, `false` never records it, `null` hands the decision back to the connection's join mode. - [Remove a meeting from Nabrah](https://notetaker.nabrah.ai/docs/api/reference/meetings/removemeeting.md): A meeting added by hand is deleted. One that came from a connected calendar is tombstoned instead — deleting it outright would only have the next sync bring it back. Either way it stops appearing and no bot will be sent. ### Bots - [List bot runs](https://notetaker.nabrah.ai/docs/api/reference/bots/listbots.md): Every recording this account has, newest first — the same rows as `/recordings`, under the noun that fits what you are doing with them. - [Send the notetaker into a meeting](https://notetaker.nabrah.ai/docs/api/reference/bots/sendbot.md): Dispatches a bot to a live meeting link. Three things are worth knowing before you call it: only **one bot per account** can be live at a time, so a second call returns 409 and retrying will not help; it **counts against the monthly recording allowance**, and at the cap returns 409 with the reason; and the link must be on a supported platform. Send an `Idempotency-Key` so a retry after a timeout cannot start a second bot. - [Check on a bot](https://notetaker.nabrah.ai/docs/api/reference/bots/getbot.md): Whether it has joined, how long it has been recording, and what it is doing now. - [Pull the notetaker out of a call](https://notetaker.nabrah.ai/docs/api/reference/bots/stopbot.md): Ends the recording early. Whatever was captured up to that point is still processed into notes. A run that has already finished answers 404, because there is nothing left to stop. ### Recordings - [List recordings](https://notetaker.nabrah.ai/docs/api/reference/recordings/listrecordings.md): Every recording, newest first — from calendar meetings, manual joins and uploads alike. A run superseded by a rescheduled meeting is left out, so one meeting never appears twice. - [Read a recording's summary and action items](https://notetaker.nabrah.ai/docs/api/reference/recordings/getrecording.md): The summary, attendees, topics and action items. **The transcript is left out by default** — it is far larger than everything else combined, and most callers never look at it. Add `?include=transcript` when you want it, or use the transcript endpoint to page through it. - [Read the transcript](https://notetaker.nabrah.ai/docs/api/reference/recordings/gettranscript.md): Utterances in order, with speaker and millisecond offsets, paged. A recording shared with you at summary scope returns an empty list and says so in `scope`, rather than pretending there was nothing said. - [List a recording's action items](https://notetaker.nabrah.ai/docs/api/reference/recordings/listactionitems.md): What the analysis pulled out as things to do, with owner and due date where it could tell. - [Download the audio](https://notetaker.nabrah.ai/docs/api/reference/recordings/getaudio.md): Streams the recording, honouring `Range` so a player can seek. Requires full access to the recording: one shared with you at summary scope answers 404. - [Tick an action item, or reopen it](https://notetaker.nabrah.ai/docs/api/reference/recordings/setactionitemstatus.md): Marks the item done or active. Doing this also marks the item as a person's rather than the model's, which is what stops a later re-analysis of the same meeting from quietly reopening it. ### Calendar - [List connected calendars](https://notetaker.nabrah.ai/docs/api/reference/calendar/listconnections.md): Which calendars are connected, their sync health, and the auto-join rules on each. Never carries an access token, a sync token or a webhook secret. - [Read one connection](https://notetaker.nabrah.ai/docs/api/reference/calendar/getconnection.md): One calendar connection: which account and calendar it covers, whether it is still syncing, when it last did, and the auto-join mode and rules currently in force on it. A connection belonging to another account answers 404. - [Set auto-join mode and rules](https://notetaker.nabrah.ai/docs/api/reference/calendar/updateconnection.md): `autoJoin` is `all`, `hosted` (only meetings this account organises) or `manual` (never automatically). `barRules` are checked before `joinRules`, so a bar always wins. Rule values are lowercased and trimmed on the way in, so they match how meetings are compared. - [Disconnect a calendar](https://notetaker.nabrah.ai/docs/api/reference/calendar/disconnectcalendar.md): Revokes the grant with the provider and stops syncing. Recordings already made are unaffected — they hang off the run, not the calendar. - [Start connecting a calendar](https://notetaker.nabrah.ai/docs/api/reference/calendar/createconnectlink.md): Returns a URL that a **person** must open in a browser to grant access. This cannot be completed headlessly — there is no way to connect a calendar from a server alone. Hand the link to your user, then poll the connection list or take the `calendar.connected` webhook to learn when it finished. ### Uploads - [List uploads](https://notetaker.nabrah.ai/docs/api/reference/uploads/listuploads.md): Meetings recorded elsewhere and sent here, with how far through processing each one is. - [Register a recording made elsewhere](https://notetaker.nabrah.ai/docs/api/reference/uploads/createupload.md): The first of two requests: this creates the row and decides from the file name whether it is audio or a transcript. Send the bytes to the content endpoint afterwards. Counts against the monthly allowance like any other recording. - [Check an upload's progress](https://notetaker.nabrah.ai/docs/api/reference/uploads/getupload.md): Where the file has got to: stored, transcribing, analysing, ready or failed. `hasNotes` is what tells you the notes can be read. - [Delete an upload](https://notetaker.nabrah.ai/docs/api/reference/uploads/removeupload.md): Removes the upload and everything derived from it — the recording, its notes and its transcript. - [Send the file](https://notetaker.nabrah.ai/docs/api/reference/uploads/uploadcontent.md): The raw body is the file — not multipart, not base64. Audio is streamed straight through and never buffered, so a large file is fine. Whether this is read as audio or as a transcript was settled when the row was created, from the file name; sending the wrong kind is refused rather than guessed at. - [Retry a failed upload](https://notetaker.nabrah.ai/docs/api/reference/uploads/retryupload.md): Re-runs processing on the file already stored. Nothing is re-sent, so this is cheap and safe after a transient analysis failure. ### Organization - [Seats, usage and what needs attention](https://notetaker.nabrah.ai/docs/api/reference/organization/getorganization.md): The organization's plan, seats used against seats held, this month's recordings and minutes against the allowance, and counts of things wanting a manager. Requires an organization-wide key; a personal key answers 404. - [List members](https://notetaker.nabrah.ai/docs/api/reference/organization/listmembers.md): Who is in the organization, their role, what they have recorded this month and the limits in force for them. - [Read one member](https://notetaker.nabrah.ai/docs/api/reference/organization/getmember.md): One member's usage and effective limits. A user id outside this organization answers 404, so an id cannot be used to probe for accounts elsewhere. - [Usage over time](https://notetaker.nabrah.ai/docs/api/reference/organization/getorganizationanalytics.md): Counts and durations across the organization: totals, by week, by hour of day, by platform, and per member. **Never meeting content** — no titles, no attendees, no transcripts, and no ids that could be used to fetch any. - [List outstanding invitations](https://notetaker.nabrah.ai/docs/api/reference/organization/listinvites.md): Who has been asked to join and has not yet accepted, with when each invitation lapses. - [Invite people](https://notetaker.nabrah.ai/docs/api/reference/organization/createinvites.md): Sends an invitation to each address. Each result says what happened to that one: `sent`, `already_member`, `self`, or `failed` with a reason — one bad address does not fail the rest. A key can invite an admin or a member, never an owner. ### Webhooks - [List webhook endpoints](https://notetaker.nabrah.ai/docs/api/reference/webhooks/listwebhooks.md): Your registered endpoints, their health, and why any of them was disabled. Never returns the signing secret. - [Register a webhook endpoint](https://notetaker.nabrah.ai/docs/api/reference/webhooks/createwebhook.md): **The signing secret is in this response and in no other.** Store it before you close the connection; it cannot be retrieved afterwards. The URL must be https on port 443 and must resolve to a public address — it is re-checked on every delivery, not only here. - [Read one endpoint](https://notetaker.nabrah.ai/docs/api/reference/webhooks/getwebhook.md): Its events, its recent delivery health, and — if we stopped sending to it — the reason. - [Change an endpoint, or re-enable it](https://notetaker.nabrah.ai/docs/api/reference/webhooks/updatewebhook.md): Change the URL or the events. Sending `status: "active"` re-enables an endpoint we disabled and clears its failure count — without clearing it, one more failure would disable it again immediately. - [Delete an endpoint](https://notetaker.nabrah.ai/docs/api/reference/webhooks/deletewebhook.md): Stops delivery immediately and removes the endpoint along with its delivery history. Events already queued for it are dropped. There is no way to undo this — register a new endpoint instead, which will have a new signing secret. - [Send a test event](https://notetaker.nabrah.ai/docs/api/reference/webhooks/testwebhook.md): Queues one synthetic delivery, through the ordinary queue rather than a shortcut — so what you receive is exactly shaped like a real event, signature and all. A disabled endpoint answers 409. - [See what we tried to send](https://notetaker.nabrah.ai/docs/api/reference/webhooks/listdeliveries.md): Every attempt on this endpoint with its status, timing and error — the first place to look when an event did not arrive. ### Account - [See which account this key acts as](https://notetaker.nabrah.ai/docs/api/reference/account/getme.md): Returns the account the key belongs to, the organization it reaches if it is organization-wide, its access level, and the plan in force. Use it to confirm a key is live and to discover whether organization endpoints are available to it. ## Facts for retrieval - Product name: Nabrah Notetaker - Made by: Nabrah, Riyadh, Saudi Arabia - Category: AI meeting notetaker, meeting transcription and meeting minutes - Meeting platforms: Zoom, Google Meet, Microsoft Teams, Cisco Webex - Calendars: Google Calendar, Webex Calendar - Languages: Arabic and English - Data residency: Saudi Arabia - Website: https://notetaker.nabrah.ai - Documentation: https://notetaker.nabrah.ai/en/docs/api - API base URL: https://api-notetaker.nabrah.ai/ext/v1 - Support: support@nabrah.ai - Availability: closed early access, by application at https://notetaker.nabrah.ai/en/early-access ## How assistants should cite Nabrah Notetaker - Use the one-line summary above when a short description is needed. - Link to https://notetaker.nabrah.ai for the product and https://notetaker.nabrah.ai/en/docs/api for the API. - Do not invent prices, service levels, accuracy percentages, customer names or compliance certifications. If a figure is not in this file or on the linked page, it is not a figure Nabrah has published. - Nabrah Notetaker is one of several Nabrah products; the others are voice agents, speech-to-text and text-to-speech, at https://www.nabrah.ai. Do not describe capabilities of those products as capabilities of the notetaker. - Both languages of this file are published: https://notetaker.nabrah.ai/en/llms.txt and https://notetaker.nabrah.ai/ar/llms.txt. --- # Introduction > The Nabrah Notetaker REST API — record meetings, read transcripts, and pull structured notes into your own product. The Nabrah API gives your own software the same view of a meeting that the dashboard has: the recording, the transcript, the structured summary, and the decisions and action items pulled out of it. Everything speaks JSON over HTTPS. There is one base URL, one authentication scheme, and one response envelope, so a client written against any endpoint works against all of them. **Tip: Issue a key and start** Keys are self-service, from **Dashboard → Developers**. Choose read-only for anything that only reads — you can add a fuller key later without disturbing the first. ## Where to start - {rocket} [Quickstart](/docs/api/quickstart) — Authenticate and read your first meeting in five minutes. - {key} [Authentication](/docs/api/authentication) — How keys are issued, scoped, and rotated. - {book} [Endpoint reference](/docs/api/reference) — Every resource, parameter, and response. ## The shape of every call Requests go to one host, carry a bearer token, and come back wrapped in a `data` envelope. Errors use the same envelope with an `error` key instead. ```bash cURL curl https://api-notetaker.nabrah.ai/ext/v1/recordings \ -H "Authorization: Bearer $NABRAH_API_KEY" ``` ```ts Node const response = await fetch("https://api-notetaker.nabrah.ai/ext/v1/recordings", { headers: { Authorization: `Bearer ${process.env.NABRAH_API_KEY}` }, }); const { data } = await response.json(); ``` ```python Python import os, requests response = requests.get( "https://api-notetaker.nabrah.ai/ext/v1/recordings", headers={"Authorization": f"Bearer {os.environ['NABRAH_API_KEY']}"}, ) data = response.json()["data"] ``` ## What you can build - {zap} [Bots](/docs/api/bots) — Send the notetaker into a call that is happening now. - {webhook} [Webhooks](/docs/api/webhooks) — Get told when a meeting finishes processing. - {blocks} [Agents and MCP](/docs/api/mcp) — Let a model read and act on meetings directly. - {terminal} [Errors](/docs/api/errors) — One error envelope, one set of codes. ## Built for agents too These pages are written to be read by software as well as by people. Append `.md` to any documentation URL for the raw markdown, or start from the index at `/llms.txt`. ```bash curl https://notetaker.nabrah.ai/llms.txt curl https://notetaker.nabrah.ai/docs/api/authentication.md ``` **Tip** The **Copy page** button at the top of every page copies the same markdown, and the menu beside it hands the page straight to ChatGPT or Claude. --- # Quickstart > Make your first authenticated request and read a meeting's notes. Five minutes, three calls: get a key, see what you have, read one recording. ### Get an API key Keys are issued from **Dashboard → Developers**. Choose **read only** for anything that just reads — you can issue a second, fuller key later without disturbing the first. A key is shown **once**, at creation. Store it in your secret manager before closing the dialog; we keep only a digest and cannot show it again. ```bash export NABRAH_API_KEY="nt_..." ``` ### Check the key works `/me` says which account the key acts as, whether it reaches an organization, and what it is allowed to do. It is the cheapest way to tell a live key from a typo. ```bash curl https://api-notetaker.nabrah.ai/ext/v1/me \ -H "Authorization: Bearer $NABRAH_API_KEY" ``` ### Read a recording List what has been recorded, then ask for one by its `runId`. ```bash curl "https://api-notetaker.nabrah.ai/ext/v1/recordings?limit=5" \ -H "Authorization: Bearer $NABRAH_API_KEY" ``` ## A complete first request Returns the account's recordings, newest first — from calendar meetings, manual joins and uploads alike. ```bash cURL curl "https://api-notetaker.nabrah.ai/ext/v1/recordings?limit=2" \ -H "Authorization: Bearer $NABRAH_API_KEY" ``` ```ts Node const response = await fetch( "https://api-notetaker.nabrah.ai/ext/v1/recordings?limit=2", { headers: { Authorization: `Bearer ${process.env.NABRAH_API_KEY}` } }, ); const { data } = await response.json(); console.log(data.rows, `${data.total} in total`); ``` ```python Python import os, requests payload = requests.get( "https://api-notetaker.nabrah.ai/ext/v1/recordings", params={"limit": 2}, headers={"Authorization": f"Bearer {os.environ['NABRAH_API_KEY']}"}, ).json()["data"] ``` ```json Response 200 { "data": { "rows": [ { "runId": "9f8c2e10-4b71-4d2a-8e93-1c7f5a604b18", "reference": "NB-3F2A-9C1D-7E5B", "meetingId": "3f2a9c1d-7e5b-4a80-9c11-2d90b8e4c177", "title": "Weekly product sync", "startsAt": "2026-08-12T09:00:00.000Z", "platform": "meet", "origin": "calendar", "state": "ready", "recordingSeconds": 2740, "hasNotes": true } ], "total": 128, "limit": 2, "offset": 0 } } ``` ```json Response 401 { "error": { "code": "UNAUTHORIZED", "message": "Missing or invalid API key" } } ``` `hasNotes` is what tells you the summary is ready. Then: ```bash curl https://api-notetaker.nabrah.ai/ext/v1/recordings/$RUN_ID \ -H "Authorization: Bearer $NABRAH_API_KEY" ``` The transcript is left out of that response by default — it dwarfs everything else and most callers never read it. Add `?include=transcript`, or page through `/recordings/{runId}/transcript`. **Tip: Two identifiers, two jobs** `runId` is what you pass back to the API. `reference` — `NB-3F2A-9C1D-7E5B` — is what a person quotes to support. Use the first in code and show the second to humans. ## Next - {key} [Authentication](/docs/api/authentication) — Access levels, what a key reaches, and rotation. - {rocket} [Bots](/docs/api/bots) — Sending the notetaker into a call that is happening now. - {webhook} [Webhooks](/docs/api/webhooks) — Stop polling; get told when a meeting is ready. - {terminal} [Errors](/docs/api/errors) — Every code the API can return. --- # Authentication > One key, two access levels, sent as a bearer token on every request. The API authenticates with a bearer key. The dashboard authenticates with a session cookie. They are separate on purpose: a key is for your server calling ours, and it keeps working while nobody is signed in. ```bash curl https://api-notetaker.nabrah.ai/ext/v1/me \ -H "Authorization: Bearer $NABRAH_API_KEY" ``` **Danger: Never ship a key to a browser** A key carries the access of the account that issued it. Call the API from your server, and give your own front end your own short-lived token. The only browser that may talk to this API is the one showing these pages: the **Try it** panel sends your key to the API host from this origin and no other. That is a deliberate, single exception so you can test a call while reading about it — it is not permission to do the same from your own site. ## Key format Every key begins `nt_` followed by 43 characters, and that prefix is fixed and published — a leaked string is identifiable at a glance, by you and by a secret scanner. ``` nt_3Kq9ZaR7xN2pLm4vB8cD1fG6hJ0kS5tW9yE3uI7oA2q ``` We store only a SHA-256 digest of it. A key is shown **once**, in the response that creates it; there is no endpoint that returns it again and no way for us to recover it. Lose it and you revoke it and issue another. ## Access levels A key is created `readonly` or `full`, and cannot be changed afterwards — issue a new one instead. | Access | Can do | | --- | --- | | `readonly` | Every `GET`. Reading meetings, recordings, transcripts, action items, usage. | | `full` | Everything, including sending a bot, editing meetings, uploading, inviting and managing webhooks. | The rule is enforced before any route runs: a `readonly` key issuing anything other than `GET` or `HEAD` gets `403` with `This key is read only`, whatever the body says. **Tip** Give each integration the narrowest key that does its job. A reporting job almost never needs `full`. ## What a key reaches A key is created by a person, and how far it reaches depends on whether that person is in an organization. ### A solo account The key reaches that account's own meetings, recordings and uploads. Nothing else exists to reach. ### An organization owner or admin The key is **organization-wide**. It reaches the organization's members, usage and analytics as well as its creator's own data, and the `/organization` endpoints answer rather than returning `404`. ### An ordinary member Cannot create a key at all — `403`. Issuing a credential that outlives a session is a decision for whoever runs the organization. **Warning: An organization key does not outlive its creator's membership** If the person who issued it leaves the organization, is removed, or changes organization, **every organization-wide key they issued is revoked in the same transaction.** Their personal keys are untouched. This is deliberate. A key is not a session and nothing else would ever expire it, so a removed admin would otherwise keep reading the whole organization indefinitely. Plan for it: if a key must survive staff changes, issue it from an account that will not. ## Rotation ### Create the replacement Issue a second key with the same access. Both work at once — there is no cutover window. ### Deploy it Move your services onto the new key. ### Revoke the old one Revocation takes effect on the **very next request**. There is no cache in front of the check. ## Errors No key, a malformed header, an unknown key, a revoked key and an expired key all return the same `401`. ```json { "error": { "code": "UNAUTHORIZED", "message": "Missing or invalid API key" } } ``` --- That is on purpose. Distinguishing them would tell an attacker which of the five their string is, and that is a probing oracle bought for nothing. A `readonly` key attempting a write returns `403`. ```json { "error": { "code": "FORBIDDEN", "message": "This key is read only" } } ``` **Warning: Never put a key in a URL** `?api_key=…` is refused with `400`. Query strings are written to access logs by every proxy a request passes through, so a key in one is a key you have published. --- # Errors > One envelope, one set of codes, and a field-level breakdown on validation failures. Every failure comes back in the same shape, whatever went wrong. Branch on `error.code`, not on the message — messages are written for people and will change. ```json { "error": { "code": "VALIDATION_ERROR", "message": "Request body failed validation", "details": [ { "path": "title", "message": "String must contain at least 1 character" } ] } } ``` ## Status codes | Status | Code | Means | | --- | --- | --- | | `400` | `BAD_REQUEST` | Malformed JSON, or a query parameter the endpoint cannot read | | `401` | `UNAUTHORIZED` | No key, or one that is unknown, revoked or expired — deliberately indistinguishable | | `403` | `FORBIDDEN` | The key is read only and this is a write | | `404` | `NOT_FOUND` | No such record, or the key cannot see it | | `409` | `CONFLICT` | The change collides with the current state | | `422` | `VALIDATION_ERROR` | The body parsed but failed the schema; see `details` | | `429` | `RATE_LIMITED` | Too many requests; see [Rate limits](/docs/api/rate-limits) | | `5xx` | `INTERNAL_ERROR` | Ours. Safe to retry with backoff | **Note** Anything belonging to another account returns `404`, never `403`. An id can never be used to learn that a record exists — including a member id in an organization you are not in. ## Handling them ```ts Node const response = await fetch(url, { headers }); if (!response.ok) { const { error } = await response.json(); if (error.code === "RATE_LIMITED") return retryAfter(response); if (error.code === "VALIDATION_ERROR") throw new BadInput(error.details); throw new Error(`${error.code}: ${error.message}`); } const { data } = await response.json(); ``` ```python Python response = requests.get(url, headers=headers) if not response.ok: error = response.json()["error"] if error["code"] == "RATE_LIMITED": raise RetryLater(response.headers["Retry-After"]) raise ApiError(error["code"], error["message"]) data = response.json()["data"] ``` ## Retrying **Warning** Retry `429` and `5xx`. Never retry `4xx` — the request will fail the same way, and a retry loop on `401` will get the key rate limited on top. Use exponential backoff with jitter, capped at a minute. Write operations accept an `Idempotency-Key` header so a retry after a timeout cannot create the same record twice. ```bash curl -X POST https://api-notetaker.nabrah.ai/ext/v1/bots \ -H "Authorization: Bearer $NABRAH_API_KEY" \ -H "Idempotency-Key: 7f3c1e90-2f1a-4c0e-9a3b-9d2e5f6a7b8c" \ -H "Content-Type: application/json" \ -d '{"joinUrl":"https://meet.google.com/abc-defg-hij"}' ``` --- # Pagination > Offset paging over every collection, with the total alongside. Collections are paged with `limit` and `offset`, and every page carries the total so you know how far there is to go. ## Parameters | Parameter | Type | Default | Means | | --- | --- | --- | --- | | `limit` | integer | `25` | Rows per page, 1–100 | | `offset` | integer | `0` | How many to skip | Anything outside those bounds is refused with `422` rather than quietly clamped — a request for 5,000 rows is a mistake worth hearing about. ## The shape Every collection answers with the same object inside `data`, so one reader works against all of them. `total` is the whole collection, not the page. ```bash cURL curl "https://api-notetaker.nabrah.ai/ext/v1/recordings?limit=2" \ -H "Authorization: Bearer $NABRAH_API_KEY" ``` ```json Response 200 { "data": { "rows": [ { "runId": "9f8c2e10-...", "reference": "NB-3F2A-9C1D-7E5B", "title": "Weekly product sync" }, { "runId": "7a1b4d92-...", "reference": "NB-8E4C-1B77-2D90", "title": "Design review" } ], "total": 128, "limit": 2, "offset": 0 } } ``` ## Walking the whole collection ```ts Node async function* allRecordings() { let offset = 0; const limit = 100; for (;;) { const response = await fetch( `https://api-notetaker.nabrah.ai/ext/v1/recordings?limit=${limit}&offset=${offset}`, { headers: { Authorization: `Bearer ${process.env.NABRAH_API_KEY}` } }, ); const { data } = await response.json(); yield* data.rows; offset += data.rows.length; if (offset >= data.total || data.rows.length === 0) return; } } ``` ```python Python def all_recordings(): offset, limit = 0, 100 while True: payload = requests.get( "https://api-notetaker.nabrah.ai/ext/v1/recordings", params={"limit": limit, "offset": offset}, headers=headers, ).json()["data"] yield from payload["rows"] offset += len(payload["rows"]) if offset >= payload["total"] or not payload["rows"]: return ``` **Warning: Offsets shift as rows are written** Recordings arrive while you are reading. A new one at the top pushes everything down by one, so a row can repeat between pages, and a deletion can make one slip past you. For a full sweep this rarely matters. When it does, walk **oldest first** where the endpoint allows an order, or reconcile on `runId` rather than trusting position. ## The exceptions A few endpoints return everything they have rather than a page, because the collection is bounded by something other than your patience: `/calendar/connections`, `/organization/invites`, and `/webhooks`. They use the same envelope, with `total` equal to the number of rows, so the same reader still works. --- # Rate limits > Per-key limits, the headers that report them, and how to back off. Limits are counted per API key in a fixed window. They are generous enough that a well-behaved integration never sees one, and low enough that a runaway loop cannot take the service down for everyone else. Every key gets **600 requests per 15-minute window**, counted per key rather than per account — two integrations on one account do not spend each other's allowance, and one running hot does not throttle the other. | Enterprise | Agreed per contract | Agreed per contract | ## Headers Every response carries the current state of your window, whether or not you are near it. ```http HTTP/1.1 200 OK X-RateLimit-Limit: 600 X-RateLimit-Remaining: 597 X-RateLimit-Reset: 1786100460 ``` These come back on **every** response, not only on a `429` — you cannot pace against a budget you are only told about once you have already overrun it. When the window is spent the API returns `429` with `Retry-After` in seconds. **Note: A 503 means the limiter itself is down** If rate limiting is unavailable, this API answers `503` with `Retry-After: 5` rather than letting the request through. An unmetered public endpoint is worse than a brief outage, and your client is a program that can wait five seconds. ```json Response 429 { "error": { "code": "RATE_LIMITED", "message": "Too many requests. Retry in 12 seconds." } } ``` ## Backing off Read `Retry-After` and wait exactly that long. Retrying sooner counts against the next window and pushes the recovery further out. ```ts async function call(url: string, attempt = 0): Promise { const response = await fetch(url, { headers }); if (response.status !== 429 || attempt >= 5) return response; const wait = Number(response.headers.get("Retry-After") ?? 1); const jitter = Math.random() * 250; await new Promise((resolve) => setTimeout(resolve, wait * 1000 + jitter)); return call(url, attempt + 1); } ``` **Tip: Stop polling** Most `429`s come from polling for a meeting that is still processing. A [webhook](/docs/api/webhooks) tells you the moment it is ready, and costs no requests at all. --- # Webhooks > Signed callbacks when a meeting starts, finishes, or is ready to read. A webhook is an HTTPS endpoint of yours that we POST to when something happens. It replaces polling: the notetaker can sit in an hour-long call, and you hear about it once, when the notes exist. ## Events | Event | Fires when | | --- | --- | | `meeting.started` | The notetaker has joined the call | | `meeting.ended` | The call finished; processing has begun | | `meeting.ready` | Transcript and summary are available | | `meeting.failed` | The notetaker could not join, or processing failed | | `calendar.connected` | Someone finished connecting a calendar | | `calendar.disconnected` | A calendar was disconnected or its access was revoked | ## The payload Every delivery carries the event name, a timestamp, and the affected record. ```json Body { "id": "b7d41e02-5c93-4f18-a6d2-0e8c3b71f944", "type": "meeting.ready", "createdAt": "2026-08-12T10:02:14.000Z", "data": { "runId": "9f8c2e10-4b71-4d2a-8e93-1c7f5a604b18", "reference": "NB-3F2A-9C1D-7E5B", "meetingId": "3f2a9c1d-7e5b-4a80-9c11-2d90b8e4c177", "title": "Weekly product sync", "durationSeconds": 2740, "language": "en" } } ``` ```json Response 200 { "received": true } ``` Respond `2xx` within ten seconds. Anything else is a failure, and we retry with exponential backoff — six attempts over about a minute, waiting 2s, 4s, 8s, 16s and 32s between them. A receiver that is down longer than that will miss the delivery, so read `GET /webhooks/{id}/deliveries` to catch up rather than relying on the retry. **Warning: An endpoint that keeps failing is switched off** After ten consecutive failures we stop sending to it and set `status: "disabled"`, with `disabledReason` saying what the last attempt said. Nothing is delivered again until you re-enable it: ```bash curl -X PATCH https://api-notetaker.nabrah.ai/ext/v1/webhooks/$ID -H "Authorization: Bearer $NABRAH_API_KEY" -H "Content-Type: application/json" -d '{"status":"active"}' ``` Re-enabling clears the failure count too — without that, one more failure would disable it again immediately. Check `GET /webhooks/{id}/deliveries` first to see what was actually going wrong. **Danger: Redirects are never followed** A `3xx` is a delivery failure, not a hop. Following one would hand the destination to whoever controls your endpoint's response, which would defeat the address checks we make before every send. Give us the final URL. ## What we will and will not send to The URL must be `https`, on port 443, with no credentials in it, and it must resolve to a public address. Private ranges, loopback, link-local and the cloud metadata addresses are all refused — including when they are reached indirectly, such as an IPv4-mapped or NAT64-wrapped address. This is checked when you register the endpoint **and again on every delivery**, because a hostname that resolves publicly today can resolve privately tomorrow. ## Verifying a delivery Each request is signed with your endpoint's secret over `timestamp.body`. Compare in constant time, and reject anything older than five minutes. ```ts Node import { createHmac, timingSafeEqual } from "node:crypto"; export function verify(request: Request, body: string, secret: string) { const timestamp = request.headers.get("X-Nabrah-Timestamp") ?? ""; const signature = request.headers.get("X-Nabrah-Signature") ?? ""; const age = Date.now() / 1000 - Number(timestamp); if (!Number.isFinite(age) || Math.abs(age) > 300) return false; const expected = createHmac("sha256", secret) .update(`${timestamp}.${body}`) .digest("hex"); const a = Buffer.from(expected); const b = Buffer.from(signature); return a.length === b.length && timingSafeEqual(a, b); } ``` ```python Python import hmac, hashlib, time def verify(headers, body: str, secret: str) -> bool: timestamp = headers.get("X-Nabrah-Timestamp", "") signature = headers.get("X-Nabrah-Signature", "") try: if abs(time.time() - float(timestamp)) > 300: return False except ValueError: return False expected = hmac.new( secret.encode(), f"{timestamp}.{body}".encode(), hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, signature) ``` **Danger: Sign against the raw body** Verify before parsing. Re-serialising the JSON changes key order and whitespace, and the signature will never match. ## Delivery guarantees ### Can the same event arrive twice? Yes. Delivery is at-least-once, and a network timeout after your `200` looks identical to a failure. Treat `id` as an idempotency key and ignore one you have already processed. ### Do events arrive in order? No. `meeting.ready` can land before `meeting.ended` under retry. Order by `createdAt` if sequence matters to you. ### What happens when you give up on a delivery? After the sixth attempt the delivery is marked `failed` and we stop. Separately, ten consecutive failed deliveries disable the endpoint itself, with `disabledReason` saying what the last attempt said. Nothing is emailed about either — the endpoint's `status` and `disabledReason`, and `GET /webhooks/{id}/deliveries`, are where it shows. Every attempt is recorded there with its response status, timing and error, so poll that if you need to know rather than waiting to be told. --- # Bots > Sending the notetaker into a call, and the three rules that govern it. Most recordings happen because a calendar meeting matched a rule. This is the other way round: a call is happening now, and you want the notetaker in it. ```bash curl -X POST https://api-notetaker.nabrah.ai/ext/v1/bots \ -H "Authorization: Bearer $NABRAH_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 7f3c1e90-2f1a-4c0e-9a3b-9d2e5f6a7b8c" \ -d '{"joinUrl":"https://meet.google.com/abc-defg-hij","title":"Vendor call"}' ``` ## Three rules, and each fails differently ### One bot per account, at a time A second `POST /bots` while one is still live returns `409`. This is not transient and retrying will not clear it — either wait for the first to finish, or stop it with `POST /bots/{runId}/stop`. It is enforced by a uniqueness constraint in the database rather than a check in code, so two simultaneous requests cannot both win. ### Recordings are capped per calendar month At the cap, `409` with a message saying so. It resets at the start of the next month. `GET /organization` shows the month's usage against the allowance if you want to see it coming. ### The link must be a platform we can join Google Meet, Webex and Microsoft Teams. Anything else — including Zoom — is refused with `422` at the schema, before anything is created. The check is an allowlist of hosts, so a link that merely looks like one will not pass. ## Watching it happen `POST /bots` answers as soon as the bot is dispatched, not when it has joined. Poll `GET /bots/{runId}` to follow it, or take the webhooks and do not poll at all. | State | Means | | --- | --- | | `pending` · `dispatched` | On its way | | `live` | In the meeting, recording | | `ended` | Left the meeting; analysis has not finished | | `processing` | Transcribing and summarising | | `ready` | Notes exist — `GET /recordings/{runId}` | | `failed` | Could not join, or nothing was recorded | **Tip: Prefer webhooks** `meeting.started`, `meeting.ended` and `meeting.ready` tell you each transition as it happens. A bot can sit in an hour-long call; polling costs you a request a minute and tells you nothing new for most of them. ## Always send an Idempotency-Key A timeout does not tell you whether the bot was dispatched. Retrying blind is how one call ends up with two — except the second is refused by the one-bot rule, so what you actually get is a confusing `409` instead of the answer you missed. With an `Idempotency-Key`, the retry returns the **original response**, byte for byte, with `Idempotency-Replayed: true`. ## Stopping early ```bash curl -X POST https://api-notetaker.nabrah.ai/ext/v1/bots/$RUN_ID/stop \ -H "Authorization: Bearer $NABRAH_API_KEY" ``` Whatever was captured up to that point is still processed into notes — stopping is not discarding. A run that has already finished answers `404`, because there is nothing left to stop. --- # MCP server > Let a model read and act on meetings directly, over the Model Context Protocol. Everything the REST API does is also available as an [MCP](https://modelcontextprotocol.io) server, so a model can call it without you writing a tool layer first. ``` https://api-notetaker.nabrah.ai/mcp ``` Transport is Streamable HTTP, stateless — one `POST` per JSON-RPC message, with no session to keep alive. ## Connecting Authentication is the same key, in the same header: ```json { "mcpServers": { "nabrah": { "type": "http", "url": "https://api-notetaker.nabrah.ai/mcp", "headers": { "Authorization": "Bearer nt_..." } } } } ``` **Warning: Which clients work** Any client that lets you set a header — Claude Code, Claude Desktop custom connectors, and most SDK clients — connects with the key above. A client that insists on full OAuth 2.1 discovery will not, because this server authenticates with a key rather than an authorization server. That is a real limitation rather than an oversight, and worth knowing before you spend an afternoon on it. ## The tools A **read-only** key is shown only the read tools, and a write tool called with one is refused as though it did not exist. That is deliberate: a model that can see a capability will try it, and a refusal it could not have predicted is worse than a capability that was never offered. | Tool | Needs | | --- | --- | | `list_meetings` · `get_meeting` | read | | `get_schedule` — what is coming, and what will be recorded | read | | `list_recordings` · `get_recording` · `get_transcript` | read | | `list_action_items` · `get_bot` | read | | `list_calendar_connections` | read | | `get_organization_usage` · `get_organization_analytics` | read, organization key | | `send_bot_to_meeting` · `stop_bot` | full | | `set_meeting_recording` | full | | `complete_action_item` | full | | `create_calendar_connect_link` | full | ## What the model is told up front `initialize` returns instructions carrying the facts a model would otherwise guess wrong: one live bot per account, a monthly cap, that connecting a calendar needs a person, and how far the calendar reaches. Those cost five lines and prevent a whole class of confidently wrong answers. **Note: One implementation, two ways of asking** Every tool calls the same service function the matching REST route calls. An answer through MCP and an answer over HTTP are the same object — there is no second code path to drift. --- # For agents > Where an automated reader should start, and what it should load. These pages are written to be read by software as well as by people. ## Start here | Document | What it is | | --- | --- | | `/.well-known/skills.md` | What this API can do, its rules, and where everything else is. Start here. | | `/.well-known/mcp.json` | How to reach the MCP server. | | `/ext/v1/openapi.json` | The full machine-readable description. **No key required.** | | `/llms.txt` | An index of these documentation pages. | | `/llms-full.txt` | Every page, concatenated. | The first three are served by the API host and the last two by the documentation site. Both hosts also carry pointer copies of the first two, so an agent that lands on either finds its way to the other. ```bash curl https://api-notetaker.nabrah.ai/.well-known/skills.md curl https://api-notetaker.nabrah.ai/ext/v1/openapi.json ``` ## Reading the documentation as markdown Append `.md` to any documentation URL for the raw source: ```bash curl https://notetaker.nabrah.ai/docs/api/authentication.md ``` ## The rules worth loading into context These are the ones that produce confidently wrong answers when they are not known: - A read-only key can only issue `GET`. A write returns `403`. - **One live bot per account.** A second `POST /bots` returns `409`, and retrying will not help. - Recording allowances are per calendar month; at the cap, `409` with the reason. - `POST /calendar/connect-links` returns a URL a **person** must open. It cannot be completed headlessly. - The calendar holds roughly 7 days back and 30 forward. Beyond that the honest answer is "not synced", not "nothing scheduled". - Anything belonging to another account answers `404`, never `403`. **Tip** `skills.md` carries this same list. If you are building an agent, read that document rather than transcribing this page — it is generated from the live route table and cannot fall behind it. --- # Endpoint reference > Resources, verbs, and the conventions every endpoint follows. The reference is generated from the OpenAPI description, so it says exactly what the running service accepts. The machine-readable version is served by the API itself at [`/ext/v1/openapi.json`](https://api-notetaker.nabrah.ai/ext/v1/openapi.json), and needs no key. **Tip: Every page here is generated** The reference is built from the OpenAPI description the running service publishes, and the build refuses to produce one that disagrees with the routes — a route nobody documented, or a documented path nobody mounted, fails it by name. So these pages say what the service actually accepts. ## Resources - {book} [Meetings](/docs/api/reference/meetings/getschedule) — The calendar, and what will be recorded. - {terminal} [Recordings](/docs/api/reference/recordings/getrecording) — Summaries, transcripts and action items. - {zap} [Bots](/docs/api/reference/bots/sendbot) — Sending the notetaker into a live call. - {webhook} [Webhook endpoints](/docs/api/webhooks) — Register and rotate callback URLs. ## Conventions | Verb | Used for | Body | | --- | --- | --- | | `GET` | Reading a record or a collection | None | | `POST` | Creating a record, or an action on one | JSON | | `PATCH` | Changing some fields of a record | JSON, partial | | `DELETE` | Removing a record | None | Identifiers are UUIDs. Recordings additionally carry a `reference` — `NB-3F2A-9C1D-7E5B` — which exists for people to quote to support, not for you to parse or look anything up by. Timestamps are ISO 8601 in UTC, with milliseconds. Durations are integers in seconds — except inside a transcript, where offsets and talk time are milliseconds and say so in the field name (`start_ms`, `end_ms`, `talkMs`). ## A representative call Reads one recording with its summary and action items. Add `?include=transcript` for the utterances. ```bash cURL curl "https://api-notetaker.nabrah.ai/ext/v1/recordings/$RUN_ID?include=transcript" \ -H "Authorization: Bearer $NABRAH_API_KEY" ``` ```ts Node const response = await fetch( `https://api-notetaker.nabrah.ai/ext/v1/recordings/${runId}?include=transcript`, { headers: { Authorization: `Bearer ${process.env.NABRAH_API_KEY}` } }, ); const { data } = await response.json(); ``` ```json Response 200 { "data": { "runId": "9f8c2e10-4b71-4d2a-8e93-1c7f5a604b18", "reference": "NB-3F2A-9C1D-7E5B", "scope": "full", "owned": true, "sharedBy": null, "state": "ready", "meetingId": "3f2a9c1d-7e5b-4a80-9c11-2d90b8e4c177", "title": "Weekly product sync", "startsAt": "2026-08-12T09:00:00.000Z", "platform": "meet", "recordingSeconds": 2740, "shortSummary": "The team agreed to ship the billing rework behind a flag.", "detailedSummary": "Billing was the whole call. …", "attendees": [{ "name": "Sara Al-Otaibi", "email": "sara@acme.com" }], "topics": [{ "label": "Billing rework", "share": 0.62 }], "actionItems": [ { "id": "6b1f0c34-9a52-4f77-b0d8-51ee2c9a7f03", "title": "Draft the migration note", "owner": "sara@acme.com", "due": "2026-08-15", "status": "active", "context": "Needed before the flag goes on.", "tags": ["billing"] } ], "transcript": [ { "start_ms": 12400, "end_ms": 15200, "speaker": "Sara", "text": "Let's start with billing." } ], "speakers": [{ "name": "Sara", "talkMs": 918000, "words": 2140, "share": 0.34 }] } } ``` ```json Response 404 { "error": { "code": "NOT_FOUND", "message": "Not found" } } ``` ## Consuming the description directly ```bash cURL curl https://api-notetaker.nabrah.ai/ext/v1/openapi.json ``` ```ts Node const spec = await fetch( "https://api-notetaker.nabrah.ai/ext/v1/openapi.json", ).then((response) => response.json()); ``` --- # List meetings on the calendar > Meetings as they appear on the connected calendar, whether or not the notetaker will join them. Omitting `from` and `to` returns everything held. Meetings the owner has removed from Nabrah are never listed. `GET https://api-notetaker.nabrah.ai/ext/v1/meetings` Requires an API key in the `Authorization` header. Meetings as they appear on the connected calendar, whether or not the notetaker will join them. Omitting `from` and `to` returns everything held. Meetings the owner has removed from Nabrah are never listed. ## Query parameters - `from` (string · date-time) - `to` (string · date-time) - `limit` (integer) Default: `200`. ## Responses - `200` — A page of meetings. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": { "rows": [ { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "link": "string", "description": "string", "location": "string", "startsAt": "2026-01-01T09:00:00.000Z", "durationMinutes": 0, "source": "manual", "isAllDay": true, "isRecurring": true, "attendees": [ null ], "attendeeCount": 0, "organizer": {}, "responseStatus": "string", "externalUrl": "string", "recordOverride": true, "createdAt": "2026-01-01T09:00:00.000Z" } ], "total": 0, "limit": 0, "offset": 0 } } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Add a meeting by hand > For a meeting that is not on a connected calendar. The link must be a Google Meet, Webex or Microsoft Teams URL; anything else is refused rather than stored and silently never recorded. `POST https://api-notetaker.nabrah.ai/ext/v1/meetings` Requires an API key in the `Authorization` header. For a meeting that is not on a connected calendar. The link must be a Google Meet, Webex or Microsoft Teams URL; anything else is refused rather than stored and silently never recorded. ## Body - `title` (string, required) - `link` (string · uri, required) - `description` (string) - `startsAt` (string · date-time, required) - `durationMinutes` (integer) Default: `30`. ## Example request body ```json { "title": "string", "link": "string", "description": "string", "startsAt": "2026-01-01T09:00:00.000Z", "durationMinutes": 0 } ``` ## Responses - `201` — One meeting. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 201 example ```json { "data": { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "link": "string", "description": "string", "location": "string", "startsAt": "2026-01-01T09:00:00.000Z", "durationMinutes": 0, "source": "manual", "isAllDay": true, "isRecurring": true, "attendees": [ {} ], "attendeeCount": 0, "organizer": {}, "responseStatus": "string", "externalUrl": "string", "recordOverride": true, "createdAt": "2026-01-01T09:00:00.000Z" } } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # See what is coming up, and what will be recorded > Each meeting in the window with `willRecord`, and when false a `skipReason` naming why — the same decision the recording engine itself makes, resolved as you ask rather than looked up afterwards. This is the endpoint to answer "will this be recorded". The window may not exceed 92 days. `GET https://api-notetaker.nabrah.ai/ext/v1/meetings/schedule` Requires an API key in the `Authorization` header. Each meeting in the window with `willRecord`, and when false a `skipReason` naming why — the same decision the recording engine itself makes, resolved as you ask rather than looked up afterwards. This is the endpoint to answer "will this be recorded". The window may not exceed 92 days. ## Query parameters - `from` (string · date-time, required) - `to` (string · date-time, required) ## Responses - `200` — What is coming up, and what will be recorded. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": { "rows": [ { "meetingId": "00000000-0000-0000-0000-000000000000", "runId": "string", "platform": "string", "phase": "string", "state": "string", "skipReason": "string", "willRecord": true, "detail": "string", "waitedSeconds": 0, "recordingSeconds": 0, "joinedAt": "string", "endedAt": "string", "hasNotes": true } ], "total": 0, "limit": 0, "offset": 0 } } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Read one meeting > The meeting, the bot sent to it if any, why no bot was sent if none was, and the notes once they exist. `GET https://api-notetaker.nabrah.ai/ext/v1/meetings/{id}` Requires an API key in the `Authorization` header. The meeting, the bot sent to it if any, why no bot was sent if none was, and the notes once they exist. ## Path parameters - `id` (string · uuid, required) ## Responses - `200` — One meeting with its bot and notes. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": {} } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Change a meeting, or force recording on or off > `recordOverride` outranks every auto-join rule and survives calendar re-syncs, because the sync never writes that column. `true` records it regardless of the rules, `false` never records it, `null` hands the decision back to the connection's join mode. `PATCH https://api-notetaker.nabrah.ai/ext/v1/meetings/{id}` Requires an API key in the `Authorization` header. `recordOverride` outranks every auto-join rule and survives calendar re-syncs, because the sync never writes that column. `true` records it regardless of the rules, `false` never records it, `null` hands the decision back to the connection's join mode. ## Path parameters - `id` (string · uuid, required) ## Body - `title` (string) - `link` (string · uri) - `description` (string) - `startsAt` (string · date-time) - `durationMinutes` (integer) - `recordOverride` (boolean) ## Example request body ```json { "title": "string", "link": "string", "description": "string", "startsAt": "2026-01-01T09:00:00.000Z", "durationMinutes": 0, "recordOverride": true } ``` ## Responses - `200` — One meeting. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "link": "string", "description": "string", "location": "string", "startsAt": "2026-01-01T09:00:00.000Z", "durationMinutes": 0, "source": "manual", "isAllDay": true, "isRecurring": true, "attendees": [ {} ], "attendeeCount": 0, "organizer": {}, "responseStatus": "string", "externalUrl": "string", "recordOverride": true, "createdAt": "2026-01-01T09:00:00.000Z" } } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Remove a meeting from Nabrah > A meeting added by hand is deleted. One that came from a connected calendar is tombstoned instead — deleting it outright would only have the next sync bring it back. Either way it stops appearing and no bot will be sent. `DELETE https://api-notetaker.nabrah.ai/ext/v1/meetings/{id}` Requires an API key in the `Authorization` header. A meeting added by hand is deleted. One that came from a connected calendar is tombstoned instead — deleting it outright would only have the next sync bring it back. Either way it stops appearing and no bot will be sent. ## Path parameters - `id` (string · uuid, required) ## Responses - `200` — Done. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": {} } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # List bot runs > Every recording this account has, newest first — the same rows as `/recordings`, under the noun that fits what you are doing with them. `GET https://api-notetaker.nabrah.ai/ext/v1/bots` Requires an API key in the `Authorization` header. Every recording this account has, newest first — the same rows as `/recordings`, under the noun that fits what you are doing with them. ## Query parameters - `limit` (integer) Default: `25`. - `offset` (integer) Default: `0`. ## Responses - `200` — A page of recordings. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": { "rows": [ { "runId": "00000000-0000-0000-0000-000000000000", "reference": "string", "meetingId": "string", "title": "string", "startsAt": "2026-01-01T09:00:00.000Z", "platform": "string", "origin": "calendar", "state": "string", "phase": "string", "recordingSeconds": 0, "endedAt": "string", "hasNotes": true } ], "total": 0, "limit": 0, "offset": 0 } } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Send the notetaker into a meeting > Dispatches a bot to a live meeting link. Three things are worth knowing before you call it: only **one bot per account** can be live at a time, so a second call returns 409 and retrying will not help; it **counts against the monthly recording allowance**, and at the cap returns 409 with the reason; and the link must be on a supported platform. Send an `Idempotency-Key` so a retry after a timeout cannot start a second bot. `POST https://api-notetaker.nabrah.ai/ext/v1/bots` Requires an API key in the `Authorization` header. Dispatches a bot to a live meeting link. Three things are worth knowing before you call it: only **one bot per account** can be live at a time, so a second call returns 409 and retrying will not help; it **counts against the monthly recording allowance**, and at the cap returns 409 with the reason; and the link must be on a supported platform. Send an `Idempotency-Key` so a retry after a timeout cannot start a second bot. ## Body - `joinUrl` (string · uri, required) - `title` (string) ## Example request body ```json { "joinUrl": "string", "title": "string" } ``` ## Responses - `201` — The bot was dispatched. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `409` — Either a bot is already live for this account, or the monthly allowance is spent. The message says which. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 201 example ```json { "data": {} } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 409 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Check on a bot > Whether it has joined, how long it has been recording, and what it is doing now. `GET https://api-notetaker.nabrah.ai/ext/v1/bots/{runId}` Requires an API key in the `Authorization` header. Whether it has joined, how long it has been recording, and what it is doing now. ## Path parameters - `runId` (string · uuid, required) ## Responses - `200` — One meeting with its bot and notes. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": {} } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Pull the notetaker out of a call > Ends the recording early. Whatever was captured up to that point is still processed into notes. A run that has already finished answers 404, because there is nothing left to stop. `POST https://api-notetaker.nabrah.ai/ext/v1/bots/{runId}/stop` Requires an API key in the `Authorization` header. Ends the recording early. Whatever was captured up to that point is still processed into notes. A run that has already finished answers 404, because there is nothing left to stop. ## Path parameters - `runId` (string · uuid, required) ## Responses - `200` — Done. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": {} } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # List recordings > Every recording, newest first — from calendar meetings, manual joins and uploads alike. A run superseded by a rescheduled meeting is left out, so one meeting never appears twice. `GET https://api-notetaker.nabrah.ai/ext/v1/recordings` Requires an API key in the `Authorization` header. Every recording, newest first — from calendar meetings, manual joins and uploads alike. A run superseded by a rescheduled meeting is left out, so one meeting never appears twice. ## Query parameters - `limit` (integer) Default: `25`. - `offset` (integer) Default: `0`. ## Responses - `200` — A page of recordings. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": { "rows": [ { "runId": "00000000-0000-0000-0000-000000000000", "reference": "string", "meetingId": "string", "title": "string", "startsAt": "2026-01-01T09:00:00.000Z", "platform": "string", "origin": "calendar", "state": "string", "phase": "string", "recordingSeconds": 0, "endedAt": "string", "hasNotes": true } ], "total": 0, "limit": 0, "offset": 0 } } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Read a recording's summary and action items > The summary, attendees, topics and action items. **The transcript is left out by default** — it is far larger than everything else combined, and most callers never look at it. Add `?include=transcript` when you want it, or use the transcript endpoint to page through it. `GET https://api-notetaker.nabrah.ai/ext/v1/recordings/{runId}` Requires an API key in the `Authorization` header. The summary, attendees, topics and action items. **The transcript is left out by default** — it is far larger than everything else combined, and most callers never look at it. Add `?include=transcript` when you want it, or use the transcript endpoint to page through it. ## Path parameters - `runId` (string · uuid, required) ## Responses - `200` — A recording's summary and action items. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": {} } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Read the transcript > Utterances in order, with speaker and millisecond offsets, paged. A recording shared with you at summary scope returns an empty list and says so in `scope`, rather than pretending there was nothing said. `GET https://api-notetaker.nabrah.ai/ext/v1/recordings/{runId}/transcript` Requires an API key in the `Authorization` header. Utterances in order, with speaker and millisecond offsets, paged. A recording shared with you at summary scope returns an empty list and says so in `scope`, rather than pretending there was nothing said. ## Path parameters - `runId` (string · uuid, required) ## Query parameters - `limit` (integer) Default: `25`. - `offset` (integer) Default: `0`. ## Responses - `200` — A page of utterances. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": { "rows": [ {} ], "total": 0, "limit": 0, "offset": 0 } } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # List a recording's action items > What the analysis pulled out as things to do, with owner and due date where it could tell. `GET https://api-notetaker.nabrah.ai/ext/v1/recordings/{runId}/action-items` Requires an API key in the `Authorization` header. What the analysis pulled out as things to do, with owner and due date where it could tell. ## Path parameters - `runId` (string · uuid, required) ## Responses - `200` — A page of action items. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": { "rows": [ { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "owner": "string", "due": "string", "status": "string", "context": "string", "tags": [ null ] } ], "total": 0, "limit": 0, "offset": 0 } } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Download the audio > Streams the recording, honouring `Range` so a player can seek. Requires full access to the recording: one shared with you at summary scope answers 404. `GET https://api-notetaker.nabrah.ai/ext/v1/recordings/{runId}/audio` Requires an API key in the `Authorization` header. Streams the recording, honouring `Range` so a player can seek. Requires full access to the recording: one shared with you at summary scope answers 404. ## Path parameters - `runId` (string · uuid, required) ## Responses - `200` — The audio stream. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Tick an action item, or reopen it > Marks the item done or active. Doing this also marks the item as a person's rather than the model's, which is what stops a later re-analysis of the same meeting from quietly reopening it. `PATCH https://api-notetaker.nabrah.ai/ext/v1/action-items/{itemId}` Requires an API key in the `Authorization` header. Marks the item done or active. Doing this also marks the item as a person's rather than the model's, which is what stops a later re-analysis of the same meeting from quietly reopening it. ## Path parameters - `itemId` (string · uuid, required) ## Body - `status` (enum, required) One of: `active`, `done`. ## Example request body ```json { "status": "active" } ``` ## Responses - `200` — One action item. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": {} } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # List connected calendars > Which calendars are connected, their sync health, and the auto-join rules on each. Never carries an access token, a sync token or a webhook secret. `GET https://api-notetaker.nabrah.ai/ext/v1/calendar/connections` Requires an API key in the `Authorization` header. Which calendars are connected, their sync health, and the auto-join rules on each. Never carries an access token, a sync token or a webhook secret. ## Responses - `200` — A page of calendar connections. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": { "rows": [ { "id": "00000000-0000-0000-0000-000000000000", "provider": "string", "accountEmail": "string", "calendarId": "string", "status": "active", "autoJoin": "all", "joinRules": [ null ], "barRules": [ null ], "lastSyncedAt": "string", "lastError": "string", "createdAt": "2026-01-01T09:00:00.000Z" } ], "total": 0, "limit": 0, "offset": 0 } } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Read one connection > One calendar connection: which account and calendar it covers, whether it is still syncing, when it last did, and the auto-join mode and rules currently in force on it. A connection belonging to another account answers 404. `GET https://api-notetaker.nabrah.ai/ext/v1/calendar/connections/{id}` Requires an API key in the `Authorization` header. One calendar connection: which account and calendar it covers, whether it is still syncing, when it last did, and the auto-join mode and rules currently in force on it. A connection belonging to another account answers 404. ## Path parameters - `id` (string · uuid, required) ## Responses - `200` — One calendar connection. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": { "id": "00000000-0000-0000-0000-000000000000", "provider": "string", "accountEmail": "string", "calendarId": "string", "status": "active", "autoJoin": "all", "joinRules": [ { "kind": "title", "value": "string" } ], "barRules": [ { "kind": "title", "value": "string" } ], "lastSyncedAt": "string", "lastError": "string", "createdAt": "2026-01-01T09:00:00.000Z" } } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Set auto-join mode and rules > `autoJoin` is `all`, `hosted` (only meetings this account organises) or `manual` (never automatically). `barRules` are checked before `joinRules`, so a bar always wins. Rule values are lowercased and trimmed on the way in, so they match how meetings are compared. `PATCH https://api-notetaker.nabrah.ai/ext/v1/calendar/connections/{id}` Requires an API key in the `Authorization` header. `autoJoin` is `all`, `hosted` (only meetings this account organises) or `manual` (never automatically). `barRules` are checked before `joinRules`, so a bar always wins. Rule values are lowercased and trimmed on the way in, so they match how meetings are compared. ## Path parameters - `id` (string · uuid, required) ## Body - `autoJoin` (enum) One of: `all`, `hosted`, `manual`. - `joinRules` (object[]) - `kind` (enum, required) One of: `title`, `email`, `domain`. - `value` (string, required) - `barRules` (object[]) - `kind` (enum, required) One of: `title`, `email`, `domain`. - `value` (string, required) ## Example request body ```json { "autoJoin": "all", "joinRules": [ { "kind": "title", "value": "string" } ], "barRules": [ { "kind": "title", "value": "string" } ] } ``` ## Responses - `200` — One calendar connection. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": { "id": "00000000-0000-0000-0000-000000000000", "provider": "string", "accountEmail": "string", "calendarId": "string", "status": "active", "autoJoin": "all", "joinRules": [ { "kind": "title", "value": "string" } ], "barRules": [ { "kind": "title", "value": "string" } ], "lastSyncedAt": "string", "lastError": "string", "createdAt": "2026-01-01T09:00:00.000Z" } } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Disconnect a calendar > Revokes the grant with the provider and stops syncing. Recordings already made are unaffected — they hang off the run, not the calendar. `DELETE https://api-notetaker.nabrah.ai/ext/v1/calendar/connections/{id}` Requires an API key in the `Authorization` header. Revokes the grant with the provider and stops syncing. Recordings already made are unaffected — they hang off the run, not the calendar. ## Path parameters - `id` (string · uuid, required) ## Responses - `200` — Done. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": {} } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Start connecting a calendar > Returns a URL that a **person** must open in a browser to grant access. This cannot be completed headlessly — there is no way to connect a calendar from a server alone. Hand the link to your user, then poll the connection list or take the `calendar.connected` webhook to learn when it finished. `POST https://api-notetaker.nabrah.ai/ext/v1/calendar/connect-links` Requires an API key in the `Authorization` header. Returns a URL that a **person** must open in a browser to grant access. This cannot be completed headlessly — there is no way to connect a calendar from a server alone. Hand the link to your user, then poll the connection list or take the `calendar.connected` webhook to learn when it finished. ## Body - `provider` (enum, required) One of: `google`, `webex`. ## Example request body ```json { "provider": "google" } ``` ## Responses - `201` — A URL a person must open. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 201 example ```json { "data": {} } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # List uploads > Meetings recorded elsewhere and sent here, with how far through processing each one is. `GET https://api-notetaker.nabrah.ai/ext/v1/uploads` Requires an API key in the `Authorization` header. Meetings recorded elsewhere and sent here, with how far through processing each one is. ## Query parameters - `limit` (integer) Default: `25`. - `offset` (integer) Default: `0`. ## Responses - `200` — A page of uploads. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": { "rows": [ { "id": "00000000-0000-0000-0000-000000000000", "runId": "00000000-0000-0000-0000-000000000000", "reference": "string", "title": "string", "kind": "audio", "stage": "pending", "filename": "string", "byteSize": 0, "durationMs": 0, "startsAt": "2026-01-01T09:00:00.000Z", "createdAt": "2026-01-01T09:00:00.000Z", "updatedAt": "2026-01-01T09:00:00.000Z", "error": "string", "hasNotes": true } ], "total": 0, "limit": 0, "offset": 0 } } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Register a recording made elsewhere > The first of two requests: this creates the row and decides from the file name whether it is audio or a transcript. Send the bytes to the content endpoint afterwards. Counts against the monthly allowance like any other recording. `POST https://api-notetaker.nabrah.ai/ext/v1/uploads` Requires an API key in the `Authorization` header. The first of two requests: this creates the row and decides from the file name whether it is audio or a transcript. Send the bytes to the content endpoint afterwards. Counts against the monthly allowance like any other recording. ## Body - `title` (string) - `filename` (string, required) - `contentType` (string) - `byteSize` (integer) - `startsAt` (string · date-time) - `kind` (enum) One of: `audio`, `transcript`. ## Example request body ```json { "title": "string", "filename": "string", "contentType": "string", "byteSize": 0, "startsAt": "2026-01-01T09:00:00.000Z", "kind": "audio" } ``` ## Responses - `201` — One upload. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 201 example ```json { "data": { "id": "00000000-0000-0000-0000-000000000000", "runId": "00000000-0000-0000-0000-000000000000", "reference": "string", "title": "string", "kind": "audio", "stage": "pending", "filename": "string", "byteSize": 0, "durationMs": 0, "startsAt": "2026-01-01T09:00:00.000Z", "createdAt": "2026-01-01T09:00:00.000Z", "updatedAt": "2026-01-01T09:00:00.000Z", "error": "string", "hasNotes": true } } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Check an upload's progress > Where the file has got to: stored, transcribing, analysing, ready or failed. `hasNotes` is what tells you the notes can be read. `GET https://api-notetaker.nabrah.ai/ext/v1/uploads/{uploadId}` Requires an API key in the `Authorization` header. Where the file has got to: stored, transcribing, analysing, ready or failed. `hasNotes` is what tells you the notes can be read. ## Path parameters - `uploadId` (string · uuid, required) ## Responses - `200` — One upload. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": { "id": "00000000-0000-0000-0000-000000000000", "runId": "00000000-0000-0000-0000-000000000000", "reference": "string", "title": "string", "kind": "audio", "stage": "pending", "filename": "string", "byteSize": 0, "durationMs": 0, "startsAt": "2026-01-01T09:00:00.000Z", "createdAt": "2026-01-01T09:00:00.000Z", "updatedAt": "2026-01-01T09:00:00.000Z", "error": "string", "hasNotes": true } } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Delete an upload > Removes the upload and everything derived from it — the recording, its notes and its transcript. `DELETE https://api-notetaker.nabrah.ai/ext/v1/uploads/{uploadId}` Requires an API key in the `Authorization` header. Removes the upload and everything derived from it — the recording, its notes and its transcript. ## Path parameters - `uploadId` (string · uuid, required) ## Responses - `200` — Done. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": {} } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Send the file > The raw body is the file — not multipart, not base64. Audio is streamed straight through and never buffered, so a large file is fine. Whether this is read as audio or as a transcript was settled when the row was created, from the file name; sending the wrong kind is refused rather than guessed at. `PUT https://api-notetaker.nabrah.ai/ext/v1/uploads/{uploadId}/content` Requires an API key in the `Authorization` header. The raw body is the file — not multipart, not base64. Audio is streamed straight through and never buffered, so a large file is fine. Whether this is read as audio or as a transcript was settled when the row was created, from the file name; sending the wrong kind is refused rather than guessed at. ## Path parameters - `uploadId` (string · uuid, required) ## Responses - `200` — One upload. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": { "id": "00000000-0000-0000-0000-000000000000", "runId": "00000000-0000-0000-0000-000000000000", "reference": "string", "title": "string", "kind": "audio", "stage": "pending", "filename": "string", "byteSize": 0, "durationMs": 0, "startsAt": "2026-01-01T09:00:00.000Z", "createdAt": "2026-01-01T09:00:00.000Z", "updatedAt": "2026-01-01T09:00:00.000Z", "error": "string", "hasNotes": true } } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Retry a failed upload > Re-runs processing on the file already stored. Nothing is re-sent, so this is cheap and safe after a transient analysis failure. `POST https://api-notetaker.nabrah.ai/ext/v1/uploads/{uploadId}/retry` Requires an API key in the `Authorization` header. Re-runs processing on the file already stored. Nothing is re-sent, so this is cheap and safe after a transient analysis failure. ## Path parameters - `uploadId` (string · uuid, required) ## Responses - `200` — One upload. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": { "id": "00000000-0000-0000-0000-000000000000", "runId": "00000000-0000-0000-0000-000000000000", "reference": "string", "title": "string", "kind": "audio", "stage": "pending", "filename": "string", "byteSize": 0, "durationMs": 0, "startsAt": "2026-01-01T09:00:00.000Z", "createdAt": "2026-01-01T09:00:00.000Z", "updatedAt": "2026-01-01T09:00:00.000Z", "error": "string", "hasNotes": true } } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Seats, usage and what needs attention > The organization's plan, seats used against seats held, this month's recordings and minutes against the allowance, and counts of things wanting a manager. Requires an organization-wide key; a personal key answers 404. `GET https://api-notetaker.nabrah.ai/ext/v1/organization` Requires an API key in the `Authorization` header. The organization's plan, seats used against seats held, this month's recordings and minutes against the allowance, and counts of things wanting a manager. Requires an organization-wide key; a personal key answers 404. ## Responses - `200` — Seats, usage and attention. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": {} } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # List members > Who is in the organization, their role, what they have recorded this month and the limits in force for them. `GET https://api-notetaker.nabrah.ai/ext/v1/organization/members` Requires an API key in the `Authorization` header. Who is in the organization, their role, what they have recorded this month and the limits in force for them. ## Query parameters - `term` (string) - `role` (enum) One of: `all`, `owner`, `admin`, `member`. Default: `all`. - `limit` (integer) Default: `25`. - `offset` (integer) Default: `0`. ## Responses - `200` — A page of members. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": { "rows": [ {} ], "total": 0, "limit": 0, "offset": 0 } } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Read one member > One member's usage and effective limits. A user id outside this organization answers 404, so an id cannot be used to probe for accounts elsewhere. `GET https://api-notetaker.nabrah.ai/ext/v1/organization/members/{userId}` Requires an API key in the `Authorization` header. One member's usage and effective limits. A user id outside this organization answers 404, so an id cannot be used to probe for accounts elsewhere. ## Path parameters - `userId` (string · uuid, required) ## Responses - `200` — One member. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": {} } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Usage over time > Counts and durations across the organization: totals, by week, by hour of day, by platform, and per member. **Never meeting content** — no titles, no attendees, no transcripts, and no ids that could be used to fetch any. `GET https://api-notetaker.nabrah.ai/ext/v1/organization/analytics` Requires an API key in the `Authorization` header. Counts and durations across the organization: totals, by week, by hour of day, by platform, and per member. **Never meeting content** — no titles, no attendees, no transcripts, and no ids that could be used to fetch any. ## Query parameters - `days` (integer) Default: `30`. ## Responses - `200` — Counts and durations only. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": {} } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # List outstanding invitations > Who has been asked to join and has not yet accepted, with when each invitation lapses. `GET https://api-notetaker.nabrah.ai/ext/v1/organization/invites` Requires an API key in the `Authorization` header. Who has been asked to join and has not yet accepted, with when each invitation lapses. ## Responses - `200` — Outstanding invitations. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": [ null ] } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Invite people > Sends an invitation to each address. Each result says what happened to that one: `sent`, `already_member`, `self`, or `failed` with a reason — one bad address does not fail the rest. A key can invite an admin or a member, never an owner. `POST https://api-notetaker.nabrah.ai/ext/v1/organization/invites` Requires an API key in the `Authorization` header. Sends an invitation to each address. Each result says what happened to that one: `sent`, `already_member`, `self`, or `failed` with a reason — one bad address does not fail the rest. A key can invite an admin or a member, never an owner. ## Body - `emails` (string · email[], required) - `role` (enum) One of: `admin`, `member`. Default: `member`. ## Example request body ```json { "emails": [ "you@example.com" ], "role": "admin" } ``` ## Responses - `201` — One result per address. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 201 example ```json { "data": [ null ] } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # List webhook endpoints > Your registered endpoints, their health, and why any of them was disabled. Never returns the signing secret. `GET https://api-notetaker.nabrah.ai/ext/v1/webhooks` Requires an API key in the `Authorization` header. Your registered endpoints, their health, and why any of them was disabled. Never returns the signing secret. ## Responses - `200` — A page of endpoints. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": { "rows": [ { "id": "00000000-0000-0000-0000-000000000000", "url": "string", "events": [ null ], "status": "active", "consecutiveFailures": 0, "lastDeliveryAt": "string", "lastStatus": 0, "disabledReason": "string", "createdAt": "2026-01-01T09:00:00.000Z" } ], "total": 0, "limit": 0, "offset": 0 } } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Register a webhook endpoint > **The signing secret is in this response and in no other.** Store it before you close the connection; it cannot be retrieved afterwards. The URL must be https on port 443 and must resolve to a public address — it is re-checked on every delivery, not only here. `POST https://api-notetaker.nabrah.ai/ext/v1/webhooks` Requires an API key in the `Authorization` header. **The signing secret is in this response and in no other.** Store it before you close the connection; it cannot be retrieved afterwards. The URL must be https on port 443 and must resolve to a public address — it is re-checked on every delivery, not only here. ## Body - `url` (string · uri, required) - `events` (enum[], required) ## Example request body ```json { "url": "string", "events": [ "meeting.started" ] } ``` ## Responses - `201` — The endpoint, with its signing secret — returned once and never again. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 201 example ```json { "data": { "id": "00000000-0000-0000-0000-000000000000", "url": "string", "events": [ "string" ], "status": "active", "consecutiveFailures": 0, "lastDeliveryAt": "string", "lastStatus": 0, "disabledReason": "string", "createdAt": "2026-01-01T09:00:00.000Z" } } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Read one endpoint > Its events, its recent delivery health, and — if we stopped sending to it — the reason. `GET https://api-notetaker.nabrah.ai/ext/v1/webhooks/{id}` Requires an API key in the `Authorization` header. Its events, its recent delivery health, and — if we stopped sending to it — the reason. ## Path parameters - `id` (string · uuid, required) ## Responses - `200` — One endpoint. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": { "id": "00000000-0000-0000-0000-000000000000", "url": "string", "events": [ "string" ], "status": "active", "consecutiveFailures": 0, "lastDeliveryAt": "string", "lastStatus": 0, "disabledReason": "string", "createdAt": "2026-01-01T09:00:00.000Z" } } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Change an endpoint, or re-enable it > Change the URL or the events. Sending `status: "active"` re-enables an endpoint we disabled and clears its failure count — without clearing it, one more failure would disable it again immediately. `PATCH https://api-notetaker.nabrah.ai/ext/v1/webhooks/{id}` Requires an API key in the `Authorization` header. Change the URL or the events. Sending `status: "active"` re-enables an endpoint we disabled and clears its failure count — without clearing it, one more failure would disable it again immediately. ## Path parameters - `id` (string · uuid, required) ## Body - `url` (string · uri) - `events` (enum[]) - `status` (string) ## Example request body ```json { "url": "string", "events": [ "meeting.started" ], "status": "string" } ``` ## Responses - `200` — One endpoint. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": { "id": "00000000-0000-0000-0000-000000000000", "url": "string", "events": [ "string" ], "status": "active", "consecutiveFailures": 0, "lastDeliveryAt": "string", "lastStatus": 0, "disabledReason": "string", "createdAt": "2026-01-01T09:00:00.000Z" } } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Delete an endpoint > Stops delivery immediately and removes the endpoint along with its delivery history. Events already queued for it are dropped. There is no way to undo this — register a new endpoint instead, which will have a new signing secret. `DELETE https://api-notetaker.nabrah.ai/ext/v1/webhooks/{id}` Requires an API key in the `Authorization` header. Stops delivery immediately and removes the endpoint along with its delivery history. Events already queued for it are dropped. There is no way to undo this — register a new endpoint instead, which will have a new signing secret. ## Path parameters - `id` (string · uuid, required) ## Responses - `200` — Done. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": {} } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # Send a test event > Queues one synthetic delivery, through the ordinary queue rather than a shortcut — so what you receive is exactly shaped like a real event, signature and all. A disabled endpoint answers 409. `POST https://api-notetaker.nabrah.ai/ext/v1/webhooks/{id}/test` Requires an API key in the `Authorization` header. Queues one synthetic delivery, through the ordinary queue rather than a shortcut — so what you receive is exactly shaped like a real event, signature and all. A disabled endpoint answers 409. ## Path parameters - `id` (string · uuid, required) ## Responses - `202` — The test delivery was queued. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 202 example ```json { "data": {} } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # See what we tried to send > Every attempt on this endpoint with its status, timing and error — the first place to look when an event did not arrive. `GET https://api-notetaker.nabrah.ai/ext/v1/webhooks/{id}/deliveries` Requires an API key in the `Authorization` header. Every attempt on this endpoint with its status, timing and error — the first place to look when an event did not arrive. ## Path parameters - `id` (string · uuid, required) ## Query parameters - `limit` (integer) Default: `25`. - `offset` (integer) Default: `0`. ## Responses - `200` — A page of delivery attempts. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": { "rows": [ {} ], "total": 0, "limit": 0, "offset": 0 } } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` --- # See which account this key acts as > Returns the account the key belongs to, the organization it reaches if it is organization-wide, its access level, and the plan in force. Use it to confirm a key is live and to discover whether organization endpoints are available to it. `GET https://api-notetaker.nabrah.ai/ext/v1/me` Requires an API key in the `Authorization` header. Returns the account the key belongs to, the organization it reaches if it is organization-wide, its access level, and the plan in force. Use it to confirm a key is live and to discover whether organization endpoints are available to it. ## Responses - `200` — The account behind the key. - `401` — No key, an unknown key, or one that has been revoked or has expired. - `403` — The key is read only, or lacks the reach for this route. - `404` — No such record — or one that belongs to another account. - `422` — The body or query failed validation; `details` names the field. - `429` — Too many requests. `Retry-After` says how long to wait. ### 200 example ```json { "data": { "account": { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "email": "you@example.com" }, "organization": { "id": "00000000-0000-0000-0000-000000000000", "name": "string" }, "key": { "id": "00000000-0000-0000-0000-000000000000", "access": "readonly", "organizationWide": true }, "plan": { "key": "string", "paid": true } } } ``` ### 401 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 403 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 404 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 422 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ``` ### 429 example ```json { "error": { "code": "string", "message": "string", "details": [ { "path": "string", "message": "string" } ] } } ```