Documentation/API Reference

    API Reference

    External integrations use the Meetric partner REST API under /rest with mt_* bearer tokens.

    Getting Started#

    REST Contract

    OpenAPI schema is the source of truth

    Scoped Tokens

    Every endpoint is controlled by token scopes

    Rate Limited

    Per-token limits and daily conversation quotas

    Base URL and OpenAPI#

    text
    REST API base URL: http://127.0.0.1:37521/api/v1/rest
    text
    OpenAPI spec: http://127.0.0.1:37521/api/v1/rest/openapi.json

    Note

    Use the OpenAPI spec above as the canonical endpoint and schema reference.

    Authentication#

    Bearer token request
    curl -X GET "http://127.0.0.1:37521/api/v1/rest/conversations?limit=25" \
      -H "Authorization: Bearer mt_your_api_token"

    Generate tokens in Settings -> REST API -> API Tokens. Tokens are shown once and should be stored securely. Webhook destinations are configured separately under Settings -> REST API -> Webhook subscriptions — see the Webhooks section below.

    Warning

    External partner integrations should use /rest endpoints only. Internal JWT endpoints under /api/v1 are not part of this partner API contract.

    Endpoint Contract#

    Current partner API endpoints and required scopes, aligned with the REST OpenAPI contract.

    MethodPathScopeDescription
    GET/rest/accountnoneGet account and branding context
    GET/rest/usersusersList active users in account
    GET/rest/teamsteamsList teams with members
    GET/rest/departmentsdepartmentsList enabled departments
    GET/rest/conversationsconversationsList conversations (30-day window, cursor pagination)
    GET/rest/conversations/:idconversationsGet conversation detail (optional transcript/insights includes)
    GET/rest/conversations/:id/transcripttranscriptsGet transcript payload for one conversation
    GET/rest/conversations/:id/insightsinsightsGet insights/topics for one conversation
    GET/rest/conversations/:id/recording-urlrecordings + include_recordings=trueGet recording URL if available
    GET/rest/conversations/:id/summaryconversationsGet stored summary text for one conversation
    POST/rest/notetaker/requestnotetakerRequest notetaker join for supported meeting URL
    GET/rest/webhooks/deliveriesnoneList recent webhook deliveries (last 7 days)

    Note

    GET /rest/conversations is limited to the last 30 days and max limit=25 per page. Use pagination.next_cursor for additional pages.

    Recordings dual-gate

    GET /rest/conversations/:id/recording-url requires both:

    • the recordings scope on the token, and
    • the include_recordings = true setting on the token.

    Missing scope returns 403 with code: missing_scope. Scope granted but token flag off returns 403 with code: recordings_not_allowed. The Settings UI couples both controls so admins cannot create an invalid combination.

    Transcript Endpoint#

    GET /rest/conversations/:id/transcript#

    Request
    curl -X GET "http://127.0.0.1:37521/api/v1/rest/conversations/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/transcript" \
      -H "Authorization: Bearer mt_your_api_token"
    Response
    {
      "conversation_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
      "transcript": {
        "sentences": [
          {
            "speaker_name": "Erik Lindstrom",
            "start": 0.84,
            "end": 3.14,
            "text": "Thanks for joining today."
          }
        ]
      },
      "_meta": {
        "token_prefix": "mt_ab12cd34",
        "timestamp": "2026-06-03T09:42:00.000Z"
      }
    }

    Tip

    Transcript shape follows stored transcript_data. Some sources use sentences, others use messages. You can also use GET /rest/conversations/:id?include_transcript=true to embed transcript in conversation detail.

    Webhooks#

    Webhook deliveries fire automatically when a conversation is fully processed (transcript, insights, and summary are ready). Webhook destinations are configured as subscriptions under Settings -> REST API -> Webhook subscriptions. Each subscription has its own URL, signing secret, scopes, optional department filter, and include_recordings flag. The signing secret is shown once on create or rotate.

    One account can have many subscriptions. Each enabled subscription receives its own delivery for the same conversation event — subscription_id identifies which one in both the payload and the /rest/webhooks/deliveries audit endpoint.

    Delivery headers#

    Headers on every delivery attempt
    X-Webhook-Signature: sha256=<hmac_sha256_of_raw_body>
    X-Webhook-Event: conversation.completed
    X-Webhook-Delivery-Id: <delivery_uuid>
    X-Webhook-Timestamp: <unix_timestamp_seconds>

    X-Webhook-Delivery-Id is stable across retries for the same delivery. Use it for idempotency. X-Webhook-Timestamp is refreshed on every attempt (including retries) so replay protection works on retries too.

    Verification and replay protection#

    1. Read the raw request body before any JSON parsing.
    2. Compute expected = HMAC_SHA256(subscription_webhook_secret, raw_body) using the signing secret of the subscription that received the delivery (matched via _meta.subscription_id).
    3. Compare expected against the hex value in X-Webhook-Signature using a constant-time comparison (for example hmac.compare_digest in Python or crypto.timingSafeEqual in Node.js).
    4. Reject the request when |now_unix_seconds - X-Webhook-Timestamp| > 300 (5 minute replay window). We never deliver payloads older than this; anything past 5 minutes is treated as a replay.
    5. Treat repeated X-Webhook-Delivery-Id values as the same delivery and skip re-processing.
    Node.js verification snippet
    import crypto from "node:crypto";
    
    function verifyMeetricWebhook(req, secret) {
      const rawBody = req.rawBody; // Buffer or string captured BEFORE JSON parsing
      const sigHeader = req.headers["x-webhook-signature"] || "";
      const tsHeader = Number(req.headers["x-webhook-timestamp"] || "0");
    
      // 1) Replay window: 5 minutes
      if (Math.abs(Math.floor(Date.now() / 1000) - tsHeader) > 300) {
        return { ok: false, reason: "replay_window_exceeded" };
      }
    
      // 2) HMAC-SHA256 of the raw body
      const expected =
        "sha256=" +
        crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
    
      // 3) Constant-time compare
      const sigBuf = Buffer.from(sigHeader);
      const expBuf = Buffer.from(expected);
      if (sigBuf.length !== expBuf.length || !crypto.timingSafeEqual(sigBuf, expBuf)) {
        return { ok: false, reason: "bad_signature" };
      }
    
      return { ok: true };
    }

    Retries and idempotency#

    Meetric retries failed deliveries with exponential backoff using the same X-Webhook-Delivery-Id. Schedule:

    • Attempt 1: immediate
    • Attempt 2: ~1 minute later
    • Attempt 3: ~5 minutes later
    • Attempt 4: ~30 minutes later
    • Attempt 5: ~2 hours later
    • Attempt 6: ~12 hours later (final)

    We consider a delivery successful when your endpoint returns a 2xx status code within 10 seconds. Use GET /rest/webhooks/deliveries to inspect recent attempts (last 7 days) across all subscriptions for your account; each row includes subscription_id, and you can filter the list by ?subscription_id=<uuid> for a per-destination view.

    conversation.completed payload#

    Example payload
    {
      "event": "conversation.completed",
      "timestamp": "2026-06-03T09:45:00.000Z",
      "data": {
        "conversation": {
          "id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
          "title": "Discovery call - Acme",
          "company": "Acme",
          "date": "2026-06-03T09:30:00.000Z",
          "duration": 1800,
          "medium": "call",
          "sentiment": "positive",
          "conversation_type": "sales",
          "summary": "Customer requested pricing follow-up.",
          "department": { "id": "11111111-0000-0000-0000-000000000001", "name": "B2B Sales" },
          "user": { "id": "67c869eb-30b1-4eb3-bd2c-be8c912a9827", "name": "Erik Lindstrom", "email": "[email protected]" },
          "participants": [
            { "name": "Jane Doe", "email": "[email protected]", "role": "customer" }
          ],
          "transcript": { "sentences": [] },
          "insights": [],
          "recording_url": "https://example.com/recording"
        }
      },
      "_meta": {
        "subscription_id": "11111111-2222-3333-4444-555555555555",
        "delivery_id": "bbbbbbbb-cccc-dddd-eeee-ffffffffffff",
        "token_prefix": "mt_ab12cd34"
      }
    }

    Scope-conditional fields

    Some fields are only included when the subscription has the matching scope:
    • transcript requires the transcripts scope.
    • insights requires the insights scope.
    • recording_url requires the recordings scope and include_recordings = true on the subscription.

    _meta fields#

    • subscription_id — canonical correlation key. Always present. Matches the subscription you configured in Settings -> REST API -> Webhook subscriptions.
    • delivery_id — same value as the X-Webhook-Delivery-Id header. Stable across retries.
    • token_prefixDEPRECATED. Retained for one minor version so existing integrations that read _meta.token_prefix keep working. Set to the prefix of a same-account API token that has the same webhook URL when one exists, otherwise null. Use subscription_id instead. This field is removed in a future release.

    Limits and Errors#

    Rate Limits#

    json
    {
      "requests_per_minute": 60,
      "daily_conversation_quota": 1000
    }

    Limits are configured per token. Daily quota counts unique conversations accessed per day. When the per-minute limit is exceeded, the response includes a Retry-After header (seconds) — honor it instead of retrying immediately.

    429 response (truncated)
    HTTP/1.1 429 Too Many Requests
    Retry-After: 60
    Content-Type: application/json
    
    {
      "statusCode": 429,
      "error": "Too Many Requests",
      "message": "Rate limit exceeded",
      "code": "rate_limit_exceeded",
      "details": { "retry_after_seconds": 60 }
    }

    Error Envelope#

    Every 4xx response on /rest/* uses the same envelope. Branch on the stable code field; treat message as human-readable text that may change between releases.

    Example error payload
    {
      "statusCode": 403,
      "error": "Forbidden",
      "message": "Token is missing required scope: transcripts",
      "code": "missing_scope",
      "details": { "required_scope": "transcripts" }
    }

    Note

    Envelope fields:

    • codestable, machine-readable identifier. Safe to branch on. We never rename or repurpose a published code.
    • message — human-readable explanation. May change between releases. Do not parse.
    • details — optional object carrying error-specific context (for example required_scope, retry_after_seconds, daily_quota).

    HTTP Status Codes#

    • 200 - request succeeded
    • 400 - invalid parameters or body
    • 401 - token missing, invalid, inactive, revoked, or expired
    • 403 - scope missing, recordings not enabled, or daily quota reached
    • 404 - resource not found in this account / not visible to this token
    • 429 - per-minute rate limit exceeded (honor Retry-After)
    • 500 - internal server error

    Stable Error Codes#

    The code field uses these stable identifiers. Use them for retry/branching logic instead of matching on the message.

    StatuscodeWhen
    401missing_api_tokenNo Authorization header on the request
    401invalid_api_tokenToken does not match any record
    401token_inactiveToken exists but is disabled
    401token_revokedToken has been revoked
    401token_expiredToken has passed its expires_at
    403missing_scopeToken is missing the scope required by this endpoint
    403recordings_not_allowedToken has 'recordings' scope but include_recordings=false
    403quota_exceededDaily unique-conversation quota reached for this token
    404conversation_not_foundConversation does not exist or is not visible to this token
    429rate_limit_exceededPer-minute request rate limit hit (honor Retry-After)

    Support#

    Note

    Questions about integration behavior or contract details? Email [email protected].