# Salesforce — API: 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:

- **Interactive** - `graph/integrations/...`, authenticated with your normal CoreModels login token. The full verb set lives here.
- **Machine-to-machine** - `v1/...`, 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:

```http
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:

```json
{ "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.

```http
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.

```http
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:

```jsonc
{
  "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:

```http
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:

```json
{ "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.

```http
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:

```json
{ "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:

```jsonc
{ "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:

```http
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

| Verb | Route | Role |
|---|---|---|
| Discovery | `GET graph/integrations/vendors` | any authenticated |
| Import | `POST .../salesforce/import/{PROJECT_ID}` | Admin |
| Audit | `POST .../salesforce/audit/{PROJECT_ID}` | Viewer |
| Re-audit | `POST .../salesforce/reaudit/{PROJECT_ID}` | Viewer |
| History | `GET .../salesforce/history/{PROJECT_ID}` | Viewer |
| Badge | `GET .../salesforce/badge/{PROJECT_ID}` | Viewer |
| Generate | `POST .../salesforce/generate/{PROJECT_ID}` | Viewer |
| Status | `GET .../salesforce/status/{PROJECT_ID}` | Viewer |
| CI audit / badge | `POST` / `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`).
