Salesforce logoAPI

Seven Verbs and Two Surfaces: The Complete Salesforce Integration API

Before writing a single call, it pays to see the whole map. The Salesforce integration in CoreModels (vendor key `salesforce`) is small enough to hold in your head: the seven core per-vendor verbs on the interactive HTTP surface, two on the machine-to-machine surface, one artifact, and two roles. The connector declares all three capabilities — **Import, Audit, Generate** — so every verb below is live for Salesforce; nothing in this reference is aspirational.

Seven Verbs and Two Surfaces: The Complete Salesforce Integration API

Before writing a single call, it pays to see the whole map. The Salesforce integration in CoreModels (vendor key salesforce) is small enough to hold in your head: the seven core per-vendor verbs on the interactive HTTP surface, two on the machine-to-machine surface, one artifact, and two roles. The connector declares all three capabilities — Import, Audit, Generate — so every verb below is live for Salesforce; nothing in this reference is aspirational.

The two surfaces differ by who is calling:

  • Interactivegraph/integrations/..., authenticated with your normal CoreModels login token. The full verb set lives here.
  • Machine-to-machinev1/..., which accepts user API keys. This is what CI and unattended callers use. It carries audit and badge; reaudit and history are interactive-only.

Every route is authenticated, and each call is additionally checked against your role in the target project: Admin for the one verb that writes governed content, Viewer for everything else. {PROJECT_ID} is the project's 32-character hex id; the base URL is written as https://coremodels.example.com.

Discovery — GET graph/integrations/vendors

Any authenticated user can ask the API what it supports:

GET https://coremodels.example.com/graph/integrations/vendors
Authorization: Bearer $TOKEN

The response lists every registered connector with its key, display name, capability flags, and artifact contract. The Salesforce entry looks like this:

{ "key": "salesforce", "displayName": "Salesforce",
  "capabilities": "Import, Audit, Generate",
  "artifacts": {
    "describe": "required — JSON array of sObject describe results (GET /services/data/vXX.0/sobjects/{name}/describe, aggregated)"
  } }

Calling any route with a key the registry doesn't know returns success: false with a message naming every registered key — the API tells you what it knows rather than guessing.

Import — POST graph/integrations/salesforce/import/{PROJECT_ID} (Admin)

The one writing verb, and it writes additively: existing governed nodes are never mutated or deleted on re-import; only new estate facts are added, and vendor bookkeeping metadata is refreshed.

POST https://coremodels.example.com/graph/integrations/salesforce/import/{PROJECT_ID}
Authorization: Bearer $TOKEN
Content-Type: application/json

{ "artifacts": { "describe": "<contents of describe.json>" } }

The body contract is shared with audit: artifacts maps artifact name to raw content as a string, and an optional spaces array of space ids targets part of the project (empty means the main space). Omit artifacts and you get a success: false error whose message spells out the required shape.

The response reports datasetsAdded, datasetsSkippedExisting, fieldsAdded, lineageEdgesAdded, lineageEdgesSkipped, nodesEnriched, a snapshotStored flag, and the lossiness and errors channels. Two Salesforce-specific readings: lineageEdgesAdded is 0 today — child relationships are not yet mapped to lineage, and we would rather report an honest zero than invent a DAG — and snapshotStored: false (with a lossiness record) means the org was too large for the snapshot cap of roughly 1.5 MB encoded, in which case fresh-artifact audits still work but re-audit has nothing stored to run against.

Audit — POST graph/integrations/salesforce/audit/{PROJECT_ID} (Viewer)

Read-only, always. The optional recordHistory flag (default false) is the single opt-in side effect: it appends the run to the project's audit history.

POST https://coremodels.example.com/graph/integrations/salesforce/audit/{PROJECT_ID}
Authorization: Bearer $TOKEN
Content-Type: application/json

{ "artifacts": { "describe": "<contents of describe.json>" },
  "spaces": [],
  "recordHistory": true }

The response is the richest payload on the surface:

{
  "success": true,
  "vendor": "salesforce",
  "projectName": "salesforce-org",
  "errorCount": 0,            // the CI-gate number: > 0 means governed meaning is violated
  "warningCount": 2,
  "infoCount": 5,
  "codes": { "picklist-unrestricted": 2, "field-no-help": 5 },
  "driftedObjects": [],       // vendor identities of anything that drifted
  "fingerprint": "b1946ac92492d234",
  "metrics": { },
  "findings": [ /* { section, severity, code, subject, message, detail } */ ],
  "markdown": "...",          // the complete human-readable report
  "historyRecorded": true,
  "lossiness": []
}

Findings are grouped into Coverage (dataset-unmapped, field-unmapped), Drift (dataset-removed, field-removed, field-type-drift, enum-constraint-removed, enum-narrowed, enum-widened, contract-drift), and Conformance, where the Salesforce connector contributes three rules of its own: field-no-help (Info — a custom field without inline help text), picklist-unrestricted (Warning — the platform does not enforce the value set), and polymorphic-reference (Info — only the first lookup target is governed as a reference).

Re-audit — POST graph/integrations/salesforce/reaudit/{PROJECT_ID} (Viewer)

Audit answers "do these fresh artifacts still conform to the governed model?" Re-audit asks the mirror question: "after the governed model changed, does the last-known org still conform?" It runs the same audit engine over the snapshot stored at import time against the current governed model — no artifacts, no Salesforce connection:

POST https://coremodels.example.com/graph/integrations/salesforce/reaudit/{PROJECT_ID}
Authorization: Bearer $TOKEN
Content-Type: application/json

