02 Get your data in /Integrations
Typeform
Send form responses from Typeform to Dimension Labs with the universal API.
- T TypeformTickets, chats, calls, reviews
- ⟳ Your jobPull new · map fields · every 15 min
- { } Universal APIPOST /track · /trackhistorical
- Dimension LabsRead in full · enriched daily
This brings in your Typeform responses: each text question and the respondent’s answer, with ratings, scores and hidden fields alongside. You run it on your side with the universal API, using Typeform’s Responses API.
What you need
Section titled “What you need”- A Typeform account. Typeform says API access works on standard accounts, with no developer account needed.
- A Typeform personal access token with the
responses:readandforms:readscopes, and the ID of each form. - A Dimension Labs API key for a new Universal API source. See Your API key.
1. Get API access in Typeform
Section titled “1. Get API access in Typeform”Typeform’s steps are in Personal access token.
- Log in to Typeform. In the drop-down menu next to your username in the upper-left corner, click Account.
- In the left menu, click Personal tokens, then Generate a new token.
- Name the token, choose the scopes
responses:readandforms:read, and click Generate token. - Send it as
Authorization: Bearer YOUR_TOKEN. The form ID is in the form’s URL: inhttps://mysite.typeform.com/to/u6nXL7it isu6nXL7.
If your responses are stored in the EU Data Center, use https://api.eu.typeform.com or https://api.typeform.eu instead of https://api.typeform.com.
2. Pull new responses
Section titled “2. Pull new responses”curl -H "Authorization: Bearer $TYPEFORM_TOKEN" \ "https://api.typeform.com/forms/FORM_ID/responses?page_size=100&since=2026-10-01T00:00:00&sort=submitted_at,asc"GET /forms/{form_id}/responsesreturns completed responses by default.- Incremental:
sinceanduntiltake a UTC time to the second (2020-03-20T14:00:59) or Unix seconds, and are inclusive.excluded_response_idsskips IDs you already sent. The default sort issubmitted_at,desc. - Typeform notes that very recent responses (within about the last 30 minutes) may not be returned yet. Set
untilto 30 minutes ago so a late response isn’t skipped. - Pagination: for more than 1,000 responses, Typeform says to narrow each request with
since/untilorbefore/after. The example below movessinceforward page by page. - Question titles come from
GET /forms/{form_id}: each item infieldshas anid, atitleand atype. - Rate limit (Typeform’s): two requests per second per Typeform account, for the Create and Responses APIs.
3. Map the fields
Section titled “3. Map the fields”Each text question becomes an outgoing message (the field title) followed by an incoming message (the answer), under the response ID. Skip blank answers.
| Dimension Labs | Typeform | Notes |
|---|---|---|
text | field title (outgoing), answers[].text where field.type is short_text or long_text (incoming) | |
userId | a hidden field such as your internal customer ID, if you pass one | Otherwise use response_id. Never an email address. |
incoming | false for the question, true for the answer. | |
sessionId | response_id | |
conversationId | response_id | |
dimensionlabs_timestamp | submitted_at | Convert with toMillis. Add 1,000 ms per message. |
platformJson | answers[].number for rating, opinion_scale and number fields, calculated.score, hidden, metadata.platform | Hidden fields are the place for account or order IDs to join on. |
Personal data. Typeform’s email and phone_number answers aren’t sent by this code; keep it that way. Don’t pass emails or names in hidden fields, because the code sends hidden in platformJson. Redact open-text answers too. See PII redaction.
4. Send it
Section titled “4. Send it”// typeform.mjs. Environment: TYPEFORM_TOKEN, TYPEFORM_FORM_IDS (comma-separated),// TYPEFORM_API (default https://api.typeform.com; use your EU address if your data is there), DL_API_KEY.import { sendToDimensionLabs, toMillis } from './dimensionlabs.mjs';
const HOUR = 60 * 60 * 1000;const sleep = (ms) => new Promise((r) => setTimeout(r, ms));const API = process.env.TYPEFORM_API ?? 'https://api.typeform.com';const iso = (ms) => new Date(ms).toISOString().slice(0, 19);
// Retries Typeform's rate limit (429) and server errors (5xx), honoring Retry-After when it is sent.async function get(url) { for (let attempt = 0; ; attempt++) { const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.TYPEFORM_TOKEN}` } }); if (res.ok) return res.json(); if ((res.status !== 429 && res.status < 500) || attempt === 5) throw new Error(`Typeform returned ${res.status}: ${await res.text()}`); await sleep((Number(res.headers.get('retry-after')) || 2 ** attempt) * 1000); }}
async function syncForm(formId, since, historical) { const titles = {}; const walk = (fields = []) => fields.forEach((f) => { titles[f.id] = f.title; walk(f.properties?.fields); }); walk((await get(`${API}/forms/${formId}`)).fields); const until = iso(Date.now() - 30 * 60 * 1000); // Typeform may not return the last 30 minutes yet let from = since, seen = [], newest = since; for (;;) { const q = new URLSearchParams({ page_size: '100', since: iso(from), until, sort: 'submitted_at,asc' }); if (seen.length) q.set('excluded_response_ids', seen.join(',')); const { items } = await get(`${API}/forms/${formId}/responses?${q}`); for (const r of items) { const scores = Object.fromEntries((r.answers ?? []).filter((a) => a.type === 'number').map((a) => [a.field.ref ?? a.field.id, a.number])); const base = { userId: r.hidden?.customer_id ?? r.response_id, sessionId: r.response_id, conversationId: r.response_id, platformJson: { formId, scores, score: r.calculated?.score, hidden: r.hidden, platform: r.metadata?.platform } }; const submitted = toMillis(r.submitted_at); const old = historical || submitted < Date.now() - HOUR; let t = submitted; for (const a of r.answers ?? []) { if (!['short_text', 'long_text'].includes(a.field.type) || !a.text?.trim()) continue; await sendToDimensionLabs({ ...base, text: titles[a.field.id] ?? a.field.ref, incoming: false, dimensionlabs_timestamp: t }, { historical: old }); await sendToDimensionLabs({ ...base, text: a.text.trim(), incoming: true, dimensionlabs_timestamp: t + 1000 }, { historical: old }); t += 2000; } const second = Math.floor(submitted / 1000) * 1000; seen = second === from ? [...seen, r.response_id] : [r.response_id]; // skip these on the next page from = second; newest = Math.max(newest, submitted); } if (items.length < 100) return newest; }}
// Sends every response submitted at or after `since` (Unix ms), for each form. Returns the new `since`.export async function syncTypeform({ since, historical = false }) { let newest = since; for (const formId of process.env.TYPEFORM_FORM_IDS.split(',').map((x) => x.trim()).filter(Boolean)) { newest = Math.max(newest, await syncForm(formId, since, historical)); } return newest;}customer_id stands for a hidden field you define yourself. Remove it if you don’t pass one. One Dimension Labs source can hold all your forms: the form ID goes in platformJson.formId.
5. Run it
Section titled “5. Run it”Save the code as typeform.mjs in a project set up as in Set up your project, and run it with the scheduled sync runner: import { syncTypeform as sync } from './typeform.mjs';.
- History first:
node run.mjs 2025-01-01sends every response since that date to/trackhistorical. Then start a backfill from the Backfill tab. - Then every 15 minutes: the scheduled job sends new responses. Because the code waits 30 minutes for Typeform to settle, a response is 30 to 45 minutes old when it is sent: it still goes to
/track, and is enriched automatically every day. A response that is more than an hour old when the job sees it goes to/trackhistorical, and needs a backfill. - Re-runs are safe: duplicates are removed automatically.
If you download responses from Typeform as a file instead, upload it and check it first with the file checker, then run a backfill.
Typeform reference
Section titled “Typeform reference”- Get started (base URL, EU Data Center, rate limits)
- Personal access token
- OAuth 2.0 scopes
- Responses API
- Retrieve responses
- Retrieve form
- Create form (field properties)
Checked against Typeform’s documentation on 2026-10-13.