# 3Min API — Complete Documentation > No-code API relay service for small businesses without backend developers. > Create an API endpoint, test it in sandbox, pull the data into your own system with a webhook or > polling, then deploy to production — all in 3 minutes. Inviting partners by email is optional on top. > > For a visual dashboard walkthrough with screenshots (step-by-step UI instructions for onboarding, endpoints, collaboration keys, logs, stats, etc.), see the User Manual at https://3minapi.com/guide ## Service Overview ### What is 3Min API 3Min API is a backend-less API service that lets users create API endpoints without writing code. External systems send JSON to the endpoint. 3Min API stores the data, records logs, and optionally sends webhooks. **Storage model:** 3Min API uses document-style storage (conceptually similar to MongoDB). The entire JSON payload is stored as-is, as a single document per record. Users do not design relational schemas, tables, foreign keys, or joins. **Core value:** - Create an API quickly - Test immediately - Invite partners via email to access the dashboard - Share a ready-made integration guide with partners - Monitor requests via logs and basic stats ### Terminology - **Owner**: The person/company using 3Min API who creates endpoints and manages settings. - **Caller** (Partner / Integration partner): The external company/system that sends data by calling the API. Can be invited via email to access the dashboard (view API keys, logs, stats). - **Partner developer**: The developer at the Caller who implements the integration. - **3Min API**: The system that receives requests, stores data, records logs, and sends webhooks. ## Getting Started ### Core Flow (Create → Test → Receive → Deploy) This is the path every endpoint goes through, whether the caller is a partner or the owner's own system. Step 4 is the point of the service: the data does not stay here, it goes into the owner's own system. **Step 1) Create an endpoint** Owner creates a new endpoint in the dashboard. 3Min API automatically provides: - Endpoint URL - Sandbox API key - Production API key - Public integration guide page link (Swagger-style test + code samples) **Step 2) First test call in Sandbox** Owner opens the integration guide page and runs a first Sandbox test call. **Step 3) Confirm Sandbox logs** Owner opens Logs and checks the Sandbox tab to confirm the request arrived. **Step 4) Get the data into your own system — webhook or polling** Decide how records reach the owner's system, and prove it in Sandbox before deploying: - **Webhook (push)**: 3Min API posts each record to a URL the owner configures. Needs a publicly reachable URL that answers within 15 seconds (HTTPS recommended). Default choice when such a URL exists. - **Polling (pull)**: the owner's client calls `GET /api/v1/data/{slug}/poll` on a loop and receives only what arrived since the last call. Needs no inbound URL at all — works from a laptop, an internal network, or behind a firewall. - The two are not exclusive. Both can be on for the same endpoint. Full mechanics and implementation for both are in the **Data Delivery** section below. **Step 5) Deploy to Production** Owner clicks the "Deploy to Production" button. Important: Before Production deployment, calls using Production API keys are rejected and NOT logged. Production webhook settings are separate from Sandbox ones — configure the production webhook URL (and verify the production polling loop) after deploying. **Step 6) Change cycle (repeatable)** - Owner edits settings in Sandbox → tests again → redeploys to Production. - The integration guide link does NOT change after redeploy. - If the API key is regenerated, the old key becomes invalid immediately — Owner MUST notify anyone calling the endpoint. ### Collaboration Track (Optional) Everything below is optional. It applies when someone else — a partner, a client, another vendor — calls the endpoint, and is skipped entirely when the owner's own system is the caller. The Core Flow above is unchanged either way. **Step A) Invite partner via email** - Owner invites the partner from the client detail page in the dashboard. - Partner receives an invitation email with a link to the dashboard. - Partner logs in (or signs up) and accepts the invitation. - Partner can now view API keys, logs, and stats in their own dashboard. **Step B) Joint testing & agreement** - Partner accesses API keys from their dashboard and tests in Sandbox. - Owner + Partner test and confirm required fields, data format, webhooks (if used), etc. - All testing should be done in Sandbox until both sides agree. **Step C) Partner starts Production calls** - After deployment, the partner calls the Production endpoint using the Production API key. - Owner can also manage production data directly via the Production Console in the dashboard. - A partner can also send their own webhook URL per call via headers (see Data Delivery below). #### Partner Invitation (Email-Based) Instead of manually sending API keys and guide links, Owners invite partners via email. **How it works:** 1. Owner goes to client detail page in the dashboard 2. Owner enters the partner's email address and sends an invitation 3. Partner receives an email with a link to the dashboard 4. Partner logs in (or signs up) and accepts the invitation in the dashboard 5. Partner can now view: API keys, integration guide, logs, and stats **Invitation details:** - Invitations expire after 7 days (can be resent) - Owner can revoke access at any time - Partner sees shared endpoints alongside their own in a unified dashboard **Partner permissions after accepting:** - View API keys (Sandbox + Production) - View integration guide page (Swagger-style testing) - View logs for their client - View stats for the endpoint - Cannot change endpoint settings (Owner only) - Cannot manage other clients (Owner only) The integration guide page is also available as a public link (no login required). ### Basic Usage Flow (Short Version) 1. Sign up (Google login) 2. Choose a subscription plan (optional at first; Free available) 3. Create an endpoint 4. Test in Sandbox (preferably from the integration guide page) 5. Confirm in Sandbox logs 6. Set up delivery into your own system — webhook URL, or a polling loop — and verify it in Sandbox 7. Deploy to Production 8. If changes needed: modify in Sandbox → test → redeploy Optional, when someone else calls the endpoint: invite the partner by email before deploying, agree on the field definitions together in Sandbox, and let them call Production after deployment. ## Data Modeling ### Data Modeling for Non-Developers This section is for users (often non-developers) who manage business data in Excel or spreadsheets and want to know how to shape that data for 3Min API. **Key principle:** In 3Min API, one record = one JSON document. Put everything related into a single payload and send it to a single endpoint. You do NOT need to create separate endpoints or a separate database for each type of information. **Common mistake (what NOT to do):** When users say "my Excel has sheets for Customers, Products, and Sales", it is tempting to suggest creating three endpoints (/customers, /products, /sales), setting up an external relational database, and using automation tools to split rows across endpoints. This is NOT how 3Min API is used. 3Min API is a document store, not a relational database. It does not require the user to define a relational schema. **Correct approach (Excel → 3Min API mapping):** If your Excel has three sheets — Customers, Products, Sales — the natural mapping is: - Create ONE endpoint (e.g., "wine-shop-sale"). - Each sale becomes ONE record. - The sale record embeds customer details and purchased items as nested objects/arrays inside the same JSON payload. **Example payload (one record = one sale):** ```json { "sale_date": "2026-04-14T10:23:14Z", "channel": "online", "customer": { "name": "Alex", "email": "alex@example.com" }, "items": [ { "name": "Château Margaux 2020", "price": 89000, "quantity": 1 }, { "name": "Pinot Noir Reserve", "price": 45000, "quantity": 2 } ], "total_price": 179000 } ``` **Notes:** - Nested objects and arrays are fully supported. - The Owner does NOT declare a schema in advance. Any valid JSON is accepted. - "Required fields" is an OPTIONAL validation layer — only used when the Owner wants to guarantee certain top-level fields are always present. It is NOT a schema definition step. - When multiple channels feed the same business event (website + POS + partner system), they all call the SAME endpoint with the SAME payload shape. **When multiple endpoints DO make sense:** Use separate endpoints only when the payload shape represents a fundamentally different business event — for example: - One endpoint for "sale events" - A different endpoint for "inventory adjustments" - A different endpoint for "customer inquiries / support tickets" Splitting by entity type alone (customers vs products vs sales) is NOT a valid reason to create multiple endpoints. ## Endpoint Management ### Endpoint Creation When the Owner creates an endpoint, they provide: - API name - Optional description - Optional required fields - Optional Sandbox webhook URL 3Min API provides automatically: - Endpoint URL - **Default client** with Sandbox + Production API keys - Public integration guide page link (Swagger-style test + code samples) **Optional**: Add more clients to distinguish different callers (partners). Each client gets its own API keys. Partners can be invited via email to access the dashboard. The webhook URL is optional at creation time because it is only one of the two ways to receive the data — a client that polls needs no webhook URL at all. Both are covered in **Data Delivery** below. ### API Keys and Client Management **Default flow:** - Endpoint creation → Default client auto-created - Start using immediately with default API keys - Sandbox key prefix: `tm_test_` - Production key prefix: `tm_live_` **Multiple clients (optional):** - Add clients when you need to distinguish callers - Each client: name + description + Sandbox key + Production key - Per-client CRUD permissions: choose which operations (POST/GET/PUT/DELETE) each client is allowed to perform - Permissions are set independently per environment (Sandbox / Production) - On Production deployment, Sandbox permissions are automatically copied to Production - Filter logs by client to track usage per caller **Key regeneration:** - Regenerate per client (not per endpoint) - Old key invalidated immediately - Notify the affected caller to update their integration ### Logs (Sandbox vs Production) Logs are available in the dashboard and separated by environment: - Sandbox tab: test traffic - Production tab: live traffic **Logs include:** - Request JSON payload - Success/failure status - Webhook delivery status - Error message (if failed) - Client name (if multiple clients are used) **Filtering by client:** If you have multiple clients, you can filter logs by client. Use the client dropdown to view logs from a specific caller. **Important:** Production calls are rejected and NOT logged before Production deployment. **Data retention (Production):** - Production records are kept for at least 60 days, then removed a whole month at a time - Usage statistics are kept separately and are not deleted with the records - To keep records beyond the retention window, set a webhook on the endpoint or poll `GET /api/v1/data/{slug}/poll` so they reach your own system as they arrive ### Production Deployment **Before deploying to Production:** Production API key calls are rejected and not logged. **Deployment steps:** 1. Test in Sandbox at least once 2. Click "Deploy to Production" 3. Field definitions, config version, and per-client CRUD permissions are copied from Sandbox to Production 4. Partner starts Production calls with `tm_live_` key **Change cycle:** Modify in Sandbox → test again → redeploy to Production. **Integration guide link:** Does not change after redeploy. You do not need to resend the guide link. **API key changes:** If you regenerate keys, you must notify the partner about the new keys. **Production Console (owner only):** - After deployment, the endpoint owner can manage production data directly from the dashboard - Access: endpoint detail > Production tab > "Production Console" card - Supports all CRUD operations (POST/GET/PUT/DELETE) on production data - Uses the owner's default production API key automatically - Requests from the Production Console do not trigger webhooks - Collaborators cannot access the Production Console ## Data Delivery Data sent to an endpoint does not stay in 3Min API — it goes into the owner's own system. There are two ways to get it there, and this section is the reference for both. ### Choosing Push or Pull | | Webhook (push) | Polling (pull) | |---|---|---| | Who initiates | 3Min API calls you | Your client calls us | | Requires | A publicly reachable URL that answers within 15 seconds (HTTPS recommended; http:// is accepted) | Nothing inbound — only outbound HTTPS | | Latency | Seconds after the record is stored | One poll interval, plus a ~60 second settle delay | | Cost | Free — deliveries are not counted as API calls | One API call per poll request, empty ones included | | Pace | Ours. Every record is delivered as it arrives | Yours. You decide the interval and the page size | **Pick webhook when** the receiver has a public URL and should react as soon as data arrives. This is the default. **Pick polling when** there is no public URL (local development, an internal network, behind a firewall, no always-on server), the receiver cannot absorb the incoming rate, or the client needs to control its own pace. **They are not exclusive.** Both can be on for the same endpoint — for example, a webhook for real-time reaction plus a nightly poll that backfills anything the receiver missed. Whichever is used, the record is stored either way. Delivery failing never loses data. ### Webhooks (Push) **Owner webhook:** - Configured in the dashboard, or via MCP `endpoints(action: "create" | "update", webhook_url: "...")` - Used by the Owner to receive incoming data in their own system - Configured separately per environment — a Sandbox URL and a Production URL **Caller (partner) webhook:** - Configured by the partner when calling the API - Partner includes webhook details in request headers **Caller webhook headers (optional):** - `X-Webhook-Callback`: the partner's webhook URL - `X-Webhook-Auth`: auth value (if needed) - `X-Webhook-Auth-Header`: auth header key (default: `Authorization`) #### Receiver contract (the three rules to implement) 1. **Return 2xx within 15 seconds.** Hand heavy work off to your own queue and respond immediately. A slow response is treated as a failure and retried. 2. **2xx means "I received it", not "it succeeded".** Return 2xx even when your own processing fails — a rejected upstream call, a validation error on your side, an order you cannot fulfil — and record that failure in your own system. Reserve 5xx for the case it actually describes: your receiver, or something it depends on, is temporarily down and a later delivery of the *same* record could succeed. A 5xx makes us redeliver that record, so the receiver runs the same work every time. 3. **The same `X-3minapi-Record-Id` may arrive more than once.** Use it to detect duplicates. See Idempotency below. #### Request headers we send - `User-Agent`: `3minapi-Webhook/1.0 (+https://3minapi.com/guide/api-docs)`. Sender identity. Match on the prefix `3minapi-Webhook/` when allowlisting us in a firewall. - `X-3minapi-Record-Id`: the record UUID. Identical across every attempt for that record — this is the idempotency key. - `X-3minapi-Delivery-Attempt`: attempt number, starting at 1. - `webhook-id`: same value as `X-3minapi-Record-Id`, named per the Standard Webhooks spec. - `webhook-timestamp`: unix seconds, regenerated on every attempt. - `webhook-signature`: HMAC-SHA256 signature. Two space-separated values arrive while a secret is being replaced. #### Verifying the signature Every delivery is signed, for owner and collaborator webhooks alike, following the [Standard Webhooks](https://www.standardwebhooks.com) spec. Verification is optional — receivers that ignore these headers keep working — but it is what stops someone who learned the receiving URL from forging requests, including replaying a record id so the receiver discards the genuine delivery as already handled. **Install an official Standard Webhooks library rather than hand-rolling this.** Packages exist for Node/TS, Python, PHP, Go, Ruby, Java/Kotlin, C#, Rust, and Elixir, and the whole check becomes about three lines. Hand-rolled verification is where receivers get it wrong. If it must be implemented by hand, these are the rules that break it when missed: - Signed content is `{webhook-id}.{webhook-timestamp}.{raw body}`; HMAC-SHA256, then base64-encoded. - The key is the secret with its `whsec_` prefix removed, then base64-decoded. - Verify against the **raw request bytes**. Re-serializing parsed JSON changes key order and whitespace and fails every time. Frameworks that auto-parse JSON must be configured to keep the raw body. - Allow about ±5 minutes of timestamp skew, and reject outside that to block replays. - `webhook-signature` may hold **several space-separated values**; accept if any `v1,` entry matches, and ignore entries that are not `v1,`. Checking only the first one breaks the rotation window. - Compare in constant time (`crypto.timingSafeEqual`, `hmac.compare_digest`, `hash_equals`), never `==`. **Secrets:** stored in Dashboard > Endpoint Detail > Webhook Signing Secret, separately per environment. Collaborators can read them; only the owner can rotate. Signing secrets are not available through MCP — there is no tool parameter that reads or rotates them. Verification code should read the secret from the receiver's own environment variable, never a literal in source. **Rotation:** reissuing a secret keeps the previous one valid for 24 hours and sends both signatures, so receivers can switch over without dropping a delivery. #### Delivery behavior and retries - One delivery attempt per try, with a 15-second timeout. There is no immediate in-request retry. - **Production: 6 attempts total** — the first delivery plus 5 retries at 30s, 2m, 10m, 1h, 4h (about 5 hours). **Sandbox: 4 attempts total** — the first delivery plus 3 retries at 30s, 2m, 10m (about 12 minutes). Count attempts, not retries: 6 is how many times a production receiver runs the same work. - Retried: 5xx (except 501/505), 429, timeouts, and connection errors. A 429 with `Retry-After` waits longer, never shorter. - Not retried: any 4xx other than 429 — including 409, read as "the receiver already has it" — plus 501, 505, and malformed URLs. - Even if every retry fails, the data is still stored safely. Failed and in-flight deliveries appear in the logs screen. - Once we give up on a delivery (retries exhausted, or a permanent response that is never retried) we email the endpoint owner on every plan, Free included — but only in production, only for the owner webhook, at most one message per endpoint per day, with no follow-up and no recovery notice. Collaborator webhook failures are never emailed because the endpoint owner cannot change that URL. A 409 is the one final failure that never emails: it means the receiver already has the record. #### Receiver-side limitations - If the receiving server has rate limits, some webhooks may be rejected. A 429 is retried and honors `Retry-After`; other 4xx responses are treated as permanent and are not retried. - Because a timed-out request may still have been processed by the receiver, deduplicate on `X-3minapi-Record-Id`. - 3Min API does not throttle webhook delivery; all API calls trigger webhooks immediately. - If the receiver cannot handle the volume, pull instead of being pushed to — see Polling below. ### Polling (Pull) `GET /api/v1/data/{slug}/poll` returns only the records that arrived since the last call, oldest first, and hands back a cursor. It is an HTTP endpoint the client calls on a loop; the loop lives in the client's own code. **Why GET (list) is not polling.** List sorts newest-first and cannot express "since when". A client looping on it refetches the same first page every cycle, cannot drain a backlog in order, and burns the plan quota on records it already has. Never use repeated list calls as a substitute. #### Endpoint and authentication - `GET /api/v1/data/{slug}/poll` - `Authorization: Bearer {api_key}` — `tm_test_*` for sandbox, `tm_live_*` for production - A collaboration key with the `read` permission is enough. There is no separate poll permission; `read` covers single GET, list, search, and poll alike. #### Parameters - `since`: **first call only**. `YYYY-MM-DD` or RFC 3339, **always interpreted as UTC** (so `2026-08-28` is UTC midnight). Omit both `since` and `cursor` to subscribe from now on. - `cursor`: every call after the first. Sending `since` and `cursor` together returns 400. - `limit`: 1–100, default 100. Not carried in the cursor — resend it on every page. #### The two cursors Every response carries exactly ONE of them, never both, never neither. | Field | Meaning | What the client does | |-------|---------|----------------------| | `next_cursor` | A backlog remains | Call again immediately with it | | `poll_cursor` | Caught up | Save it and wait until the next cycle | The cursor is a **position, not a fixed token for the endpoint**. It changes on every call and the server stores nothing: the watermark lives entirely on the client, so two clients polling the same endpoint each keep their own. **Decide "caught up" from which field is present, never from the number of records.** A full page can still carry `poll_cursor`, when exactly `limit` records were left. Treating a full page as "more to come" spends one extra call, and calls are what is billed. #### Constraints - Order is `created_at` ASC (oldest first) — the opposite of list and search. That is what lets a client drain a backlog in order and move a watermark forward. - The newest ~60 seconds are held back while the queue settles. A record that was just created will not appear in the very next poll; it arrives on a later one — deferred, not lost. - An empty page is normal, including the very first one after subscribing. - Counts as one read call per request regardless of page size, empty pages included — the quota is spent on the request, not the records. #### How to implement it 1. Store exactly one cursor, in something that survives a restart. 2. Call `/poll`. First time with no parameters (or with `since`); after that with the stored cursor. 3. Process the records, idempotently on the record id. 4. **Only after processing succeeded**, overwrite the stored cursor with whichever one came back. If storing or processing failed, keep the old cursor — overwriting it loses the place and every record in between. 5. `next_cursor` → loop again immediately. `poll_cursor` → sleep until the next cycle. 6. Lose the cursor and the only restart is `since`, which redelivers that stretch. That is why idempotency on the record id is a prerequisite here, not a nicety. ```bash BASE="https://api.3minapi.com/api/v1/data/{slug}" CURSOR="" while true; do if [ -z "$CURSOR" ]; then RES=$(curl -s -H "Authorization: Bearer $API_KEY" "$BASE/poll?limit=100") else RES=$(curl -s -H "Authorization: Bearer $API_KEY" "$BASE/poll?limit=100&cursor=$CURSOR") fi echo "$RES" | jq -c '.data[]' | while read -r r; do handle_record "$r" # must be idempotent on .id done NEXT=$(echo "$RES" | jq -r '.pagination.next_cursor // empty') POLL=$(echo "$RES" | jq -r '.pagination.poll_cursor // empty') if [ -n "$NEXT" ]; then CURSOR="$NEXT"; continue; fi # backlog remains if [ -n "$POLL" ]; then CURSOR="$POLL"; sleep 60; continue; fi # caught up sleep 60 # neither one: the request failed. Keep the old cursor and retry later done ``` #### Choosing the interval Usage tracks the interval, not the traffic: an empty poll costs the same as a full one. Work this out against the plan limit before picking a number. | Interval | Calls per month | % of Basic (100,000) | % of Pro (1,000,000) | |----------|-----------------|----------------------|----------------------| | 30 seconds | ~87,600 | 88% | 9% | | 1 minute | ~43,800 | 44% | 4% | | 5 minutes | ~8,760 | 9% | <1% | | 10 minutes | ~4,380 | 4% | <1% | A longer interval never loses records — each page just covers a longer slice. #### Polling error codes - 400: `since` and `cursor` sent together, or `limit` outside 1–100 - 401: Invalid API key - 403: The collaboration key lacks `read`, or the environment is inactive - 404: Endpoint not found - 429: monthly quota exhausted — this is not a per-second rate limit; retrying does not clear it, and it resets when the billing period rolls over ### Idempotency & Delivery Guarantees Both paths can hand the same record to the receiver more than once, so processing must be idempotent on the record id. This is a requirement, not an optimization. - **The key is the record id** — `X-3minapi-Record-Id` (identical to `webhook-id`) on a webhook delivery, `data[].id` on a poll response. It is the same id in both, so a receiver that uses both paths deduplicates across them with one key. - **Webhook**: a retry redelivers the same record id, and a receiver that timed out may still have processed the request. Attempt number is in `X-3minapi-Delivery-Attempt`. - **Polling**: a lost or un-advanced cursor replays a stretch of records; restarting from `since` replays it deliberately. - **What the service guarantees**: once the API returns 202, storage is guaranteed (queue-first, with auto-recovery). Delivery is at-least-once, never exactly-once. Ordering is guaranteed only on the poll path (`created_at` ASC); webhook deliveries can overtake one another under retry. - **Practical receiver shape**: store the record id with a unique constraint, insert first, and treat a duplicate-key error as "already handled, return 2xx". ## Technical Reference ### Architecture, Performance, Security **Architecture:** 3Min API processes requests asynchronously. 1. Request is validated 2. Queued immediately 3. Fast response is returned 4. Actual storage and webhook delivery happen asynchronously **Performance (typical):** - API response: instant - Full processing (store + webhooks): ~3 seconds - Abnormal maximum processing time: up to ~30 seconds **Scaling:** Backend auto-scales with traffic. High traffic should not significantly degrade response time. **Request limits:** No per-second limit for normal usage. Requests are allowed freely within the monthly plan limit. Abusive/malicious traffic may be blocked automatically. **Security:** - HTTPS/TLS encryption for all API communication - DDoS/malicious traffic detection and blocking - Harmful traffic is filtered before reaching core systems **Data:** - Any valid JSON is accepted - Nested objects and arrays supported - Max request size: 100KB per request **Script safety:** JSON is stored as-is and never executed. Webhook payloads are forwarded as-is. Systems consuming webhook data must handle proper escaping if needed. **Reliability:** - Queue-first design: requests are queued before processing, preventing data loss even during database outages - 3-tier auto-recovery system: - Tier 1: Application-level retry (200ms, 500ms delays, 3 attempts) - Tier 2: Queue retry (5-min intervals, ~8 hours coverage) - Tier 3: Dead Letter Queue recovery (30-min intervals, ~48 hours coverage) - Total: approximately 56 hours of automatic recovery - Once API returns 202 Accepted, data processing is guaranteed by the system - Database backups: daily, 7-day retention **Error handling responsibility:** - If API returns 202 Accepted: processing is guaranteed (system responsibility) - If API returns 5xx error: request was NOT received — caller must implement retry with exponential backoff - Webhook failure does NOT affect data storage (data is saved regardless) **Limitations:** - Asynchronous batch processing is not ideal for strict real-time use cases (real-time IoT sensors, real-time games, etc.) - Consider a different solution if you need immediate synchronous processing ### Key Concepts - **Endpoint**: The address (URL) that receives JSON data. - **API Key**: A secret password for calling the endpoint. Sandbox keys start with `tm_test_`, Production keys start with `tm_live_`. - **Sandbox**: Test environment (safe for testing). - **Production**: Live environment (real operation). - **Webhook**: A callback notification. When data arrives, 3Min API can send a request to a URL you specify (push). - **Polling**: The opposite direction — your client calls `GET /{slug}/poll` on a loop and receives only what arrived since its last call, oldest first, carrying a cursor it stores itself (pull). For receivers with no public URL, or that need to control their own pace. - **Slug**: An internal URL identifier automatically generated by 3Min API. Users do NOT set, edit, or control it. It is generated as a nanoid-style random string (not derived from the API name). - **Required fields**: An optional setting defined by the user. If configured, those fields must exist in incoming JSON, otherwise the request is rejected. If not configured, any valid JSON payload is accepted. Only field presence is checked; data types/values are NOT validated. - **Log content search**: The entire payload of every record is searchable from the Logs page and via the public `/search` API endpoint (no per-field opt-in required). Searches are 3+ character text substring matches (ILIKE) across all fields of the JSON payload. Comparison operators (>, <, =) and field-scoped queries are not supported. Number values are matched as substrings (e.g. searching "32" matches both `age:32` and `age:132`), and JSON key names are also indexed, so very generic search terms may match many records. Cursor-based pagination; date range is optional (defaults to the last 30 days) with no upper limit. ### API Reference **Base URL:** `https://api.3minapi.com` **CRUD Endpoints:** | Method | Endpoint | Description | |--------|----------|-------------| | POST | /api/v1/data/{slug} | Create a new record | | GET | /api/v1/data/{slug}/{record_id} | Retrieve a record by ID | | GET | /api/v1/data/{slug} | List recent records (cursor pagination, default 10 / max 30 per page) | | GET | /api/v1/data/{slug}/search | Search records by payload text (q required, optional date range — defaults to last 30 days, cursor pagination) | | GET | /api/v1/data/{slug}/poll | Records created since the last poll, oldest first (cursor pagination, 1–100 per page, default 100) | | PUT | /api/v1/data/{slug}/{record_id} | Replace a record (full replacement) | | DELETE | /api/v1/data/{slug}/{record_id} | Delete a record (permanent) | The `read` permission covers all four read routes alike — single GET, list, search, and poll. A collaboration key with `read` allowed can perform every one of them; there is no separate poll permission. **Required headers:** - `Content-Type: application/json` (for POST/PUT) - `Authorization: Bearer {API_KEY}` **Optional partner webhook headers:** - `X-Webhook-Callback: https://partner.example.com/webhook` - `X-Webhook-Auth: Bearer partner-secret-token` - `X-Webhook-Auth-Header: Authorization` (default) **Request body (POST/PUT):** JSON object, max size 100KB, UTF-8. **Responses:** CREATE (POST) — 202 Accepted: ```json { "success": true, "id": "rec_abc123def456", "message": "Request queued" } ``` READ (GET, single record) — 200 OK: ```json { "success": true, "data": { "id": "rec_abc123def456", "payload": { "...original_json..." }, "status": "success", "created_at": "2024-01-15T10:30:45.123Z", "updated_at": "2024-01-15T11:00:00.000Z" } } ``` LIST (GET without record_id) — 200 OK: ```json { "success": true, "data": [ { "id": "rec_abc123def456", "payload": { "...original_json..." }, "status": "success", "created_at": "2024-01-15T10:30:45.123Z", "updated_at": null } ], "pagination": { "limit": 10, "next_cursor": "eyJ0IjoiMjAyNC0wMS0xNVQxMDoyOTowMFoiLCJpIjoicmVjXy4uLiJ9" } } ``` `pagination.next_cursor` is omitted on the last page. Pass it back as `?cursor=` to fetch the next page. Records are sorted by `created_at DESC`. Counts as one read call against monthly usage per request (regardless of returned record count). SEARCH (GET /search) — 200 OK: ```json { "success": true, "data": [ { "id": "rec_abc123def456", "payload": { "...records matching q..." }, "status": "success", "created_at": "2024-01-15T10:30:45.123Z", "updated_at": null } ], "pagination": { "limit": 10, "next_cursor": "eyJ0IjoiMjAyNC0wMS0xNVQxMDoyOTowMFoiLCJpIjoicmVjXy4uLiJ9" } } ``` Query parameters: `q` (required, 3–200 chars), `start`/`end` (optional, RFC 3339 or YYYY-MM-DD; defaults to the last 30 days, no upper limit), `limit` (1–30, default 10), `cursor` (opaque, from previous `pagination.next_cursor`). Counts as one read call per request (same as LIST). Uses the same `read` permission as single GET and LIST. POLL (GET /poll) — 200 OK: ```json { "success": true, "data": [ { "id": "rec_abc123def456", "payload": { "...original_json..." }, "status": "success", "created_at": "2024-01-15T10:30:45.123Z", "updated_at": null } ], "pagination": { "limit": 100, "poll_cursor": "eyJ0IjoiMjAyNC0wMS0xNVQxMDozMDo0NVoiLCJpIjoicmVjXy4uLiJ9" } } ``` `updated_at` is omitted from a record when it has no value — the response leaves the key out rather than sending `null` (the same is true of the LIST and SEARCH responses above). Read it with an "absent or falsy" check, not `=== null`. Query parameters: `since` (first call only, RFC 3339 or YYYY-MM-DD, always UTC), `cursor` (every call after the first — sending `since` and `cursor` together returns 400), `limit` (1–100, default 100, not carried in the cursor). Records are sorted by `created_at` **ASC** — the opposite of LIST and SEARCH. Every response carries exactly one of `pagination.next_cursor` (a backlog remains, call again immediately) or `pagination.poll_cursor` (caught up, save it and wait); decide from which field is present, never from the number of records. Records younger than ~60 seconds are held back until the queue settles. Counts as one read call per request, empty pages included. Full contract and a working loop: see Data Delivery > Polling above. UPDATE (PUT) — 202 Accepted: ```json { "success": true, "id": "rec_xyz789...", "message": "Update request queued" } ``` DELETE — 202 Accepted: ```json { "success": true, "id": "rec_xyz789...", "message": "Delete request queued" } ``` GET returns data immediately. POST/PUT/DELETE are queued and processed asynchronously. **Common error responses:** 400 validation error / invalid record ID, 401 invalid API key, 403 endpoint inactive or no subscription, 404 record not found, 413 payload exceeds 100KB, 415 unsupported content type, 429 monthly quota exhausted (not a per-second rate limit — retrying does not clear it), 500 internal server error. **curl examples:** CREATE (POST): ```bash curl -X POST https://api.3minapi.com/api/v1/data/{slug} \ -H "Content-Type: application/json" \ -H "Authorization: Bearer tm_test_xxxxxxxxxxxx" \ -d '{ "order_id":"ORD-001", "customer_email":"user@example.com" }' ``` READ (GET, single record): ```bash curl https://api.3minapi.com/api/v1/data/{slug}/{record_id} \ -H "Authorization: Bearer tm_test_xxxxxxxxxxxx" ``` LIST (GET, paginated): ```bash curl "https://api.3minapi.com/api/v1/data/{slug}?limit=10" \ -H "Authorization: Bearer tm_test_xxxxxxxxxxxx" # Next page — pass the next_cursor returned in the previous response curl "https://api.3minapi.com/api/v1/data/{slug}?limit=10&cursor=" \ -H "Authorization: Bearer tm_test_xxxxxxxxxxxx" ``` SEARCH (GET /search): ```bash # q is required (3–200 chars). start/end are optional — defaults to the last 30 days. curl "https://api.3minapi.com/api/v1/data/{slug}/search?q=keyword&limit=10" \ -H "Authorization: Bearer tm_test_xxxxxxxxxxxx" # Next page — pass the next_cursor returned in the previous response curl "https://api.3minapi.com/api/v1/data/{slug}/search?q=keyword&limit=10&cursor=" \ -H "Authorization: Bearer tm_test_xxxxxxxxxxxx" ``` POLL (GET /poll): ```bash # First call — no parameters subscribes from now on; since=YYYY-MM-DD starts from a point in time curl "https://api.3minapi.com/api/v1/data/{slug}/poll?limit=100" \ -H "Authorization: Bearer tm_test_xxxxxxxxxxxx" # Every call after that — send back whichever cursor the previous response returned curl "https://api.3minapi.com/api/v1/data/{slug}/poll?limit=100&cursor=" \ -H "Authorization: Bearer tm_test_xxxxxxxxxxxx" ``` The loop that ties these together — when to call again immediately, when to sleep, and when NOT to overwrite the stored cursor — is in Data Delivery > Polling above. UPDATE (PUT): ```bash curl -X PUT https://api.3minapi.com/api/v1/data/{slug}/{record_id} \ -H "Content-Type: application/json" \ -H "Authorization: Bearer tm_test_xxxxxxxxxxxx" \ -d '{ "order_id":"ORD-001-UPDATED", "customer_email":"user@example.com" }' ``` DELETE: ```bash curl -X DELETE https://api.3minapi.com/api/v1/data/{slug}/{record_id} \ -H "Authorization: Bearer tm_test_xxxxxxxxxxxx" ``` POST with caller webhook: ```bash curl -X POST https://api.3minapi.com/api/v1/data/{slug} \ -H "Content-Type: application/json" \ -H "Authorization: Bearer tm_test_xxxxxxxxxxxx" \ -H "X-Webhook-Callback: https://partner.example.com/webhook" \ -H "X-Webhook-Auth: Bearer partner-secret-token" \ -d '{ "order_id":"ORD-001", "customer_email":"user@example.com" }' ``` **Webhook payload:** ```json { "id": "rec_abc123def456", "operation": "create", "status": "success", "endpoint_slug": "{slug}", "payload": { "...original_json..." }, "target_record_id": "...", "processed_at": "2024-01-15T10:30:45.123Z" } ``` - `operation` is one of: `create`, `update`, `delete` - `target_record_id` is included for update/delete operations - `payload` is null for delete operations ### Testing and Production Console **Testing tools (Sandbox):** - Built-in Test Page: access via the "Test" button on the endpoint detail page — no extra tools needed - cURL: test from the command line - Postman: use the API testing tool **Production Console (owner only):** - After deploying to Production, the endpoint owner can manage production data directly from the dashboard — without external API tools or code. - Access: Endpoint Detail → Production tab → click "Open Console" (opens in a new tab) - Full CRUD support (POST / GET / PUT / DELETE) on production data - Production API key is pre-configured automatically — no manual entry needed - Requests from the console do not trigger webhooks - Only the endpoint owner can access the Production Console; collaborators cannot use it. ## Pricing and Plans ### Plans and Limits **Free:** - Free, 1,000 calls/month - Sandbox & Production environments included - Calls blocked after limit (warnings sent before blocking) **Basic:** - $10/month, 100,000 calls/month - Calls blocked after limit (warnings sent before blocking) **Pro:** - $30/month, 1,000,000 calls/month - Calls blocked after limit (warnings sent before blocking) **Usage alerts:** - 80%: warning email - 100%: warning email; calls blocked until the next billing period **Data retention:** - Production (paid plans): records are kept for at least 60 days, then removed a whole month at a time. Export what you need to keep as it arrives — set a webhook on the endpoint, or poll `GET /api/v1/data/{slug}/poll`. - Sandbox (paid plans): records older than 30 days are automatically deleted (test data, not storage) - Usage statistics: kept separately and not deleted with the records, so dashboard charts stay intact - Free plan: endpoints and all related data are deleted 7 days after creation - Account deletion: all data immediately deleted - Subscription cancellation: transitions to Free plan at end of billing period (Free plan policies then apply) **Subscription policy:** - Upgrade: applies immediately, prorated difference charged right away - Downgrade: applies immediately, prorated credit issued. Limited to once per billing cycle. Current usage must not exceed: min(target plan limit × (elapsed days / total days) × 1.1, target plan limit). If current usage already exceeds the target plan limit, downgrade is rejected immediately. - Cancellation: takes effect at end of current billing period; service available until then - Reactivation: can reactivate before period ends to restore plan - Refund: full refund within 30 days if unused; no refund after 30 days or first API call ## AI Integration ### MCP (Model Context Protocol) 3Min API supports MCP for AI tool integration. Users can manage their APIs using natural language through AI tools. - Documentation: https://3minapi.com/docs/mcp - Setup guide: https://3minapi.com/dashboard/settings/mcp - MCP Server URL: https://3minapi.com/api/mcp (needed only for the CLI / IDE method below) **Two authentication methods:** **1. Directory connector — Claude or ChatGPT (Recommended)** - Authentication: OAuth 2.1 (automatic, no API key needed) - 3Min API is listed in both the Claude Connectors directory and the ChatGPT Plugins directory, so there is no server URL to enter - Claude setup: claude.ai → Settings → Connectors → search "3minapi" → select "3Min API" → Connect - Direct listing: https://claude.ai/directory/connectors/3minapi - Labeled "Community" — passed Anthropic's automated checks, not reviewed in depth. Claude shows a confirmation notice before connecting; this is expected. - Available on every Claude plan, including Free. Works on Claude web, desktop, and mobile. - Guide: https://3minapi.com/guide/ai-claude - ChatGPT setup: https://chatgpt.com/plugins → search "3min api" → Connect - Guide: https://3minapi.com/guide/ai-chatgpt - Authentication is handled automatically via web login (OAuth) - No destructive operations — safe for monitoring and quick responses **2. CLI / IDE (Advanced)** - Authentication: x-api-key header with MCP API key - MCP API Key: Generate in Dashboard → Settings → MCP → CLI / IDE tab - Works with any MCP-compatible CLI or IDE (Claude Code, Cursor, etc.) - Claude Code: `claude mcp add --transport http 3minapi https://3minapi.com/api/mcp --header "x-api-key: YOUR_KEY"` - Cursor: Add to .cursor/mcp.json with url + headers - Guide: https://3minapi.com/guide/ai-mcp **What you CAN do via MCP:** - Service guide (help): reference by topic - Endpoint management (endpoints): list, get, create, update, deploy to production - API calls (api_call): POST, GET (single + list), PUT, DELETE records via Gateway - Log search (logs): record lookup, full-payload text search (ILIKE partial match, min 3 chars). Cursor pagination (limit 1–30, default 10). `start_date`/`end_date` optional — defaults to last 30 days, no upper bound. - Statistics (stats): monthly summary, daily CRUD breakdown, response time - Collaborator management (collaborators): create keys, update permissions, invite - Subscription info (subscription): view plan, usage, billing, available plans - WordPress plugin context (wordpress_plugin): plugin overview, usage guide, and AI codegen system prompt for generating sandbox-safe `[3minapi]` shortcode UI code (HTML/CSS + JS) — see https://3minapi.com/wordpress/ai-codegen-prompt - Workers (worker_list): names and descriptions of the user's registered task workers — never their URLs or secrets - Tasks (task_create, task_list, task_get, task_cancel): hand a job to a worker, list tasks, read one task's result or error, cancel running tasks — see the Tasks section below **What you MUST do in the web dashboard (https://3minapi.com/dashboard):** - Sign up / log in - Change subscription plan / payment - Delete account - Create/manage MCP API keys - Delete endpoints - Delete/deactivate collaboration keys, regenerate API keys, revoke invitations - Register, edit and delete task workers; view and rotate a worker's signing secret and callback key ## Tasks Tasks let an AI such as ChatGPT or Claude hand a job to the user's own HTTP server (a worker) and bring the result back into the conversation. 3Min API only delivers the task and records its status — the worker does the actual work. When the goal is to store and collect data, use an endpoint instead. **Flow:** 1. Register a worker in the web dashboard (Dashboard → Tasks → Workers): name, description, worker URL, optional auth header. The AI never sees the URL or secrets. 2. The AI calls task_create with a worker name and input (JSON object, up to 100 KB). The task starts as working. 3. 3Min API sends a signed POST to the worker URL. The body includes callback_url. 4. The worker answers 2xx right away — that means "received", not a result — and then does the work. 5. The worker reports to callback_url: optional progress messages, then completed (with a result) or failed (with an error). 6. The AI calls task_get to read the result. **Webhook vs task:** a webhook announces that a record arrived and is done. A task hands off work and stays open until the worker reports back. **Limits and behavior:** - Deadline: 24 hours after creation, then the task becomes failed - Delivery is at-least-once — the same task can arrive twice, so deduplicate on task_id - Delivery timeout 15 seconds, 6 attempts over about 5 hours - The worker needs a public URL. A worker on a local machine needs a tunnel (e.g. Cloudflare Quick Tunnels, ngrok) - Workers: 1 on Free, unlimited on paid plans - API call counting: one successful task tool call and one accepted report each count as 1 call; deliveries and retries count as 0 - A worker's result and error reach the AI conversation unmasked Full guide (worker contract, signature verification, status report API, example code): https://3minapi.com/guide/tasks ## Support Contact: contact@3minapi.com (https://3minapi.com/support). Email is the only support channel; a person reads every message and we aim to reply within a few business days. When reporting a problem, include the endpoint name or slug, the environment (Sandbox or Production), and roughly when it happened — never a full API key. ### FAQ **Q: Can I start for free?** A: Yes. New sign-ups get the Free plan with 1,000 monthly calls. Sandbox and Production environments included. Upgrade anytime for higher limits. **Q: Can I use this without a developer?** A: Yes. 3Min API is designed for non-developers. Create endpoints in the dashboard and invite partners via email. They can view API keys and test directly from their own dashboard. **Q: How does the partner call the API?** A: Invite the partner via email from the client detail page. After accepting, they can view API keys and the integration guide in their dashboard. The guide includes Swagger-style testing and code samples (curl/JavaScript/Python). **Q: What happens if I exceed the monthly limit?** A: You get warning emails at 80% and 100%, and additional calls are blocked until the next billing period starts. **Q: How do I change plans?** A: In dashboard settings → subscription. Upgrade applies immediately; downgrade also applies immediately with a prorated credit. Plan changes are limited to once per billing cycle. **Q: Are webhook calls counted as API calls?** A: No. One API request counts as one call even if multiple webhooks are triggered. **Q: I have no server with a public URL. How do I get the data into my system?** A: Poll for it. `GET /api/v1/data/{slug}/poll` hands your client only what arrived since its last call, oldest first, with a cursor your client stores. It needs no inbound URL, so it works from a laptop, an internal network, or behind a firewall. See Data Delivery > Polling. **Q: Are poll requests counted as API calls?** A: Yes — one call per request regardless of how many records come back, empty responses included. Usage therefore tracks your poll interval, not your traffic: every 30 seconds is ~87,600 calls a month whether or not anything arrives. Pick the interval against your plan limit. **Q: A record I just created is missing from the poll response. Is it lost?** A: No. The newest ~60 seconds are held back while the queue settles, so a brand-new record arrives on a later poll — deferred, not lost. **Q: Can I just call GET (list) repeatedly instead of polling?** A: No. List sorts newest-first and cannot express "since when", so every cycle refetches the same first page, cannot drain a backlog in order, and spends quota on records you already have. `/poll` exists for exactly this. **Q: Can I use a webhook and polling at the same time?** A: Yes. They are not exclusive — a common shape is a webhook for immediate reaction plus a periodic poll that backfills anything the receiver missed. Deduplicate on the record id, which is the same value on both paths. **Q: Can data be lost?** A: Once the API returns 202 Accepted, the system guarantees processing through a 3-tier auto-recovery system covering approximately 56 hours. Requests are queued first, so even during database outages, no data is lost. **Q: What should I do if the API returns a 5xx error?** A: 5xx errors mean the request did not reach the system. Implement retry logic with exponential backoff in your code (e.g., 1s → 2s → 4s, max 3 attempts). Once you get 202 Accepted, processing is guaranteed. **Q: What happens to data if I cancel?** A: After cancellation, your account transitions to the Free plan at the end of the billing period. Free plan data policy then applies with no exceptions: endpoints created more than 7 days ago are deleted on the next cleanup cycle, together with all of their data — sandbox and production alike. Download anything you need to keep before cancelling. **Q: Can I create multiple endpoints?** A: Yes. No endpoint count limit on any plan. **Q: Can I change required fields later?** A: Yes in Sandbox. After Production deployment you can still change, but be careful about compatibility. **Q: How long is data stored?** A: Production records are kept for at least 60 days and are then removed a whole month at a time. Sandbox records older than 30 days are automatically deleted. Free plan endpoints are deleted 7 days after creation. Usage statistics are kept separately and survive record deletion. To keep records beyond the retention window, set a webhook on the endpoint or poll `GET /api/v1/data/{slug}/poll`. Subscription cancellation transitions to Free plan (Free plan policies then apply), and deleting your account removes everything immediately. **Q: Can I retrieve stored data via API?** A: Yes. Use GET request to retrieve individual records by their ID. Data can also be searched in the dashboard logs. **Q: What is the Production Console?** A: The Production Console is an owner-only feature that lets you manage production data (Create, Read, Update, Delete) directly from the dashboard without external API calls. Access it from the Production tab on the endpoint detail page after deployment. **Q: My Excel has multiple sheets (customers, products, sales). Do I need a separate endpoint or external database for each sheet?** A: No. 3Min API uses document-style storage (similar to MongoDB), so related entities belong together in one nested JSON payload sent to one endpoint. For example, a "sale" record can embed the customer object and an "items" array inside the same payload — you do not split customers, products, and sales into separate endpoints or tables. See the "Data Modeling for Non-Developers" section for a concrete example payload. **Q: Do I need to set up a database (Supabase, MySQL, PostgreSQL) before using 3Min API?** A: No. 3Min API stores the JSON payload for you. There is no separate database for the user to provision, and no relational schema to design. Create an endpoint in the dashboard and start sending JSON. ### Troubleshooting **Error codes:** - **401 Unauthorized**: API key missing/invalid or wrong environment key. Fix: confirm key, confirm Sandbox vs Production, header format: `Authorization: Bearer {API_KEY}` - **400 Bad Request**: Invalid JSON, missing required fields, or invalid record ID. Fix: validate JSON syntax, confirm required fields, confirm record ID format, confirm Content-Type is `application/json` - **403 Forbidden**: Endpoint inactive in that environment, or no active subscription. Fix: confirm environment is active in dashboard; confirm subscription is active - **413 Payload Too Large**: Request body exceeds 100KB. Fix: reduce payload size by trimming large fields, or split into multiple smaller requests - **415 Unsupported Media Type**: Wrong Content-Type. Fix: add `Content-Type: application/json` - **429 Too Many Requests**: Monthly limit exceeded. Fix: check usage, upgrade plan, or wait for next month - **5xx Internal Server Error**: Temporary server issue. Fix: implement retry with exponential backoff (1s → 2s → 4s). Note: 5xx means the request was NOT received. Once you get 202, data is safe. **Common problems:** **Problem: API call fails with 401** 1. Confirm the key has no extra spaces when copied 2. Confirm you are not using a Production key in Sandbox (or vice versa) 3. Confirm header is exactly: `Authorization: Bearer tm_test_xxx` (space after Bearer) **Problem: Webhook is not arriving** 1. Confirm webhook URL is reachable from the public internet 2. Confirm HTTPS is used (recommended) 3. Check webhook status in dashboard logs — the Webhook column and the delivery card on the log detail show the response body and every attempt. Only failed deliveries are recorded, so a blank cell is not proof of delivery. 4. Confirm webhook server responds within 15 seconds 5. If the receiver has no public URL at all, or cannot absorb the rate, pull instead — see Data Delivery > Polling **Problem: Polling returns nothing, or misses a record I just created** 1. An empty page is normal, including the first one after subscribing with no `since` 2. The newest ~60 seconds are held back while the queue settles — a brand-new record arrives on a later poll, it is not lost 3. Confirm the client sends `since` on the FIRST call only and the returned cursor on every call after. Sending both returns 400 4. Confirm the client stores whichever cursor came back and only after processing succeeded. Overwriting it on failure skips the records in between **Problem: The polling loop never sleeps, or spends double the calls** 1. Decide "caught up" from which cursor field came back, never from the number of records — a full page can still carry `poll_cursor` 2. `next_cursor` means call again immediately; `poll_cursor` means save it and wait until the next cycle 3. If neither field is present, the request failed — keep the old cursor and retry later **Problem: Required field validation keeps failing** 1. Confirm all required fields exist in the JSON payload 2. Confirm field names match exactly (case-sensitive) 3. Note: only presence is checked (no type validation) **Problem: Cannot deploy to Production** 1. Confirm you tested at least once in Sandbox 2. Confirm you are subscribed to a paid plan if required **Problem: Production logs are empty** 1. Confirm you deployed to Production (most common) 2. Before deployment, Production calls are rejected and NOT logged 3. Confirm the partner is using the `tm_live_` key for Production (`tm_test_` goes to Sandbox logs)