Ingest API
Send conversations and tables from any system without a built-in integration. Bulk uploads on a schedule, hourly or daily. A real-time streaming API is planned.
Getting started
- 1
Ask us to turn it on
The API is enabled per workspace by your Rippit contact. Until then, every request returns 403.
- 2
Create a token
A Rippit admin goes to Tables > Add table > Add API Table > Create API token. You can also ask Rippit in chat to connect via API.
- 3
Send it with every request
Put the token in an apitoken header. Keep it out of scripts you share and out of AI chats.
https://integrations.maestroqa.com/api/v2/bulk_ingesthttps://integrations.maestroqa.com/api/v2/openapi.jsonHow an upload works
- 1
Create the destination
A conversation source or a dataset. Calling it again with the same name is safe.
- 2
Request an upload slot
You get a one-time upload link that’s valid for 1 hour.
- 3
Upload one file
Up to 128 MB, sent straight to the upload link without your Rippit token.
- 4
Start the job
Mark the upload complete so processing starts.
- 5
Check until it’s done
Poll every 10 to 30 seconds until the job reads done or failed.
Test with about 10 conversations or 1,000 rows before sending everything.
What you can send
| Type | Format | One row is | Matched on re-send by |
|---|---|---|---|
| Conversations | JSON Lines | One conversation with all its messages | Your conversation ID |
| Tables | Parquet (recommended) or JSON Lines | One record | The primary key you choose |
Limits on the free tier
| Limit | Value |
|---|---|
| File size | 128 MB per upload |
| Uploads | 10 a minute, 100 an hour, 100 a day. Only completed uploads count |
| Messages per conversation | 10,000 |
| Columns per table | 500 |
| Higher limits | Ask your Rippit contact or the Rippit Community |
Conversations
One conversation per line in a JSON Lines file, with a conversation object and its events.
Endpoints
| Method | Path | What it does |
|---|---|---|
| POST | /conversation_sources | Create a source. Body: name, description. Returns created or exists, so it’s safe on every run |
| GET | /conversation_sources | List sources with their columns and table link (empty until the first upload) |
| POST | /conversation_blocks/bulk?source=<name> | Request an upload slot. Returns job_id, upload_url, upload_fields, expires_at, max_bytes |
| POST | /jobs/{job_id}/start | Mark the upload complete. Returns queued |
| GET | /jobs/{job_id} | Check the job until it’s done or failed |
To upload, send a multipart POST to upload_url with every upload_fields entry unchanged and the file under the field name file. Don’t send your token. Success returns 204.
Conversation fields
| Field | Required | Type | What it’s for |
|---|---|---|---|
| conversation_id | Yes | string, up to 96 characters | Your ID. Sending it again updates the conversation instead of duplicating it |
| started_at | Yes | ISO 8601 with time zone | When the conversation started |
| ended_at | No | ISO 8601 | When it ended. Defaults to the last message’s time, and can’t be earlier than started_at |
| searchable | No | object, up to 50 keys | Your own fields for filtering and reports. Values are text, numbers, true or false, or empty. No lists |
Message fields (events)
| Field | Required | Type | What it’s for |
|---|---|---|---|
| event_id | Yes | string, up to 96 characters | Unique within the conversation. “1”, “2” and so on works |
| time | Yes | ISO 8601 | When the message was sent |
| author_role | Yes | agent or customer | Who wrote it |
| body | Yes | string, up to 100,000 characters | The message text |
| author_id | No | string | Your ID for the author |
| author_email | No | string | The author’s email |
| author_name | No | string | Display name. Shows as Anonymous if missing |
| public | No | true or false (default true) | false marks an internal note |
| attributes | No | object, up to 20 keys | Your own fields on the message |
- Field names in searchable and attributes use letters, numbers and underscores, start with a letter or underscore, and are up to 64 characters.
- A source’s columns are set by the searchable fields in its first upload. Use the same fields after that. If a file matches a different source better, the upload fails and the error names that source.
- Each source becomes a table with one row per conversation.
{"conversation":{"conversation_id":"chat_8f2a","started_at":"2026-09-27T14:02:11Z","ended_at":"2026-09-27T14:09:40Z","searchable":{"channel":"web_chat","plan":"pro","csat":5}},"events":[{"event_id":"1","time":"2026-09-27T14:02:11Z","author_role":"customer","author_email":"sam@acme.com","author_name":"Sam Lee","body":"My invoice is wrong"},{"event_id":"2","time":"2026-09-27T14:03:05Z","author_role":"agent","author_id":"agent_42","author_name":"Alex Kim","body":"Sorry about that, let me check."}]}Tables
Mirror flat data like accounts or orders, and join it to conversations.
Endpoints
| Method | Path | What it does |
|---|---|---|
| POST | /datasets | Create a table. Safe to repeat with the same name |
| GET | /datasets | List tables with their keys, settings and table link |
| PATCH | /datasets/{dataset_id} | Rename it, label or hide columns, and link it to conversations |
| POST | /datasets/{dataset_id}/bulk?format=parquet|jsonl | Request an upload slot |
| POST | /jobs/{job_id}/start | Mark the upload complete |
| GET | /jobs/{job_id} | Check the job |
Rules
- Column types: int, float, string (store IDs as text), bool, datetime (converted to UTC) and list of any of those.
- Column names use letters, numbers and underscores, start with a letter or underscore, and are up to 128 characters. Names starting with two underscores are reserved.
- Update in place: set primary key columns. A row with an existing key replaces the stored one.
- Keep every row: leave the key empty and turn on append-only. Uploading the same file twice creates duplicates.
- You must pick one of the two, and the primary key can’t be changed later.
- If you set an updated-at column, the later value wins a conflict. Otherwise the latest upload wins.
- New columns in a later file are added automatically.
{"name":"accounts","description":"One row per customer account, synced nightly from the billing database.","columns":[{"column":"account_id","type":"string"},{"column":"plan","type":"string"},{"column":"seats","type":"int"},{"column":"mrr","type":"float"},{"column":"is_active","type":"bool"},{"column":"updated_at","type":"datetime"}],"pkey_cols":["account_id"],"updated_at_col":"updated_at"}Linking to conversations
- To join a table to conversations, add a text column called conversation_id with IDs that match your conversation uploads, then set it as the conversation link. One or many rows per conversation both work.
- Renaming changes the label only. The table’s ID and name stay the same.
{"label":"Accounts (billing)","columns":{"mrr":{"label":"MRR (USD)"},"internal_notes":{"hidden":true}},"conversation_link_column":"conversation_id"}When something goes wrong
Partial failures
- Conversations and JSON Lines tables: each line is checked on its own. Bad lines come back with their line number (starting at 0) and the reason, and every other line is written. Fix and re-send just those lines.
- Parquet tables: types must match exactly. A mismatch fails the job and names the column. Files that finished before it stay written.
- Text that fits the type is accepted in JSON Lines, like “12” for a number.
Error codes
| Code | Meaning | Fix |
|---|---|---|
| 401 | Token missing, wrong or expired | Check the apitoken header, or create a new token |
| 403 | The API isn’t turned on for your workspace | Ask your Rippit contact |
| 404 | Source, table or job not found, or not in your account | Create it first, or check the job ID |
| 409 | Job started before the file finished uploading, or started twice | Upload first, then start once |
| 413 or 400 | File over 128 MB | Split the file |
| 422 | Bad table definition, like an invalid column name or type | Check detail in the response |
| 429 | Rate limit | Wait the number of seconds in Retry-After, then retry |
Let your AI assistant do it
The rippit-bulk-ingest-api skill walks an AI assistant through the whole thing: saving your token privately, checking access, a test upload, the full upload and a schedule. It works best in assistants that can run code, like Claude Code, Codex or Cursor. In a chat-only assistant, it writes the script for you to run.
mkdir -p ~/.claude/skills/rippit-bulk-ingest-api
curl -sL https://integrations.maestroqa.com/api/v2/guides/rippit-bulk-ingest-api.md \
-o ~/.claude/skills/rippit-bulk-ingest-api/SKILL.md- Claude web or desktop: save the file as SKILL.md in a folder named rippit-bulk-ingest-api, zip the folder, and upload it under Settings > Capabilities > Skills.
- Other assistants: put rippit-bulk-ingest-api/SKILL.md in the assistant’s skills folder.
- Start a new session after installing. Run the same command again to update.
Read https://integrations.maestroqa.com/api/v2/guides/rippit-bulk-ingest-api.md and follow it to help me send my data into Rippit.