SOURCE_ID: import_from_salesforce NAME: Import Salesforce Records CATEGORY: CRM Pull records of any Salesforce object (Account, Contact, Lead, Opportunity, custom objects, …) into Floqer — either as a one-time import or as an ongoing source that re-pulls on a schedule. Imported records become available to your Floqer workflows for enrichment, scoring, outreach, and the like. INDEX: 1. Endpoints 2. Object + fields 3. Pull mode (one-time vs ongoing) 4. Body shape (preview + create) 5. Filter operators by field type 6. Dynamic options (mandatory pre-calls) 7. How to configure end-to-end 8. Key notes 9. Where it fits 10. When to use ================================================================================ 1. ENDPOINTS ================================================================================ Source identifier (used in every endpoint path): `import_from_salesforce`. POST /api/v1/sources/import_from_salesforce/preview Scope: sources:read Returns a sample of the records that WOULD be imported for the given body, without creating anything. Use this to validate the object, fields, and filters before committing. POST /api/v1/sources/import_from_salesforce Scope: sources:write Creates the source, imports matching records immediately (when `pull_existing`), and — for an active source — keeps re-importing on a schedule. Returns `source_instance_id` (the new source's UUID). POST /api/v1/sources/import_from_salesforce/options/ Scope: sources:read Resolves dynamic option values for a field. Two `field_name` values matter: `salesforce_objects` and `salesforce_properties` (see §6). GET /api/v1/sources//data Scope: sources:read Paginated rows imported into the created source. `` is the UUID returned by Create. Query: `page_no` (default 1), `page_size` (default 20, max 200). Source-agnostic; see concepts.txt §10. POST /api/v1/sources//sync Scope: sources:write Connects the created source to a workflow and (by default) backfills it with the source's current rows. `` here is the UUID returned by Create. Body: { workflow_id, field_mapping, push_existing?, run? } — `field_mapping` keys are `input.` references on the target workflow, values are the source fields to pull (the Salesforce field API names you imported). This step is source-agnostic; see concepts.txt §10 for the full shape. ================================================================================ 2. OBJECT + FIELDS ================================================================================ Unlike a list-based source, Salesforce Pull Data selects ONE object and a set of fields on it: object required. The Salesforce object API name — "Account", "Contact", "Lead", "Opportunity", or a custom object like "My_Object__c". Resolve the catalogue via the `salesforce_objects` dynamic option (§6). properties required, non-empty. The field API names to SELECT and return on each row — e.g. ["Id", "Name", "Industry", "Website", "CreatedDate"]. Resolve the field catalogue for the chosen object via `salesforce_properties` (§6). Field names are validated against the object server-side; any field that doesn't exist on the object is rejected with a 400 listing the offending names. Relationship fields (dot paths like "Owner.Name") are supported and are not field-existence-checked (they reference fields on related objects). ================================================================================ 3. PULL MODE (ONE-TIME VS ONGOING) ================================================================================ `pull_mode` is required on the create endpoint (not on preview). Values (same as every CRM source): "static" One-time import. Imports matching records once (when `pull_existing`) and stops — no recurring schedule. `max_count` caps the import size. "active" Ongoing source. Imports matching records initially (when `pull_existing`) AND re-imports records created/modified since the last run on the cadence in `schedule` (a cron string), continuing until `expiration_date`. `max_count` is ignored for an active source. `schedule` is required when `pull_mode: active`. Otherwise it's ignored. ================================================================================ 4. BODY SHAPE (PREVIEW + CREATE) ================================================================================ Preview accepts (all snake_case): object required: Salesforce object API name properties required: array of field API names (non-empty) filters optional: filter expression (see below + §5). Omit to pull every record of the object. properties_metadata optional: array<{name, label}> — sets the `label` on each returned cell. Every cell comes back as {label, value} regardless; with metadata the label is the human label, without it the label is the field's API name. max_count optional: integer | string (preview always samples) Create accepts all the preview fields plus: name required: display name for the new source pull_mode required: "static" | "active" schedule cron string; required when pull_mode === "active" expiration_date ISO date (YYYY-MM-DD); optional (active source only) pull_existing boolean; optional, defaults to true max_count cap on rows imported (static only). Optional. Omit: imports every matching record (up to the source-wide ceiling of 50,000). Set to N: caps the import at N rows. N MUST NOT exceed the Preview's `metadata.total_results` — Recommended: set it equal to `metadata.total_results`, or omit to import all matches. Do not pick generic "safe" values like 100. Unknown top-level keys are rejected with 400 — the schema is strict. Each returned row also carries a `uid` field — the record's Salesforce `Id`, surfaced as a plain string (not wrapped as `{label, value}`). Use it as the stable per-row identifier; it appears in both Preview and Get Source Data rows. Filter expression shape (compiled into a SOQL WHERE clause): filters: { operator?: "AND" | "OR", // combinator between groups (default AND) conditions: [ // array of GROUPS { operator?: "AND" | "OR", // joins this group to the previous one conditions: [ // array of LEAVES { variable: "", condition: "", variable_type: "", values: [, ...], operator?: "AND" | "OR", // joins this leaf to the previous one case_sensitive?: boolean } ] } ] } - A leaf's `condition` is the comparison operator; the valid set depends on `variable_type` (§5). - A leaf's `operator` ("AND"/"OR") joins it to the PREVIOUS leaf in the same group (the first leaf's `operator` is ignored). Group `operator` works the same way between groups. - `values` is always an array. Single-value operators read `values[0]`; `is between` reads `values[0]` (low) and `values[1]` (high); `is` / `is not` with multiple values become SOQL `IN` / `NOT IN`. Example (Name contains "uni"): { "object": "Account", "properties": ["Id", "Name", "Industry", "Website", "CreatedDate"], "filters": { "conditions": [ { "conditions": [ { "variable": "Name", "condition": "contains", "variable_type": "string", "values": ["uni"] } ]} ] } } ================================================================================ 5. FILTER OPERATORS BY FIELD TYPE ================================================================================ `condition` is interpreted according to the leaf's `variable_type`. Set `variable_type` to the family that matches the Salesforce field — look at the field's type via `salesforce_properties` (`.extras.type`) and map it to one of the families below. (For numeric fields use `number`; for picklists use `select`.) ──────────────────────────────────────────────────────────────────── variable_type: string (string, textarea, email, url, phone, id, …) ──────────────────────────────────────────────────────────────────── is / equals field = 'value' (multiple values → IN (...)) is not field != 'value' (multiple values → NOT IN (...)) contains field LIKE '%value%' (any of the values → OR) does not contain NOT (field LIKE '%value%') (AND across values) starts with field LIKE 'value%' ends with field LIKE '%value' does not start with NOT (field LIKE 'value%') does not end with NOT (field LIKE '%value') is empty field = null is not empty field != null ──────────────────────────────────────────────────────────────────── variable_type: number (number, currency, percent) ──────────────────────────────────────────────────────────────────── is / equals field = value is not field != value greater than field > value less than field < value greater than or equal to field >= value less than or equal to field <= value is between field >= values[0] AND field <= values[1] is empty field = null is not empty field != null ──────────────────────────────────────────────────────────────────── variable_type: date / datetime ──────────────────────────────────────────────────────────────────── is field = is not field != is after field > is before field < is between field >= values[0] AND field <= values[1] is empty field = null is not empty field != null For `date` send "YYYY-MM-DD"; for `datetime` an ISO timestamp is used. ──────────────────────────────────────────────────────────────────── variable_type: boolean ──────────────────────────────────────────────────────────────────── is / equals field = value (value: true | false) is not field != value is empty field = null is not empty field != null ──────────────────────────────────────────────────────────────────── variable_type: select (picklist / multi-select) ──────────────────────────────────────────────────────────────────── is / equals field = 'value' is not field != 'value' contains field INCLUDES ('value') (multi-select) does not contain field EXCLUDES ('value') (multi-select) is empty field = null is not empty field != null Valid picklist values are in the field metadata's `extras.picklistValues[].value` — discoverable via `salesforce_properties`. Values are escaped server-side before being placed in the SOQL query. ================================================================================ 6. DYNAMIC OPTIONS (MANDATORY PRE-CALLS) ================================================================================ Two dynamic-options field names are available for this source. Both are POSTs to /api/v1/sources/import_from_salesforce/options/ with body `{ "context"?: {...} }`. salesforce_objects Context: none. Returns: {value: , label: , extras: {...}} Use the returned `value` directly as the source body's `object`. salesforce_properties Context: { "salesforce_object": "" } — REQUIRED. Returns: {value: , label: , extras: } Use the `value`s inside `properties[]`. For `properties_metadata`, send `{name: value, label: label}` per field (name + label only — the full metadata is not needed). Use `extras.type` to choose each filter leaf's `variable_type`, and `extras.picklistValues` for valid picklist values. Connection check: every endpoint for this source verifies the API-key user has an active Salesforce connection first. Missing connection → 424. ================================================================================ 7. HOW TO CONFIGURE END-TO-END ================================================================================ Typical flow for an AI agent building a Salesforce Pull Data source: Step 1 — Pick the object POST /api/v1/sources/import_from_salesforce/options/salesforce_objects Body: {} Choose the object's `.value` (e.g. "Account"). Step 2 — Resolve the field catalogue for that object POST /api/v1/sources/import_from_salesforce/options/salesforce_properties Body: {"context": {"salesforce_object": "Account"}} Pick a subset of `.value`s as `properties`. Build `properties_metadata` as `{name: .value, label: .label}` per field. Note `.extras.type` for any field you plan to filter on (→ the filter `variable_type` in §5). Step 3 — Preview before committing POST /api/v1/sources/import_from_salesforce/preview Body: { "object": "Account", "properties": ["Id", "Name", "Industry", "Website", "CreatedDate"], "properties_metadata": [{"name": "Name", "label": "Account Name"}, ...], "filters": { "conditions": [{ "conditions": [ {"variable": "Name", "condition": "contains", "variable_type": "string", "values": ["uni"]} ]}]} } Check the returned `data[]` and `metadata.total_results`. Iterate until happy — preview never creates anything. Step 4 — Create the source POST /api/v1/sources/import_from_salesforce Body: + the create-only fields: "name": "University Accounts", "pull_mode": "static" | "active", "schedule": "0 12 * * *", // only when pull_mode=active "expiration_date": "2027-01-01", // optional, active only "pull_existing": true, // optional, defaults true "max_count": // MUST NOT exceed Step 3's // metadata.total_results If Step 3 returned // total_results: 13, use max_count: 13 // (or omit to import all matches). // Don't pick 100 "to be safe". Response: { "status": 201, "data": { "source_instance_id": "", "name", "created_at" }} Step 4b — (Optional) Poll imported rows while the backfill runs GET /api/v1/sources//data?page_no=1&page_size=20 ( = UUID from Step 4) Row keys match Preview (field API names you imported). `total_count` grows until import finishes. Step 5 — Sync the source into a workflow (so its records flow downstream) POST /api/v1/sources//sync ( = the UUID from Step 4 ) Build `field_mapping` (two lookups): a. GET /api/v1/workflows → pick the destination workflow_id. b. GET /api/v1/workflows//sheets//inputs → each input has a `reference` like `{{input.email}}`. c. Map each workflow input (reference WITHOUT braces) to a source field — the source fields are the Salesforce field API names you imported (Step 2 / the preview `data[]` keys). Body: { "workflow_id": "", "field_mapping": { "input.account_name": "Name", "input.website": "Website" }, "push_existing": true, // optional, default true "run": "all" } // optional, default "all" (none | first_10 | all) See concepts.txt §10 for the full sync semantics (source-agnostic). The initial import runs asynchronously — the create call returns as soon as the source is created and the import has been queued, not when records have finished arriving. ================================================================================ 8. KEY NOTES ================================================================================ - Salesforce connection is mandatory. If the API-key user has no active Salesforce connection, every endpoint returns 424 ("User is not connected to 'salesforce'."). Surface this as a "connect Salesforce first" CTA. - Fields are validated against the object. Sending a field that doesn't exist on the object returns a 400 listing the offending field names — run `salesforce_properties` for the object to get valid names. - `filters` is optional. Omit it to pull every record of the object (capped by `max_count` on a static source). - `variable_type` drives operator interpretation. Pick it from the field's `extras.type` family (§5). The wrong family can produce an unexpected SOQL comparison; numeric fields should use `number`, picklists `select`. - Total counts are best-effort. `metadata.total_results` comes from a Salesforce COUNT() query and reflects the current matching set. - Credits are consumed when the source is created. If credit consumption fails (e.g. insufficient balance), the source is still created but its import can't start; the API returns 402 so you can top up and retry. - `properties_metadata` is optional and only needs `{name, label}`. Each returned cell is `{label, value}`; with metadata the `label` is the human label, without it the label falls back to the field's API name. Field VALUES are returned either way — metadata only controls the labels. - `max_count` is honored for a static source and ignored for an active one (an active source stays in sync indefinitely). On a static source `max_count` MUST NOT exceed the Preview's `metadata.total_results` Set it equal to the Preview total, or omit to import every match (up to the source-wide 50,000 ceiling). ================================================================================ 9. WHERE IT FITS ================================================================================ UPSTREAM (in Salesforce) Records of the chosen object (Accounts, Contacts, Leads, …) live in the user's Salesforce org, with their field values and relationships. THIS SOURCE Creates a Salesforce Pull Data source. On creation it imports matching records immediately (when `pull_existing`); an active source also re-imports created/modified records on the `schedule` cron until `expiration_date`. DOWNSTREAM (in Floqer) Imported records become available to your Floqer workflows — wire the source into a workflow to enrich, score, or run outreach on each record. ================================================================================ 10. WHEN TO USE ================================================================================ - One-time import of a filtered set of Salesforce records for enrichment / outreach: `pull_mode: static` with a `filters` expression. - Keep a Salesforce segment (e.g. "Accounts in the Technology industry") in sync with a Floqer workflow on a schedule: `pull_mode: active`, `schedule: "0 12 * * *"`. - Pull an entire object (no `filters`) as a one-time snapshot, capped with `max_count`. When you instead want to push data OUT of Floqer INTO Salesforce, use the Salesforce action templates (e.g. `salesforce_create_record`, `salesforce_upsert_record`) inside a workflow — see https://floqer.com/docs/action-detail/salesforce_create_record.txt. ================================================================================ This file is maintained manually. Last updated: 2026-05-26. Full interactive reference: https://floqer.com/docs/reference