REST API

Zime API Reference

A read-only REST API for your company's sales conversation data. Pull call records with deal and account context, evidence-backed insights, and full transcripts into your warehouse, BI stack, or AI tooling.

Base URL
https://embedding-api-prod.zime.ai

Watch: 3-minute API walkthrough

A quick tour of all three endpoints, the required date parameters, and how cursor-based pagination works.

Understanding API calls and data pagination. Also available on Loom.

Authentication

Every request must include your company API key in the X-API-Key header. Keys are issued per company, and every response is scoped to your company's data. To get a key, ask your Zime administrator or contact dev@zime.ai.

cURL
curl -G 'https://embedding-api-prod.zime.ai/api/v1/calls' \
  -H 'X-API-Key: YOUR_API_KEY' \
  --data-urlencode 'start-date=2026-06-01' \
  --data-urlencode 'end-date=2026-06-30'

Treat API keys like passwords: keep them server-side, never ship them in browser code, and rotate them if you suspect exposure.

Response format & pagination

All endpoints return JSON with two top-level keys: data (an array of records) and pagination. All endpoints also require a date range via start-date and end-date in YYYY-MM-DD format.

Response envelope
{
  "data": [ ... ],
  "pagination": {
    "has_more": true,
    "next_cursor": 12245,
    "page_size": 100,
    "returned_count": 100
  }
}

Pagination is cursor-based. While has_more is true, pass the next_cursor value as the after-key query parameter on your next request to fetch the following page.

EndpointMax page sizeCursor format
/api/v1/calls1000Integer call ID
/api/v1/calls/insight100Composite string call_id-insight_id
/api/v1/calls/transcript100Integer call ID

Exporting a large date range

Every response is one page of a larger result set. To pull a full history, loop over date chunks and paginate within each chunk: two nested loops, not one. The script below does both.

Page size controls response size, not the date range

These are independent. A narrower date range returns fewer pages; it does not make any single page smaller. If a response is too large for your runtime to hold, lower page-size. Shrinking the date window will not help.

Transcripts are the ones to watch, at roughly 39 KB per record:

page-size on /api/v1/calls/transcriptApproximate response size
100 (default)~3.8 MB
50~1.9 MB
25~0.85 MB
10~0.38 MB

If you are calling from a runtime with a memory ceiling, such as Salesforce Apex, AWS Lambda or Google Apps Script, use a page-size of 10 to 25 for transcripts. Salesforce Apex in particular allows 6 MB of heap synchronously and 12 MB asynchronously, and JSON parsing expands a payload well beyond its transferred size.

Chunk the date range, and overlap the chunks

A call is returned when it starts inside the requested window and ends before the window closes. A call that begins shortly before a chunk boundary and ends after it therefore belongs to neither adjacent chunk, and consecutive chunks would skip it.

Extend each chunk a couple of days past its end so the earlier chunk always captures such a call, then discard records you have already seen. The script does this by default.

Why pages are sized the way they are

A page is bounded by the work it costs to build. An insight request resolves the calls in the requested window before returning rows, so its cost tracks the width of that window rather than the number of records. That is why narrowing the date range speeds it up and lowering page-size does not. A transcript request fetches one stored file per call, so it is bounded by response size instead.

Chunked pagination is how large REST APIs return bulk data, and the Zime limits are at or above the norm:

ZimeGongFireflies
Records per pageUp to 1000 on calls, 100 on insights and transcripts10050
Caller can choose the page sizeYesNo, fixed at 100Yes

Zime cursors are plain record identifiers and do not expire, so a long export can run for hours, stop, and resume the next day from where it left off.

Export script

Python 3.8 or later, standard library only, so there is nothing to install. It chunks the date range, paginates within each chunk, removes duplicates, retries on rate limits, and resumes if interrupted.

zime_export.py
#!/usr/bin/env python3
"""Export Zime API data over a date range. Python 3.8+, standard library only.

Two loops: an outer loop over date chunks, an inner cursor-pagination loop
within each chunk. Chunks overlap by two days because a call that starts
before a chunk boundary and ends after it belongs to neither side; duplicates
from the overlap are removed automatically.

  export ZIME_API_KEY=your_key
  python3 zime_export.py --endpoint insight \
      --start-date 2026-01-01 --end-date 2026-08-31 --out insights.ndjson

Re-run the same command to resume an interrupted export.
"""
import argparse, json, os, sys, time, urllib.error, urllib.parse, urllib.request
from datetime import datetime, timedelta

BASE_URL = 'https://embedding-api-prod.zime.ai'
FMT = '%Y-%m-%d'
ENDPOINTS = {
    'calls':      {'path': '/api/v1/calls',            'max_page': 1000, 'key': ('call_id',)},
    'insight':    {'path': '/api/v1/calls/insight',    'max_page': 100,  'key': ('call_id', 'insight_id')},
    'transcript': {'path': '/api/v1/calls/transcript', 'max_page': 100,  'key': ('call_id',)},
}


