Core concepts

Agent skills

Everything an agent needs to use this API, in one file. Also served raw at /.well-known/skills.md.

The same document, raw

Agents can load it from https://notetaker.nabrah.ai/.well-known/skills.md.

A meeting notetaker. It watches a calendar, sends a bot into Google Meet, Teams and Webex calls, and turns each recording into a summary, action items and a searchable transcript in Arabic or English. Recordings made elsewhere can be uploaded and get the same notes.

  • Base URL: https://api-notetaker.nabrah.ai/ext/v1

  • Machine-readable description: https://notetaker.nabrah.ai/docs/api/openapi.json

  • MCP server: https://api-notetaker.nabrah.ai/mcp (same key)

  • Human docs: https://notetaker.nabrah.ai/en/docs/api

  • Docs index: https://notetaker.nabrah.ai/llms.txt, every page in one file: /llms-full.txt

  • Any docs page as markdown: append .md to its URL

Authentication

Send Authorization: Bearer nt_… on every request. Keys are created by a person at https://notetaker.nabrah.ai/en/dashboard/developers and are either:

AccessAllows
readonlyGET only. Any write answers 403.
fullEverything below, including sending and stopping bots.

A key from an organization owner or admin is organization-wide: the /organization endpoints answer. Every other key is personal, and those endpoints answer 404. Call GET /me first to learn which you hold.

Envelope, errors, paging

  • Success: { "data": … }. Failure: { "error": { "code", "message", "details"? } }.

  • Lists: limit (1–100, default 25) and offset; each page carries total. GET /usage/activity is the exception: it takes cursor and returns nextCursor.

  • Anything belonging to another account answers 404, never 403.

  • Rate limit: 600 requests per 15 minutes per key. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; a spent window answers 429 with Retry-After.

  • Writes accept Idempotency-Key. A retry with the same key returns the original response with Idempotency-Replayed: true. Always send one on POST /bots and POST /uploads.

StatuscodeWhat to do
401UNAUTHORIZEDThe key is missing, unknown, revoked or expired. Stop.
403FORBIDDENA read-only key tried a write. Stop.
404NOT_FOUNDNot there, or not this key's to see.
409CONFLICTThe state does not allow it (e.g. a bot is already live). Do not retry.
409RECORDING_QUOTA_REACHEDThis month's meetings are used. Resets on the 1st.
409MEETING_TOO_LONGLonger than the plan's minutes per meeting.
409UPLOAD_PENDING_LIMITThree uploads are still waiting for their file.
409UPLOAD_EXPIREDThe file came too late; create a new upload.
409USAGE_RETRYThe team changed mid-request. Send it again now.
422VALIDATION_ERRORFix the body; details names the field.
429RATE_LIMITEDWait Retry-After seconds.
5xxINTERNAL_ERRORRetry with backoff.

Usage refusals (RECORDING_QUOTA_REACHED, MEETING_TOO_LONG, UPLOAD_PENDING_LIMIT, USAGE_RETRY) are never remembered by Idempotency-Key, so the same request succeeds once there is room.

Usage and limits

