{
  "openapi": "3.0.1",
  "info": {
    "title": "Salesforce Lead Pusher — Upsert Leads & Contacts",
    "description": "Imports leads from any Apify scraper directly into Salesforce CRM as Leads, Contacts, or Accounts. Email deduplication, 200-record batch upserts, custom field mapping, and free dry-run preview. B2B CRM sync at $0.05 per lead created.",
    "version": "1.1",
    "x-build-id": "hgmAwdoRydzPxyMe4"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/ryanclinton~salesforce-lead-pusher/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-ryanclinton-salesforce-lead-pusher",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/acts/ryanclinton~salesforce-lead-pusher/runs": {
      "post": {
        "operationId": "runs-sync-ryanclinton-salesforce-lead-pusher",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor and returns information about the initiated run in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/runsResponseSchema"
                }
              }
            }
          }
        }
      }
    },
    "/acts/ryanclinton~salesforce-lead-pusher/run-sync": {
      "post": {
        "operationId": "run-sync-ryanclinton-salesforce-lead-pusher",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "inputSchema": {
        "type": "object",
        "properties": {
          "mode": {
            "title": "Mode (job preset)",
            "enum": [
              "prospect-import",
              "crm-hygiene-sync",
              "signup-gate",
              "event-attendees",
              "account-based-push",
              "audit-only",
              "raw"
            ],
            "type": "string",
            "description": "Preset bundle for common Salesforce push jobs. 'prospect-import' = cold outbound (Lead, status=Open). 'crm-hygiene-sync' = weekly refresh (Lead, no status). 'signup-gate' = newsletter capture (Contact, leadSource=Web). 'event-attendees' = Lead with leadSource=Event, status=Working. 'account-based-push' = one Account per company. 'audit-only' = dry-run preview. 'raw' = no preset. Explicit objectType / leadStatus / leadSource always override the preset.",
            "default": "raw"
          },
          "salesforceUsername": {
            "title": "Salesforce Username",
            "type": "string",
            "description": "Your Salesforce username (email used to log in). Example: user@mycompany.com"
          },
          "salesforcePassword": {
            "title": "Salesforce Password + Security Token",
            "type": "string",
            "description": "Your Salesforce password concatenated with your security token (no space). Example: MyPassword123TOKEN123. Get your security token at Setup > My Personal Information > Reset Security Token."
          },
          "salesforceClientId": {
            "title": "Connected App Consumer Key",
            "type": "string",
            "description": "Consumer Key from your Salesforce Connected App. Create one at Setup > App Manager > New Connected App. Enable OAuth and add the 'api' scope."
          },
          "salesforceClientSecret": {
            "title": "Connected App Consumer Secret",
            "type": "string",
            "description": "Consumer Secret from your Salesforce Connected App."
          },
          "salesforceLoginUrl": {
            "title": "Salesforce Login URL",
            "type": "string",
            "description": "OAuth token endpoint. Use https://test.salesforce.com for sandbox orgs.",
            "default": "https://login.salesforce.com"
          },
          "datasetId": {
            "title": "Apify Dataset ID (optional)",
            "type": "string",
            "description": "Apify dataset ID to load leads from instead of inline `leads`. Use to chain with other actors — for example, B2B Lead Gen Suite, Lead Enrichment Pipeline, Google Maps Lead Enricher, or Website Contact Scraper. Loads up to 5,000 records."
          },
          "leads": {
            "title": "Leads (inline data)",
            "type": "array",
            "description": "Lead records to push to Salesforce. Each item should have at minimum an email (for deduplication) and a company name. Supports output from any Apify scraper or enrichment actor. Use `datasetId` instead to chain with another actor's output.",
            "default": [
              {
                "name": "Sarah Chen",
                "email": "sarah.chen@acmecorp.com",
                "phone": "+1-415-555-0192",
                "companyName": "Acme Corp",
                "jobTitle": "VP of Engineering",
                "industry": "Technology",
                "website": "https://acmecorp.com",
                "city": "San Francisco",
                "state": "CA",
                "country": "US",
                "description": "Met at SaaStr 2025. Interested in automation tools."
              }
            ]
          },
          "objectType": {
            "title": "Salesforce Object Type",
            "enum": [
              "Lead",
              "Contact",
              "Account"
            ],
            "type": "string",
            "description": "Which Salesforce object to create: Lead (unqualified prospect), Contact (person linked to an Account), or Account (company/organisation). Overrides the mode preset if set explicitly.",
            "default": "Lead"
          },
          "leadStatus": {
            "title": "Lead Status (Salesforce)",
            "type": "string",
            "description": "Salesforce Status field on Lead records (e.g. 'Open - Not Contacted', 'Working - Contacted', 'Closed - Converted'). Overrides the mode preset."
          },
          "leadSource": {
            "title": "Lead Source (Salesforce)",
            "type": "string",
            "description": "Salesforce LeadSource field on Lead/Contact records (e.g. 'Web', 'Event', 'Outbound', 'Referral'). Overrides the mode preset."
          },
          "skipDuplicates": {
            "title": "Skip duplicates",
            "type": "boolean",
            "description": "Query Salesforce before inserting each batch. Records with a matching Email (Lead/Contact) or Name (Account) are skipped rather than re-created. Ignored when `upsertExternalIdField` is set.",
            "default": true
          },
          "upsertExternalIdField": {
            "title": "Upsert via External Id field (real upsert)",
            "type": "string",
            "description": "Salesforce custom field marked as External Id (e.g. 'External_Id__c'). When set, the actor uses Salesforce's PATCH /sobjects/<Type>/<extIdField>/<value> endpoint for true upsert (creates or updates based on the external id). Make sure the field is marked as External Id in your Salesforce org. Each input lead must carry the external id value in `upsertExternalIdSource` (default 'externalId')."
          },
          "upsertExternalIdSource": {
            "title": "Input field with external id value",
            "type": "string",
            "description": "Input record field whose value provides the external id for each lead (e.g. 'externalId', 'crmId', 'sourceRecordId'). Required when `upsertExternalIdField` is set.",
            "default": "externalId"
          },
          "createAccountIfMissing": {
            "title": "Auto-create Salesforce Account when pushing Contacts",
            "type": "boolean",
            "description": "When `objectType` is 'Contact', auto-resolve and attach an AccountId for each Contact: looks up existing Salesforce Accounts by domain (or by name) and creates Accounts that don't exist before the Contact push. Closes Salesforce's hard requirement that Contacts link to an Account. Ignored for Lead and Account object types.",
            "default": false
          },
          "accountMatchKey": {
            "title": "Account match key",
            "enum": [
              "domain",
              "name"
            ],
            "type": "string",
            "description": "How to match existing Salesforce Accounts when `createAccountIfMissing` is true. 'domain' matches Account.Website against the lead's domain (recommended for B2B). 'name' matches Account.Name against the lead's companyName.",
            "default": "domain"
          },
          "dryRun": {
            "title": "Dry run (preview only)",
            "type": "boolean",
            "description": "Preview the mapped Salesforce records without actually creating or updating anything. Use this to verify your field mapping before committing. Auto-on when mode='audit-only'. Set to false to push live data.",
            "default": true
          },
          "maxLeads": {
            "title": "Max leads to process",
            "minimum": 1,
            "maximum": 5000,
            "type": "integer",
            "description": "Maximum number of leads to process in a single run. Useful for testing with large datasets.",
            "default": 500
          },
          "outputProfile": {
            "title": "Output profile",
            "enum": [
              "minimal",
              "standard",
              "full",
              "llm"
            ],
            "type": "string",
            "description": "Shape the dataset records for different consumers. 'minimal' = ids + status only (CSV-friendly). 'standard' = full result minus diagnostic blocks. 'full' = every field including decisionSnapshot for audit/replay. 'llm' = decision-friendly subset for AI agents.",
            "default": "standard"
          },
          "systemMode": {
            "title": "System mode (auto-enable batch + cohort insights)",
            "type": "boolean",
            "description": "Auto-enables batch insights (run-level list-quality summary) and cohort insights (per-domain rollup) without setting them individually. Use when integrating into recurring workflows.",
            "default": false
          },
          "includeBatchInsights": {
            "title": "Include batch insights",
            "type": "boolean",
            "description": "Append a run-level summary record with success rate, failure category distribution, and sibling-actor recommendations. Auto-on with systemMode.",
            "default": false
          },
          "includeCohortInsights": {
            "title": "Include cohort insights (per-domain)",
            "type": "boolean",
            "description": "Group leads by canonical domain and emit per-domain push outcome on each record + a COHORT_INSIGHTS key in KV. Useful for ABM workflows. Auto-on with systemMode.",
            "default": false
          },
          "pushStrategy": {
            "title": "Push strategy",
            "type": "object",
            "description": "Fine-grained control over which contacts get pushed and how. contactPolicy ('all' | 'best-only' | 'role-based') filters nested contacts[] arrays per lead (best-only = top by seniority; role-based = only senior roles). accountPolicy ('always' | 'only-if-new') skips Account updates on existing accounts when 'only-if-new'. seniorityRoles (string array) overrides the default seniority keyword list for role-based contactPolicy. Each field is optional — defaults come from the mode preset."
          },
          "pushRules": {
            "title": "Push rules (if/then logic)",
            "maxItems": 50,
            "type": "array",
            "description": "Programmable per-lead rules evaluated in order. Each rule has 'if' (conditions: score / grade / emails.count / contacts.count / industry / domain) and 'then' (actions: skip with reason, override leadStatus / leadSource / objectType). Rules with skip:true halt evaluation for that lead. Useful for branching CRM behavior without external orchestration. Example: [{\"if\":{\"score\":{\"gte\":80}},\"then\":{\"leadStatus\":\"Working - Contacted\"}},{\"if\":{\"emails\":{\"count\":{\"eq\":0}}},\"then\":{\"skip\":true,\"skipReason\":\"no_email\"}}]"
          },
          "qualityGate": {
            "title": "Quality gate (pre-push filter)",
            "type": "object",
            "description": "Prevent garbage leads from reaching Salesforce AND from being charged. Set any combination: requireEmail / requireDomain / requirePhone (booleans), minScore (number; lead.score must meet), minFields (number; at least N input fields populated), requireDecisionMaker (boolean; at least one contact with senior title). Failing leads are output as recordType:'skipped' with a skipReason — never billed."
          },
          "decisionProfile": {
            "title": "Decision profile (gate strictness)",
            "type": "object",
            "description": "Tune the decision engine's strictness without rewriting it. Standard fields: strictness ('high' | 'balanced' | 'aggressive'), minScore (number), requireDecisionMaker (boolean). Defaults come from the mode preset."
          },
          "fieldOverrides": {
            "title": "Field overrides (custom Salesforce fields)",
            "type": "object",
            "description": "Remap default Salesforce field names per object type before sending. Useful when your Salesforce org uses custom field names (LinkedIn_URL__c, Lead_Score__c, etc.). Shape: {Lead: {industry: 'Custom_Industry__c'}, Contact: {Title: 'role'}, Account: {Phone: 'Main_Phone__c'}}. Only the specified keys are remapped; everything else is sent as-is."
          },
          "fieldMapping": {
            "title": "Custom field mapping (legacy alias)",
            "type": "object",
            "description": "[Legacy] Override the default field mapping. Keys are input field names, values are Salesforce API field names. Example: {\"linkedinUrl\": \"LinkedIn_URL__c\"}. Replaced by `fieldOverrides` (per object type) — kept for backwards compatibility. The legacy mapping is treated as an override on the active objectType.",
            "default": {}
          },
          "identityResolution": {
            "title": "Identity resolution (multi-signal dedup)",
            "type": "object",
            "description": "Resolve duplicate leads with multi-signal matching beyond simple email. Each lead gets an `identityId` hash, `matchConfidence` 0-1, and `matchType` enum (exact-email | domain+company | name+company | fuzzy | low-signal). Configure: { strategy: 'multi-signal' | 'email-only' | 'domain-only', signals: ['email','domain','linkedin','name+company'], confidenceThreshold: 0.85 }. Default uses all signals."
          },
          "deltaPush": {
            "title": "Delta-only push (skip unchanged records)",
            "type": "boolean",
            "description": "When true AND `watchlistName` is set, the actor compares each lead's canonical fields against the prior run's snapshot and skips records whose hash hasn't changed (no field updates, no score change). Skipped records emit `recordType: 'skipped'` with `skipReason.code: 'no_change_since_last_push'` and the per-record `changeSet` block. Eliminates redundant Salesforce writes on scheduled refresh runs. Requires watchlistName.",
            "default": false
          },
          "simulateScenarios": {
            "title": "Multi-scenario simulation (advanced)",
            "maxItems": 10,
            "type": "array",
            "description": "Compare multiple decision-profile scenarios in one run. Each scenario emits a `recordType: 'scenario'` record with predicted pushable / spend / skip counts AND a delta vs the primary input. Useful for tuning quality gates before production runs. Each item: { name: 'strict', qualityGate: { requireEmail: true, minScore: 80 }, skipDuplicates: true }."
          },
          "fieldConflictPolicy": {
            "title": "Truth resolution: per-field conflict policy",
            "type": "object",
            "description": "Per-field policy resolved against entity history (NOT live CRM — that would add a /sobjects/<Type>/<id> GET per record). Values: 'highest-confidence' (compare values; pick higher when comparable), 'most-recent' (incoming wins — current run is newer than snapshot), 'history-wins' (preserve historical when present), 'incoming-wins' (always overwrite), 'locked' (never overwrite once set). Surfaces `fieldConflicts[]` per record with the resolution + reason. Example: { score: 'highest-confidence', companyName: 'locked', industry: 'history-wins' }."
          },
          "lifecyclePolicies": {
            "title": "Lifecycle policies (stale + decay + archive + re-enrich)",
            "type": "object",
            "description": "Drives behaviour from `entityHistory.lastSeenAt` + `changeSet`. Fields: `staleAfterDays` (default 30), `decayScore` (default false), `decayRatePerDay` (default 0.01 = 1%/day past threshold), `archiveIfNoChangeDays` (default 90), `reEnrichIfStale` (default true — adds enrichment hint to nextActions[]). Surfaces `lifecycleState` block per record."
          },
          "cohortPolicies": {
            "title": "Cohort policies (advisory routing by domain risk)",
            "type": "object",
            "description": "Map cohort risk tier to push action (advisory only — surfaced in lifecycleState.reasons; live in-flight skipping requires 2-pass design, deferred to v1.3). Tiers: high / high_variance / moderate / low. Actions: 'skip' / 'push-all' / 'best-only'. Defaults: high → skip, high_variance → best-only, moderate → push-all, low → push-all."
          },
          "replayRecordId": {
            "title": "Replay record (debug mode)",
            "type": "string",
            "description": "Debug mode: short-circuits the run to replay a single record by eventId. Looks up the prior snapshot in the entity store, re-runs the decision pipeline against the matching lead in the input, emits a `recordType: 'replay'` record showing the diff. NO Salesforce write. Requires `watchlistName` to load the entity store. Use to debug why a record's decision changed across runs."
          },
          "watchlistName": {
            "title": "Watchlist name (cross-run state)",
            "type": "string",
            "description": "Name a watchlist to enable cross-run state: skip leads already pushed in a prior run on the same watchlist (replayMode='skip'), and emit run-over-run delta on the summary record. Leave empty to disable. Each watchlist gets its own named KV store ('salesforce-lead-pusher-state-<name>') bounded at 50K eventIds."
          },
          "monitorStateKey": {
            "title": "Monitor state key (alias for watchlistName)",
            "type": "string",
            "description": "Suite-aligned alias for watchlistName. Either input works; if both are set, watchlistName wins. Lets the same upstream orchestrator pass one consistent field name across salesforce-lead-pusher, hubspot-lead-pusher, lead-scoring-engine, bulk-email-verifier, waterfall-contact-enrichment, phone-number-finder, company-deep-research, and lead-enrichment-pipeline."
          },
          "lastAction": {
            "title": "Last action (closes the feedback loop)",
            "type": "object",
            "description": "Optional. Tells the actor what action you took on this watchlist since the last run. On the next scheduled run, the actor compares the prior push outcome against the current one and emits decisionMemory with an inferred outcome. Honest: only push-result delta is observable — direct Salesforce-side activity (lead conversion, deal stage) is not. Shape: { type: 'pushed-batch' | 'manual-review' | string, takenAt: ISO date, note?: string }. Requires watchlistName / monitorStateKey."
          },
          "replayMode": {
            "title": "Replay mode",
            "enum": [
              "skip",
              "force"
            ],
            "type": "string",
            "description": "When watchlistName is set: 'skip' (default) skips leads whose eventId already appears in the watchlist's processed set — prevents accidental double-billing on re-runs. 'force' pushes regardless (useful for full refresh runs).",
            "default": "skip"
          }
        }
      },
      "runsResponseSchema": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "actId": {
                "type": "string"
              },
              "userId": {
                "type": "string"
              },
              "startedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "finishedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "status": {
                "type": "string",
                "example": "READY"
              },
              "meta": {
                "type": "object",
                "properties": {
                  "origin": {
                    "type": "string",
                    "example": "API"
                  },
                  "userAgent": {
                    "type": "string"
                  }
                }
              },
              "stats": {
                "type": "object",
                "properties": {
                  "inputBodyLen": {
                    "type": "integer",
                    "example": 2000
                  },
                  "rebootCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "restartCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "resurrectCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "computeUnits": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "options": {
                "type": "object",
                "properties": {
                  "build": {
                    "type": "string",
                    "example": "latest"
                  },
                  "timeoutSecs": {
                    "type": "integer",
                    "example": 300
                  },
                  "memoryMbytes": {
                    "type": "integer",
                    "example": 1024
                  },
                  "diskMbytes": {
                    "type": "integer",
                    "example": 2048
                  }
                }
              },
              "buildId": {
                "type": "string"
              },
              "defaultKeyValueStoreId": {
                "type": "string"
              },
              "defaultDatasetId": {
                "type": "string"
              },
              "defaultRequestQueueId": {
                "type": "string"
              },
              "buildNumber": {
                "type": "string",
                "example": "1.0.0"
              },
              "containerUrl": {
                "type": "string"
              },
              "usage": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "integer",
                    "example": 1
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "usageTotalUsd": {
                "type": "number",
                "example": 0.00005
              },
              "usageUsd": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "number",
                    "example": 0.00005
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}