def log(m):
    print('%s  %s' % (datetime.now().strftime('%H:%M:%S'), m), file=sys.stderr, flush=True)


def chunks(start, end, days, overlap):
    cur, final = datetime.strptime(start, FMT), datetime.strptime(end, FMT)
    while cur <= final:
        nominal = min(cur + timedelta(days=days - 1), final)
        yield cur.strftime(FMT), min(nominal + timedelta(days=overlap), final).strftime(FMT)
        cur = nominal + timedelta(days=1)


def fetch(url, key, timeout, retries=5):
    for attempt in range(retries + 1):
        try:
            req = urllib.request.Request(url, headers={'X-API-Key': key})
            with urllib.request.urlopen(req, timeout=timeout) as r:
                return json.loads(r.read().decode())
        except urllib.error.HTTPError as e:
            if e.code != 429 and e.code < 500:
                raise SystemExit('HTTP %d: %s' % (e.code, e.read().decode('utf-8', 'replace')[:300]))
            reason = 'HTTP %d' % e.code
        except Exception as e:
            reason = type(e).__name__
        if attempt == retries:
            raise SystemExit('giving up after %d retries (%s)' % (retries, reason))
        wait = min(60, 2 ** (attempt + 1))
        log('  %s - retrying in %ds' % (reason, wait))
        time.sleep(wait)


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument('--endpoint', required=True, choices=sorted(ENDPOINTS))
    ap.add_argument('--api-key', default=os.environ.get('ZIME_API_KEY'))
    ap.add_argument('--start-date', required=True)
    ap.add_argument('--end-date', required=True)
    ap.add_argument('--out', required=True)
    ap.add_argument('--chunk-days', type=int, default=30)
    ap.add_argument('--overlap-days', type=int, default=2)
    ap.add_argument('--page-size', type=int, default=None)
    ap.add_argument('--delay', type=float, default=1.0)
    ap.add_argument('--timeout', type=int, default=300)
    a = ap.parse_args()

    if not a.api_key:
        raise SystemExit('set ZIME_API_KEY or pass --api-key')
    spec = ENDPOINTS[a.endpoint]
    page_size = min(a.page_size or spec['max_page'], spec['max_page'])

    ck = a.out + '.checkpoint'
    state = (json.load(open(ck)) if os.path.exists(ck)
             else {'done': [], 'seen': [], 'n': 0, 'dropped': 0, 'offset': 0})
    done = set(map(tuple, state['done']))
    seen = set(map(tuple, state['seen']))
    dropped = state['dropped']

    todo = list(chunks(a.start_date, a.end_date, a.chunk_days, a.overlap_days))
    log('%s: %d chunk(s) of %dd (+%dd overlap), page-size %d'
        % (a.endpoint, len(todo), a.chunk_days, a.overlap_days, page_size))

    # Truncate back to the last completed chunk. A chunk interrupted mid-run has
    # already flushed rows to disk that the checkpoint never recorded; without this
    # they would be written a second time on resume.
    if done and os.path.exists(a.out):
        with open(a.out, 'r+') as f:
            f.truncate(state['offset'])

    with open(a.out, 'a' if done else 'w') as out:
        for i, (cs, ce) in enumerate(todo, 1):
            if (cs, ce) in done:
                continue
            cursor, written = None, 0
            while True:
                q = {'start-date': cs, 'end-date': ce, 'page-size': str(page_size)}
                if cursor is not None:
                    q['after-key'] = str(cursor)
                d = fetch(BASE_URL + spec['path'] + '?' + urllib.parse.urlencode(q), a.api_key, a.timeout)
                for row in d.get('data') or []:
                    rk = tuple(row.get(f) for f in spec['key'])
                    if rk in seen:
                        dropped += 1
                        continue
                    seen.add(rk)
                    out.write(json.dumps(row, separators=(',', ':')) + '\n')
                    written += 1
                out.flush()
                page = d.get('pagination') or {}
                cursor = page.get('next_cursor')
                if not page.get('has_more') or cursor is None:
                    break
                time.sleep(a.delay)
            state.update(n=state['n'] + written, dropped=dropped, offset=out.tell(),
                         seen=[list(k) for k in seen], done=state['done'] + [[cs, ce]])
            done.add((cs, ce))
            json.dump(state, open(ck, 'w'))
            log('[%d/%d] %s..%s  +%d  (total %d)' % (i, len(todo), cs, ce, written, state['n']))
            time.sleep(a.delay)

    log('done: %d record(s) -> %s' % (state['n'], a.out))
    if dropped:
        log('removed %d duplicate row(s)' % dropped)
    os.remove(ck)