{}

An optional "projectName" selects which stored snapshot to re-audit (null means the latest). The response has the same shape as audit, with one difference in posture: a re-audit run is always recorded in the history — that is the verb's purpose.

History — GET graph/integrations/salesforce/history/{PROJECT_ID} (Viewer)

The rolling audit trail, newest first and capped:

{ "success": true, "vendor": "salesforce",
  "projects": [
    { "projectName": "salesforce-org",
      "runs": [
        { "at": "2026-08-03T09:12:44Z", "trigger": "ci",
          "errorCount": 0, "warningCount": 2, "infoCount": 5,
          "codes": { "picklist-unrestricted": 2, "field-no-help": 5 },
          "fingerprint": "b1946ac92492d234" } ] } ] }

The trigger tells you how each run got there: an explicit audit with recordHistory, a re-audit, a CI call, or the scheduled heartbeat.

Badge — GET graph/integrations/salesforce/badge/{PROJECT_ID} (Viewer)

Returns image/svg+xml rendered from the latest recorded run: green for clean, yellow for warnings only, red for errors, gray for no recorded runs. The label reads salesforce audit. Runs only enter the history when a caller opts in (or re-audits), so the badge reflects deliberate checkpoints, not incidental traffic.

Generate — POST graph/integrations/salesforce/generate/{PROJECT_ID} (Viewer)

Generation closes the loop in the other direction: governed model → Metadata-API CustomObject XML scaffolds, one objects/{ApiName}.object artifact per governed Type.

POST https://coremodels.example.com/graph/integrations/salesforce/generate/{PROJECT_ID}
Authorization: Bearer $TOKEN
Content-Type: application/json

{ "typeNames": ["Invoice__c"] }

typeNames restricts generation (empty means everything eligible). The request shape also accepts targetVersion and extra for connectors that need dialect switches; the Salesforce generator keys off typeNames. The response carries the artifacts inline:

{ "success": true,
  "artifacts": [
    { "name": "objects/Invoice__c.object", "kind": "xml", "content": "<?xml version=\"1.0\" ..." } ],
  "lossiness": [
    { "kind": "ConstraintRelaxation", "path": "Invoice__c.Is Paid",
      "explanation": "Salesforce checkboxes cannot be required; the constraint was dropped." } ],
  "errors": [] }

Inside the XML: governed taxonomies become restricted picklist value sets, governed references become Lookup fields with minted relationship names, NotNull checks become required (checkboxes excepted — hence the lossiness record above), and Unique checks become unique. Standard fields (Id, Name, the audit fields, OwnerId) are never scaffolded, and governed-first fields that never existed in Salesforce are minted with the __c suffix. These are review-and-deploy scaffolds for your own change process — CoreModels never deploys to your org, and the generated file says so in a comment at the top.

Status — GET graph/integrations/salesforce/status/{PROJECT_ID} (Viewer)

The last-import state at a glance:

{ "success": true, "vendor": "salesforce", "imported": true,
  "state": { /* versions, timestamps, fingerprint, counts */ },
  "governedDatasets": 4 }

imported: false simply means no Salesforce import has ever run in this project.

The machine-to-machine surface

Two routes accept user API keys, both Viewer-role and both read-only:

POST https://coremodels.example.com/v1/{PROJECT_ID}/integrations/salesforce/audit
GET  https://coremodels.example.com/v1/{PROJECT_ID}/integrations/salesforce/badge

The v1 audit takes the same body as the interactive one but wraps its response in an envelope: success at the top level, everything else under data — so a CI script tests data.errorCount. When recordHistory is true here, the run is recorded with the CI trigger. The badge is byte-identical to the interactive one, embeddable wherever you can attach the key.

Roles at a glance

VerbRouteRole
DiscoveryGET graph/integrations/vendorsany authenticated
ImportPOST .../salesforce/import/{PROJECT_ID}Admin
AuditPOST .../salesforce/audit/{PROJECT_ID}Viewer
Re-auditPOST .../salesforce/reaudit/{PROJECT_ID}Viewer
HistoryGET .../salesforce/history/{PROJECT_ID}Viewer
BadgeGET .../salesforce/badge/{PROJECT_ID}Viewer
GeneratePOST .../salesforce/generate/{PROJECT_ID}Viewer
StatusGET .../salesforce/status/{PROJECT_ID}Viewer
CI audit / badgePOST / GET v1/{PROJECT_ID}/integrations/salesforce/...Viewer (API keys)

That Viewer-heavy column is deliberate: everything except import is read-only, so the credentials you spread around — CI keys, agent tokens — are the ones that cannot change governed meaning.

What is deliberately not here

There is no live-connection route. LiveSync is a declared-but-deferred capability: CoreModels never holds Salesforce credentials, and every artifact is something you exported yourself. Describe carries inline help text but not Metadata-API long descriptions, so those are not extracted; child relationships are not yet lineage. Each of those limits is stated rather than papered over — the same honesty the lossiness channel applies per call.

One newer surface is out of scope here rather than missing: the sync-plan routes (POST graph/integrations/salesforce/sync/propose/{PROJECT_ID}, plus plan fetch and a sync ledger under graph/integrations/sync/...), which classify fresh artifacts into a reviewable, replayable change plan instead of a report. Audit and re-audit remain the drift verbs this reference covers.

The full worked example of every call above, with the extraction recipe included, is the Salesforce quickstart in the CoreModels docs (quickstarts/salesforce).