API reference

One endpoint takes leads from every website. Agents manage websites, forms and leads through the agent API or the MCP server.

Endpoints and credentials

Every website posts to the same URL. The key tells 1DB which website it is, and formSlug which form.

WhatURLCredential
Send a leadPOST https://gallant-goshawk-513.convex.site/v1/leadsIngest key: X-API-Key: 1db_…
Agent APIhttps://gallant-goshawk-513.convex.site/v1/agent/…Agent token: Authorization: Bearer 1db_at_…
MCP serverhttps://www.1-db.com/api/mcpAgent token, same header

The API is not served from www.1-db.com; only the MCP server is. In code, use ${DB_API_ENDPOINT}/v1/leads with these variables:

.env.local
DB_API_ENDPOINT=https://gallant-goshawk-513.convex.site
DB_API_KEY=1db_...        # 1db_ followed by 32 hex characters
DB_FORM_SLUG=acme-contact # the real slug, from the dashboard, get_website or 1db init
  • Ingest key (1db_ + 32 hex characters): one per website. It can only create leads, so it is safe in browser code. A server route is still better: it can pass on the visitor's IP and browser and retry on failure. If the browser posts directly, the public prefix is fine: NEXT_PUBLIC_DB_API_KEY, VITE_DB_API_KEY.
  • Agent token (1db_at_…): manages an organization. Only a person can create one, in Settings → API keys → Agent tokens or by approving npx @1db/cli setup in the browser. It never goes in a project.

For AI agents

If you don't have an agent token, ask your user for one. Don't try to sign up or sign in to 1DB yourself.

Form slugs are unique across all of 1DB and look like acme-contact (website slug, then the form). Always use the real slug, never a guess like contact.

Send a lead

bash
curl -X POST https://gallant-goshawk-513.convex.site/v1/leads \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $DB_API_KEY" \
  -d '{"formSlug":"acme-contact","email":"jane@example.com","name":"Jane Doe","message":"Hello"}'

The body is formSlug plus the form's fields, flat. A stored lead answers:

json
200 {"success":true,"leadId":"…","duplicate":false}

duplicate: true means this key already sent this submission_id (v2 only): nothing new was stored or sent on.

Errors from /v1/leads

Every error has a readable error string and a stable code, and a hint saying what to change. Show error to visitors; branch on code.

json
{"error":"Check the highlighted fields","code":"INVALID_FIELDS",
 "fields":{"email":"Enter a valid email address"},"field":"email","details":"Enter a valid email address"}
CodeStatusMeaning
MISSING_API_KEY401Send the website's ingest key in the X-API-Key header.
INVALID_API_KEY401The key is wrong or was revoked. Get the live one from the dashboard, get_website or 1db keys.
INVALID_JSON400The body isn't valid JSON.
INVALID_FIELDS400One or more fields are wrong. fields names each one with a message you can show the visitor.
FORM_NOT_FOUND404No form has that formSlug. Use the slug from the dashboard, get_website or 1db init.
KEY_NOT_FOR_FORM403The form belongs to another organization than the key.
KEY_NOT_FOR_WEBSITE403The key is for another website than the form. Use that website's key.
RATE_LIMIT_API_KEY429Too many leads on this key. Wait retryAfter seconds.
RATE_LIMIT_IP429Too many leads from one visitor IP. Wait retryAfter seconds.
INTERNAL500Something broke on 1DB's side. Retry with backoff and the same submission_id.

fields maps each bad field to a message; field and details repeat the first one. 429s also carry retryAfter (seconds) and a Retry-After header.

Lead contract

v1 (the default) stores the body as sent. v2 checks every field and names each bad one. Opt in per request with X-1DB-Contract: 2.

Headers

HeaderMeaning
X-API-KeyThe website's ingest key. Required.
X-1DB-Contract: 2Check the body against v2 and answer with every bad field.
X-1DB-Client-IPThe visitor's IP, when a server route sends the lead: cf-connecting-ip on Cloudflare, the first x-forwarded-for hop on Vercel.
X-1DB-Client-UAThe visitor's browser user-agent.

The two client headers work with either contract. Without them, 1DB uses the connection's own IP and user agent, so every visitor of a server route shares one rate-limit bucket.

v2 fields

Use these names; common aliases such as zip or firstName still work. Fields not listed here are stored as sent.

FieldRule
submission_idRequired. 8–128 of A-Z a-z 0-9 . _ : -. A UUID made when the form loads. externalId is accepted for it.
email, phoneOne of the two is required. Email must be well formed; phone must parse to E.164.
first_name, last_nameUp to 80 characters each.
companyUp to 120.
lead_typebuy, sell, recycle, waste or inquiry.
service_label, product_labelThe site's own wording for those two, up to 200. Kept as metadata.
product_typeA product type slug the website's organization uses (see list_product_types).
quantityA whole number from 1 to 1,000,000. A digit string is accepted and stored as a number.
address_line_1, address_line_2Up to 200.
city / stateUp to 80 / 40.
postal_codeUS ZIP or ZIP+4, or a Canadian postal code; up to 12 characters when country is another country.
timeframeasap, this_week, within_2_weeks, this_month, within_a_month, flexible, just_pricing. Labels like “Within 2 weeks” are accepted.
needed_byYYYY-MM-DD.
sms_consentyes or no (or a boolean). yes needs sms_consent_text (the exact checkbox wording) and a phone.
messageUp to 2,000.
_1db_utm_*, _1db_referrer, _1db_landing_page, _1db_current_pageAttribution.
_1db_gclid, _1db_gbraid, _1db_wbraid, _1db_fbclid, _1db_msclkid, _1db_ttclid, _1db_ga_client_idClick and analytics IDs.

Retries

On a 429 or 500, retry with the same submission_id. If the first attempt was stored, the retry answers duplicate: true and nothing is sent twice. The same id on another key is a different lead. v1 never dedupes.

Rate limits

  • /v1/leads: 100 leads a minute per ingest key (bursts to 200), and 50 a minute per visitor IP.
  • /v1/agent/*: 60 requests a minute per agent token (bursts to 120).

Agent API

Send the agent token as Authorization: Bearer 1db_at_…. GET /v1/agent lists every route. The routes, MCP tools and CLI commands are in AI agents, MCP and CLI.

bash
curl https://gallant-goshawk-513.convex.site/v1/agent/websites/health?website=acme.com \
  -H "Authorization: Bearer $ONEDB_TOKEN"

Errors are an object under error:

json
{"error":{"code":"forbidden","message":"This token is missing the routing scope","hint":"…"}}
CodeStatusMeaning
invalid_argument400A parameter is missing or wrong; message says which.
unauthorized401No token, or it is invalid or revoked. Ask your user for a new one.
forbidden403The token lacks the scope this route needs; message names it.
not_found404No such website, form or rule in the token's organization.
conflict409The request clashes with current state, e.g. a test lead for a site with no live key.
rate_limited429Over 60 requests a minute. Wait retryAfter seconds.
upstream502A step that calls out failed, e.g. the test lead's post to /v1/leads.
internal500Something broke on 1DB's side. Retry later.

Embed script

For a site where you can't write code, one script tag renders a form built from the form's fields in 1DB and posts it to /v1/leads:

html
<script src="https://gallant-goshawk-513.convex.site/v1/embed.js?key=1db_...&form=acme-contact" async></script>
<button data-1db>Contact us</button>   <!-- opens the popup -->
<div data-1db-inline></div>           <!-- or renders it here, in inline mode -->

The mode (popup, slide-in or inline), title, colors and success message come from the form's embed settings in the dashboard. The key is in the page, which is fine for an ingest key.