if __name__ == '__main__':
    main()
Usage
export ZIME_API_KEY=your_api_key

# a full year of insights
python3 zime_export.py --endpoint insight \
  --start-date 2026-01-01 --end-date 2026-12-31 \
  --out insights.ndjson

# transcripts from a memory-constrained runtime
python3 zime_export.py --endpoint transcript \
  --start-date 2026-01-01 --end-date 2026-12-31 \
  --page-size 25 --out transcripts.ndjson

Run it sequentially rather than in parallel. Several concurrent exports will slow each other down and offer no gain, since the limit is how fast each request completes, not how many you can have open.

Errors

Errors return an error field with a human-readable message:

Error response
{ "error": "start-date parameter is required (format: YYYY-MM-DD)" }
StatusMeaning
400Bad request: missing or invalid parameters, such as a malformed date or a malformed after-key. A page-size above the endpoint maximum is not an error; it is clamped to the maximum.
401Unauthorized: the X-API-Key header is missing.
403Forbidden: the API key is not valid.
500Internal server error. Retry with backoff.
504Gateway timeout. Narrow the date range and retry. Reducing page-size does not help here: the cost of an insight request is driven by the width of the date window, not by the number of records returned.

GET/api/v1/calls

Returns call records for your company within a date range. Each record carries the call's timing plus its CRM context: the linked deal, deal owner, stage at call time, current stage, and every linked account.

Query parameters

ParameterTypeRequiredDescription
start-datestringYesStart date, YYYY-MM-DD.
end-datestringYesEnd date, YYYY-MM-DD.
page-sizeintegerNoRecords per page, 1 to 1000. Default 100.
after-keyintegerNoPagination cursor: the call ID to start after. Use the next_cursor from the previous page.

Example request

cURL
curl -G 'https://embedding-api-prod.zime.ai/api/v1/calls' \
  -H 'X-API-Key: YOUR_API_KEY' \
  --data-urlencode 'start-date=2026-06-01' \
  --data-urlencode 'end-date=2026-06-30' \
  --data-urlencode 'page-size=200'

Example response

JSON
{
  "data": [
    {
      "call_id": 84396,
      "call_title": "Outbound call to Ray Burch",
      "start_time": "2026-06-07T22:48:23",
      "end_time": "2026-06-07T22:48:39",
      "deal_id": "OPP-12345",
      "deal_name": "Enterprise Deal Q4",
      "deal_owner": "John Doe",
      "deal_type": "New Business",
      "deal_stage_during_call": "Qualification",
      "current_deal_stage": "Discovery",
      "accounts": [
        {
          "account_id": "0012M00002AWMsgQAH",
          "account_name": "TechCorp Industries"
        }
      ]
    }
  ],
  "pagination": {
    "has_more": true,
    "next_cursor": 84396,
    "page_size": 200,
    "returned_count": 200
  }
}

The accounts array is the union of accounts referenced by the deal and by the calendar event, de-duplicated by account_id. It is empty when no accounts are linked.

GET/api/v1/calls/insight

Returns call insights with outcome details. Each row is one insight on one call: a named signal (for example a budget discussion), its category and sub-category, the evidence quote from the conversation, and an intensity score. A call link points back to the recording in Zime.

Query parameters

ParameterTypeRequiredDescription
start-datestringYesStart date, YYYY-MM-DD.
end-datestringYesEnd date, YYYY-MM-DD.
page-sizeintegerNoRecords per page, 1 to 100. Default 100.
after-keystringNoPagination cursor in call_id-insight_id format, since one call can have many insights.

Example request

cURL
curl -G 'https://embedding-api-prod.zime.ai/api/v1/calls/insight' \
  -H 'X-API-Key: YOUR_API_KEY' \
  --data-urlencode 'start-date=2026-06-01' \
  --data-urlencode 'end-date=2026-06-30' \
  --data-urlencode 'after-key=38419-12345'

Example response

JSON
{
  "data": [
    {
      "call_id": 38419,
      "call_title": "Q4 Product Demo - Enterprise Client",
      "call_link": "https://your-company.zime.ai/recordings/38419",
      "start_time": "2026-06-15T14:30:00",
      "end_time": "2026-06-15T15:30:00",
      "insight_id": 12345,
      "title": "Budget Authority Mentioned",
      "signal_name": "Budget Discussion",
      "category": "MEDDIC",
      "sub_category": "Budget",
      "evidence": "Customer mentioned they have budget allocated for Q4",
      "intensity_score": 8.5,
      "deal_id": "OPP-12345",
      "deal_name": "Enterprise Deal Q4",
      "deal_owner": "John Doe",
      "deal_type": "New Business",
      "deal_stage_during_call": "Discovery",
      "current_deal_stage": "Proposal",
      "accounts": [
        {
          "account_id": "0012M00002AWMsgQAH",
          "account_name": "TechCorp Industries"
        }
      ]
    }
  ],
  "pagination": {
    "has_more": true,
    "next_cursor": "38419-12345",
    "page_size": 100,
    "returned_count": 100
  }
}

