Every REDCap Route in CoreModels: the Full HTTP Reference
The CoreModels REDCap connector declares all three capabilities — **Import, Audit, Generate** — which means every integration verb on our HTTP surface is live for it. This article is the complete route-by-route reference: what each endpoint takes, what it returns, which role it requires, and which of the two HTTP surfaces it lives on. If you're writing a script, a service, or an internal tool against the REDCap integration, this is the page to keep open.
Every REDCap Route in CoreModels: the Full HTTP Reference
The CoreModels REDCap connector declares all three capabilities — Import, Audit, Generate — which means every integration verb on our HTTP surface is live for it. This article is the complete route-by-route reference: what each endpoint takes, what it returns, which role it requires, and which of the two HTTP surfaces it lives on. If you're writing a script, a service, or an internal tool against the REDCap integration, this is the page to keep open.
Two surfaces, one posture
CoreModels exposes vendor integrations on two surfaces:
- The interactive surface (
/graph/integrations/...) authenticates with your normal CoreModels login token and carries the full verb set: import, audit, reaudit, history, badge, generate, status, plus connector discovery. - The machine-to-machine surface (
/v1/...) accepts user API keys and is what CI and unattended services should call. It deliberately carries a smaller set: audit and badge only.reauditandhistorylive on the interactive surface.
The posture across both is read-authority: import writes to the graph (additively — existing governed nodes are never mutated), while audit and generate never write anything. Recording an audit run in the history is opt-in bookkeeping, and the reaudit verb always records its run.
Roles at a glance:
| Route | Surface | Role |
|---|---|---|
GET /graph/integrations/vendors | interactive | any authenticated user |
POST /graph/integrations/redcap/import/{projectId} | interactive | Admin |
POST /graph/integrations/redcap/audit/{projectId} | interactive | Viewer |
POST /graph/integrations/redcap/reaudit/{projectId} | interactive | Viewer |
GET /graph/integrations/redcap/history/{projectId} | interactive | Viewer |
GET /graph/integrations/redcap/badge/{projectId} | interactive | Viewer |
POST /graph/integrations/redcap/generate/{projectId} | interactive | Viewer |
GET /graph/integrations/redcap/status/{projectId} | interactive | Viewer |
POST /v1/{projectId}/integrations/redcap/audit | API key | Viewer |
GET /v1/{projectId}/integrations/redcap/badge | API key | Viewer |
Everything below uses https://coremodels.example.com as the API host and $TOKEN for the bearer credential.
Discovery
GET https://coremodels.example.com/graph/integrations/vendors
Authorization: Bearer $TOKEN
Returns every registered connector with its capabilities and expected artifacts. The REDCap entry:
{ "key": "redcap", "displayName": "REDCap",
"capabilities": "Import, Audit, Generate",
"artifacts": {
"data_dictionary": "required — the data dictionary CSV (Project Setup → Data Dictionary download, or the API 'metadata' export as CSV)"
} }
One artifact, always required. Calling any REDCap verb with a vendor key that isn't registered gets you an explicit error: Unknown vendor '<v>'. Registered: <comma-joined keys>.
Import — Admin
POST https://coremodels.example.com/graph/integrations/redcap/import/{projectId}
Authorization: Bearer $TOKEN
Content-Type: application/json
{ "artifacts": { "data_dictionary": "<contents of data_dictionary.csv>" } }
The body is an artifacts request: artifacts maps artifact name to raw content (required — omitting it returns success: false with an error spelling out the expected 'artifacts': { "<name>": "<content>" } shape), and an optional spaces array targets specific space ids (empty means the project's main space). The response reports counts, whether the parsed dictionary snapshot was stored for later re-audits, and the two honesty channels — lossiness (what a successful import approximated or dropped) and errors (what made it unable to proceed).
The counts are most instructive on a re-import. Say three instruments (64 variables) are already governed, and the dictionary you upload now adds a fourth instrument with twelve variables plus two new variables on an existing instrument:
{ "success": true, "vendor": "redcap", "projectName": "redcap-project",
"datasetsAdded": 1, "datasetsSkippedExisting": 3, "fieldsAdded": 2,
"lineageEdgesAdded": 0, "lineageEdgesSkipped": 0, "nodesEnriched": 82,
"snapshotStored": true, "lossiness": [], "errors": [] }
Four counters, four different questions. datasetsAdded: 1 — the new instrument, written whole; its twelve variables arrive with it, which is exactly why they do not show up in fieldsAdded. fieldsAdded: 2 — that counter is specifically the additive pass over datasets that already existed, so it means "two variables were grafted onto an already-governed instrument". datasetsSkippedExisting: 3 — already governed, left untouched: the additive contract in numbers. nodesEnriched: 82 is one write per instrument plus one per variable (4 + 78), because vendor-metadata bookkeeping — native types, checks, PHI marks, branching logic — is refreshed on every import even for nodes that already existed; governed meaning is not. lineageEdgesAdded is always 0 for REDCap: a data dictionary carries structure, not pipelines.
One thing to watch on that additive pass: if a variable grafted onto an already-governed instrument carries a choice list, the Element is created but its Taxonomy is not — governed value sets are a human decision, not an import side effect. You get a ConstraintRelaxation entry in lossiness saying so, and the audit surfaces the same gap afterwards.
Audit — Viewer
POST https://coremodels.example.com/graph/integrations/redcap/audit/{projectId}
Authorization: Bearer $TOKEN
Content-Type: application/json
{ "artifacts": { "data_dictionary": "<data_dictionary.csv>" },
"recordHistory": true }
recordHistory defaults to false — the audit verb stays strictly read-only unless asked. When true, the run is appended to the rolling history with trigger audit. The response is the full audit report:
{ "success": true, "vendor": "redcap", "projectName": "redcap-project",
"errorCount": 0, "warningCount": 1, "infoCount": 4,
"codes": { "phi-fields": 2, "text-no-validation": 2, "duplicate-choice-codes": 1 },
"driftedObjects": [],
"fingerprint": "9b1c2a7e0d4f8a31",
"metrics": {
"Datasets (estate)": "3",
"Datasets governed": "3 / 3",
"Fields governed": "64 / 64",
"Governed nodes with canonical mappings": "0 / 67 (0%)",
"Last import": "2026-07-27T08:55:10.4120000+00:00"
},
"findings": [
{ "section": "Conformance", "severity": "Warning", "code": "duplicate-choice-codes",
"subject": "demographics.consent_status",
"message": "Choice list reuses a code — exported data for this field is ambiguous.",
"detail": null }
],
"markdown": "…PR-comment-ready report…",
"historyRecorded": true,
"lossiness": [] }
(The findings array is abridged to one entry here; a real response carries one object per finding, and codes is its per-code tally.) metrics holds the headline numbers that lead the markdown report — how much of the estate is governed, and how many governed nodes also carry a mapsTo to something other than REDCap, which is the canonical-mapping coverage figure. It reads 0 / 67 (0%) until you start binding ontology or standard terms to those nodes.
Findings fall 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 REDCap-specific rules live: phi-fields (Info — aggregated PHI inventory per instrument), text-no-validation (Info), duplicate-choice-codes (Warning). The contract that matters to automation: errorCount > 0 means the artifact violates governed meaning.
Reaudit — Viewer
POST https://coremodels.example.com/graph/integrations/redcap/reaudit/{projectId}
Authorization: Bearer $TOKEN
Content-Type: application/json
{}
Reaudit is the audit's mirror image. A live audit asks "do these fresh artifacts still conform to the governed model?"; reaudit asks "does the governed model still match the last-known estate?" It runs the same audit engine over the dictionary snapshot stored at import time against the current governed model — no artifacts, no REDCap access, no credentials. The optional body property projectName selects a specific vendor-side project (null means the latest stored snapshot), and spaces narrows scope. Unlike audit, the run is always recorded in the history, with trigger reaudit. The response shape is the same audit report shown above, plus one extra metric — Snapshot stored, the timestamp of the import whose dictionary is being replayed, so you always know how old the estate side of the comparison is. If nothing has been imported, or the dictionary was over the ~1.5 MB encoded snapshot cap, there is nothing to replay and the call says so: No stored estate snapshot for vendor 'redcap' — import the vendor project first (imports persist the parsed snapshot).
History — Viewer
GET https://coremodels.example.com/graph/integrations/redcap/history/{projectId}
Authorization: Bearer $TOKEN
{ "success": true, "vendor": "redcap",
"projects": [
{ "projectName": "redcap-project",
"runs": [
{ "at": "2026-07-27T09:12:44.7130000+00:00", "trigger": "reaudit",
"errorCount": 1, "warningCount": 0, "infoCount": 3,
"codes": { "field-type-drift": 1, "phi-fields": 3 },
"fingerprint": "9b1c2a7e0d4f8a31" } ] } ] }
The trail is rolling: one trail per vendor-side project, newest run first, capped at the 50 most recent (an append past the cap drops the oldest). Each run records its trigger (audit, reaudit, ci, or scheduled from the opt-in server-side heartbeat), the three counters, the per-code counts, and the artifact fingerprint, which lets you see at a glance whether two runs audited the same dictionary bytes.
Badge — Viewer
GET https://coremodels.example.com/graph/integrations/redcap/badge/{projectId}
Authorization: Bearer $TOKEN
Returns image/svg+xml: a self-contained shields-style badge labeled redcap audit, rendered from the latest recorded run. Green (#4c1) is clean, yellow (#dfb317) warnings only, red (#e05d44) errors, gray (#9f9f9f) no recorded runs.
Generate — Viewer
POST https://coremodels.example.com/graph/integrations/redcap/generate/{projectId}
Authorization: Bearer $TOKEN
Content-Type: application/json
{ "typeNames": [] }
Generate reverses the flow: governed model out, vendor artifact in hand. typeNames restricts output to the named types (empty means everything eligible); targetVersion and extra exist on the request shape for vendors that need dialect switches — REDCap doesn't. The response:
{ "success": true,
"artifacts": [
{ "name": "coremodels_data_dictionary.csv", "kind": "csv",
"content": "\"Variable / Field Name\",\"Form Name\",…" } ],
"lossiness": [], "errors": [] }
One artifact: an upload-ready data dictionary CSV with the full 18-column REDCap header. Governed taxonomies become dropdowns with minted numeric codes, NotNull checks become Required Field? = y, and governed types become validation types (integer, number, date_ymd) — Boolean becomes a yesno field. Cross-form references have no REDCap slot and are carried as plain text fields — a documented limit of the format, not silent loss. If no governed fields are eligible, the call fails explicitly with No eligible governed fields found to emit as a data dictionary. rather than emitting an empty file.
Status — Viewer
GET https://coremodels.example.com/graph/integrations/redcap/status/{projectId}
Authorization: Bearer $TOKEN
Returns { "success": true, "vendor": "redcap", "imported": true, "state": { … }, "governedDatasets": 3 } — whether an import has happened, the last-import state recorded on the integration state node (timestamps, fingerprint, counts, facts), and how many governed datasets currently resolve to Types in the graph.
The v1 surface — audit and badge for machines
User API keys authenticate here, which is why CI calls it. The audit takes the identical artifacts request, but the response is wrapped in the standard ApiResponse envelope, so every field above lives under data.*:
curl -sS -X POST \
"https://coremodels.example.com/v1/$PROJECT_ID/integrations/redcap/audit" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
--data-binary @audit-request.json | jq '.data.errorCount'
With recordHistory: true, runs recorded through this surface carry the trigger ci — so your history distinguishes gate runs from interactive ones. The badge is also here (GET /v1/{projectId}/integrations/redcap/badge), embeddable wherever you can attach a user API key. That's the entire v1 vendor surface by design: the machine surface audits and reports; it never imports, never generates, never writes.
One honest limitation
There is no live REDCap connection on any route. LiveSync is a declared-but-deferred capability across our connectors: you upload the dictionary CSV your REDCap tooling already produces, and your REDCap API token never reaches CoreModels.
The compact version of this reference, with extraction recipes, lives in the REDCap quickstart in the CoreModels docs (docs/quickstarts/redcap).