Plans limit three things per calendar month: meetings, minutes per meeting, and AI credits (used by chat in the app). In a team the monthly allowance is shared by everyone; a team may also set a limit on one person. People can hold bonus meetings or credits granted by support.

  • GET /usage — the month so far. For meetings and aiCredits: planUsed against allowance (what the plan paid for), used (including bonus), available (what this key's person can still start), bonus[], mine and myLimit in a team, and reached. Also minutesPerMeeting, maxMinutes, resetsAt (the 1st) and planRenewsAt (the billing date). null always means unlimited, never zero.

  • GET /usage/activity?kind=meeting|ai_credit&cursor&limit — every entry that used a meeting or credit, and every bonus grant, newest first.

  • POST /bots and POST /uploads return a usage block — { remaining, maxMinutes, resetsAt } — so you can check before the next one.

  • Deleting a recording never gives a meeting back. Only a bot that was never let in, a failed upload, or an upload that was refused is not counted.

  • A bot leaves when the meeting reaches maxMinutes. An uploaded file longer than that is refused with MEETING_TOO_LONG and not counted.

What you can do

Know who you are

GoalCall
Which account, plan and key type this isGET /me
What is left this monthGET /usage

Calendar and schedule

GoalCall
Upcoming meetings, and whether the bot will join each (with the reason if not)GET /meetings/schedule?from&to
Meetings on the calendarGET /meetings?from&to
One meeting, with its bot status and notesGET /meetings/{id}
Add a meeting by handPOST /meetings
Force recording on or off for one meetingPATCH /meetings/{id} { recordOverride } with true, false or null
Connected calendars and their auto-join rulesGET /calendar/connections
Change auto-join mode or rulesPATCH /calendar/connections/{id}
Connect a calendarPOST /calendar/connect-links { provider } with "google", "microsoft" (Outlook / Teams) or "webex" — returns a URL a person must open

Bots

GoalCall
Send the notetaker into a call nowPOST /bots { joinUrl, title? }
Check on itGET /bots/{runId}
Pull it outPOST /bots/{runId}/stop
Recent bot runsGET /bots

Recordings and notes

GoalCall
Finished and in-progress recordingsGET /recordings
Summary, attendees and action itemsGET /recordings/{runId}
Same, with the transcriptGET /recordings/{runId}?include=transcript
The transcript, pagedGET /recordings/{runId}/transcript?limit&offset
Action itemsGET /recordings/{runId}/action-items
Tick or reopen onePATCH /action-items/{itemId} { status } with "done" or "active"
Download the audio (supports Range)GET /recordings/{runId}/audio

Uploads (recordings made elsewhere)

  1. POST /uploads { filename, title?, startsAt?, byteSize? } — audio or transcript is decided from the file name. Counts as a meeting now.

  2. PUT /uploads/{uploadId}/content with the raw file as the body (not multipart). Audio up to 500 MB; transcripts up to 20 MB.

  3. Poll GET /uploads/{uploadId} or wait for meeting.ready.

  • POST /uploads/{uploadId}/retry re-runs a failed analysis on the stored file.

  • DELETE /uploads/{uploadId} removes it (the meeting stays counted if the file arrived).

Organization (organization-wide keys only)

GoalCall
Seats, this month's usage, what needs attentionGET /organization
Members and one memberGET /organization/members, GET /organization/members/{userId}
Usage over timeGET /organization/analytics?days (totals, weekly series, members, and insights: change vs the previous window, join rate, weekdays, sources, AI questions)
InvitationsGET /organization/invites, POST /organization/invites

Webhooks

GoalCall
Register an endpointPOST /webhooks { url, events } — the response holds the signing secret, once
List, read, change, deleteGET/PATCH/DELETE /webhooks/{id}
Send a test eventPOST /webhooks/{id}/test
What was sent and how it wentGET /webhooks/{id}/deliveries

Events: meeting.started, meeting.ended, meeting.ready, meeting.failed, meeting.skipped (not recorded: this month's meetings are used), usage.threshold_reached (80% or 100% of a monthly limit), calendar.connected, calendar.disconnected, calendar.reauth_required.

Verify every delivery before parsing: HMAC-SHA256 of ${X-Nabrah-Timestamp}.${raw body} with the endpoint secret, hex, compared in constant time with X-Nabrah-Signature; reject timestamps older than five minutes. Answer 2xx within ten seconds. Failures are retried six times over about a minute; after that, read the deliveries list. Events can arrive out of order — sort by createdAt.

Recipes

Record a call happening now. GET /usage (is meetings.available above 0?) → POST /bots with an Idempotency-Key → wait for meeting.ready, or poll GET /bots/{runId} → GET /recordings/{runId}.

Record everything on a calendar. POST /calendar/connect-links → hand the URL to the person → GET /calendar/connections until it appears → PATCH its join mode → check GET /meetings/schedule for what will be recorded.

Pull notes into another system. POST /webhooks { events: ["meeting.ready"] } → on each event, GET /recordings/{runId} (and /transcript if needed) → tick action items back with PATCH /action-items/{itemId}.

Warn before running out. Subscribe to usage.threshold_reached and meeting.skipped, or read GET /usage daily and compare planUsed with allowance.

Rules worth knowing before you try

  • One live bot per account. A second POST /bots answers 409; retrying will not help. Wait for the first, or stop it.

  • Only Google Meet, Microsoft Teams and Webex links are accepted. Zoom is not.

  • The calendar holds roughly 7 days back and 30 forward. Beyond that the honest answer is "not synced", not "nothing scheduled".

  • A meeting under three minutes is not transcribed (meeting.failed with reason: "too_short") and not counted.

  • Ticking an action item marks it as a person's, so a later re-analysis will not reopen it.

  • Connecting a calendar always needs a person in a browser; never try to do it headlessly.

  • A key issued by an organization admin is revoked the moment they leave the organization.