GET/api/v1/calls/transcript

Returns full call transcripts as parsed VTT content: an ordered list of cues, each with its timing window and text lines. Transcript files are fetched from storage per call, which is why this endpoint caps at 100 calls per page.

Query parameters

ParameterTypeRequiredDescription
start-datestringYesStart date, YYYY-MM-DD.
end-datestringYesEnd date, YYYY-MM-DD.
page-sizeintegerNoCalls per page, 1 to 100. Default 100.
after-keyintegerNoPagination cursor: the call ID to start after.

Example request

cURL
curl -G 'https://embedding-api-prod.zime.ai/api/v1/calls/transcript' \
  -H 'X-API-Key: YOUR_API_KEY' \
  --data-urlencode 'start-date=2026-06-01' \
  --data-urlencode 'end-date=2026-06-07'

Example response

JSON
{
  "data": [
    {
      "call_id": 83536,
      "call_title": "Legacy Park Advisors and IT Solutions - AI Project",
      "start_time": "2026-06-05 17:00:07",
      "end_time": "2026-06-05 17:15:51",
      "deal_id": "OPP-12345",
      "deal_name": "Enterprise Deal Q4",
      "transcript": [
        {
          "id": "1",
          "timing": "00:00:00.000 --> 00:00:05.000",
          "text": ["Hello, welcome to the call"]
        }
      ],
      "accounts": [
        {
          "account_id": "0012M00002AWMsgQAH",
          "account_name": "TechCorp Industries"
        }
      ]
    }
  ],
  "pagination": {
    "has_more": false,
    "next_cursor": 83534,
    "page_size": 100,
    "returned_count": 3
  }
}

Frequently asked questions

What data can I get from the Zime API?

Three resources, all scoped to your company: call records with deal and account context (GET /api/v1/calls), per-call insights with categories, evidence quotes, and intensity scores (GET /api/v1/calls/insight), and full parsed VTT transcripts (GET /api/v1/calls/transcript).

How do I authenticate with the Zime API?

Every request needs an X-API-Key header carrying your company API key. Keys are issued per company and scope every response to your own data. Ask your Zime administrator or dev@zime.ai for a key.

What are the page size limits?

The calls endpoint returns up to 1000 records per page. The insight and transcript endpoints are capped at 100 records per page because of heavier query and file operations. All three default to 100.

How does pagination work?

The API uses cursor-based pagination. Each response includes a pagination object with has_more and next_cursor. Pass next_cursor as the after-key query parameter on your next request. For calls and transcripts the cursor is a call ID; for insights it is a composite call_id-insight_id string.

Why do I only get a limited number of records per request?

Every response is one page of a larger result set, which is how large REST APIs return bulk data. Gong returns 100 records per page and does not let you change it; Fireflies returns at most 50. Zime lets you choose your page size, up to 1000 on the calls endpoint. To pull a full history, loop over date chunks and paginate within each chunk - see "Exporting a large date range".

Is pagination a Zime limitation?

No. Cursor pagination is standard across major APIs, and Zime is at or above the norm. Gong returns 100 records per request and Fireflies returns at most 50, while Zime lets you choose your page size and returns up to 1000 on the calls endpoint. Zime cursors are plain record identifiers that do not expire, so a long export can run for hours or resume the next day from where it stopped.

My transcript requests are too large for my runtime. What should I change?

Lower page-size. Response size is governed by page-size, not by the date range - a narrower date range returns fewer pages, but each page is the same size. Transcripts run about 39 KB per record, so a page of 100 is roughly 3.8 MB while a page of 25 is roughly 0.85 MB. If you are calling from a constrained runtime such as Salesforce Apex, AWS Lambda or Google Apps Script, use a page-size of 10 to 25 for transcripts.

Why is my insight request slow?

An insight request resolves every call in the date window before it returns a page, so the cost tracks the width of the window rather than the number of records. Narrowing the date range is the effective lever; reducing page-size is not. Requesting a month at a time keeps responses quick, and narrower chunks are quicker still.

Can I pass the API key in the URL?

No. The key must be sent in the X-API-Key request header. A key placed in the query string is ignored and the request returns 401. This also means an API URL cannot be tested by pasting it into a browser address bar, because a browser cannot attach custom headers - use curl, Postman or your own code.

Prefer a no-code integration? The Zime MCP connector puts the same calls, insights, and transcripts inside Claude, with your normal Zime login instead of an API key.