# Azure Synapse — API: The Synapse Export, Route by Route: A CoreModels API Reference

Every format in the CoreModels transform surface belongs to two lists - the formats we decode and the formats we encode - and `synapse` appears in exactly one of them. The decode list is `jsonschema | shex | avro | jsonld | sql | osi | osi-json | owl | linkml | protobuf | odcs | odm`; the encode list swaps `odm` out and `synapse` in. This page is the working reference for that one direction: which routes produce a Synapse-ready schema, what each accepts, which one controls the registered-schema `$id`, and how to bring a Synapse schema back in anyway.

# The Synapse Export, Route by Route: A CoreModels API Reference

Every format in the CoreModels transform surface belongs to two lists - the formats we decode and the formats we encode - and `synapse` appears in exactly one of them. The decode list is `jsonschema | shex | avro | jsonld | sql | osi | osi-json | owl | linkml | protobuf | odcs | odm`; the encode list swaps `odm` out and `synapse` in. This page is the working reference for that one direction: which routes produce a Synapse-ready schema, what each accepts, which one controls the registered-schema `$id`, and how to bring a Synapse schema back in anyway.

We are CoreModels (by ARAMAI); the transform routes live under `https://coremodels.example.com/graph/transform/...` with no `api/` prefix, all `[Authorize]`d and project-scoped like every other graph API. Every response uses one envelope - `success`, `lossiness`, `errors`, plus the payload key for the route - and `success: true` means "it ran", never "nothing changed". The `lossiness` array is the change record.

## Route 1: export a governed project - the full-control path

`POST graph/transform/schema/export/{projectId}` reads the project's schema and encodes it. **Viewer** role suffices; nothing is written. This is the only REST route that accepts the three Synapse identity options, so it is the one to use when the schema is headed for actual registration:

```json
{
  "format": "synapse",
  "synapseOrg": "myorg.dcc",
  "synapseName": "experimentalData.biopsy",
  "synapseVersion": "0.1.0"
}
```

The three keys compose the registered-schema `$id` as `{org}-{name}-{semver}` under the registered base URI. Their contracts, stated plainly:

- **`synapseOrg`** - your Synapse Organization name. Default `example`, which is a placeholder: registration requires a pre-created, unique organization, and `org.sagebionetworks` is reserved for Synapse's own core models.
- **`synapseName`** - the schema-name segment. Default: derived from the project's root type label. Dots are legal (`experimentalData.biopsy`); anything that cannot live in the segment is folded to a camelCase boundary, because the hyphen is the delimiter *between* segments.
- **`synapseVersion`** - a plain `major.minor.patch`. Default `0.1.0`-style is not assumed - the default is `0.0.1`. Synapse rejects re-registering an existing version, so bump on every change. A version that is not plain semver (`1.0.0-rc1`, `0.0.1 beta`) cannot be expressed in the `$id` at all; the export falls back to the default and says so in the ledger. An empty string is meaningful: it emits the unversioned registered-pointer form of the `$id`.

A representative response for a small project:

```json
{
  "success": true,
  "lossiness": [
    {
      "kind": "TypeApproximation",
      "path": "Element[BiopsyCollectedOn]",
      "explanation": "DateTime emitted as 'string' with format 'date-time'; draft-07 treats 'format' as an annotation and Synapse's enforcement is unconfirmed - pending a Sage fixture."
    },
    {
      "kind": "StructuralDrop",
      "path": "Taxonomy[BiopsyStage]",
      "explanation": "Term hierarchy flattened: a draft-07 enum is a flat value list. The hierarchy survives only in the curation manifest's value-set section."
    }
  ],
  "errors": [],
  "schema": {
    "$schema": "http://json-schema.org/draft-07/schema#",
    "$id": "https://repo-prod.prod.sagebase.org/repo/v1/schema/type/registered/myorg.dcc-experimentalData.biopsy-0.1.0",
    "title": "Biopsy",
    "type": "object",
    "properties": {
      "collectedOn": { "type": "string", "format": "date-time" },
      "stage": { "enum": ["stageI", "stageII", "stageIII"], "type": "string" }
    },
    "required": ["stage"]
  }
}
```

Both ledger entries are worth internalizing. The first is honesty about a behavior we will not invent: draft-07 defines `format` as an annotation, and whether Synapse enforces `date-time` as an assertion is not settled by the public documentation, so we emit the keyword *and* record the uncertainty. The second tells you that a hierarchical vocabulary became a flat `enum` - and points you at the artifact where the hierarchy does survive, the curation manifest (generated by the `generate_synapse_manifests` MCP tool, covered in the next article).

## Route 2: stateless conversion - any source, defaults on the `$id`

`POST graph/transform/schema/map/{projectId}` converts without touching the project (**Viewer**; the project scopes auth only). Any decodable source format can feed it - here, SQL DDL straight to a Synapse-ready schema:

```json
{
  "sourceFormat": "sql",
  "sourceSchema": "CREATE TABLE Biopsy (\n  sample_id VARCHAR(64) NOT NULL,\n  stage VARCHAR(32),\n  vial_count INTEGER\n);",
  "targetFormat": "synapse",
  "targetHintFormat": "sql",
  "targetHintSchema": "CREATE TABLE Biopsy (\n  sample_id VARCHAR(64) NOT NULL,\n  stage VARCHAR(32),\n  vial_count INTEGER\n);",
  "mapping": { "kind": "inferred" }
}
```

The hint is required because the default `inferred` strategy matches labels against a target vocabulary; for a pure conversion, the source is its own hint. The response adds a `plan` key next to `schema` and `lossiness` - the executed mapping as a replayable artifact.

One direction-honesty note that saves a confused afternoon: **the mapping routes encode `synapse` with the default `$id` segments** (`example`, root-type name, `0.0.1`). The `synapseOrg`/`synapseName`/`synapseVersion` keys exist on `schema/export` and on the `transform_schema` MCP tool. If a stateless conversion is destined for registration, either re-encode through a project export or drive it over MCP.

## Route 3: replay a stored plan

`POST graph/transform/plan/execute/{projectId}` (**Viewer**, stateless) re-executes a previously returned plan against the same source - the plan travels as a JSON *string*:

```json
{
  "sourceFormat": "sql",
  "sourceSchema": "CREATE TABLE Biopsy (\n  sample_id VARCHAR(64) NOT NULL,\n  stage VARCHAR(32),\n  vial_count INTEGER\n);",
  "plan": "{\"operations\":[{\"kind\":\"TypeMapping\",\"origin\":\"Inferred\",\"sourceTypeId\":\"Biopsy\",\"targetTypeId\":\"Biopsy\"}]}",
  "targetFormat": "synapse"
}
```

A parsed plan is still just a proposal: it passes the identical validation gate every strategy passes, then executes deterministically. Same plan, same source, same output - which is what makes the plan worth storing in the first place. The `synapse` encode here also uses the default `$id` segments.

## The round trip, honestly

There is no `synapse` import, by design. Attempting `POST graph/transform/schema/import/{projectId}` with `"format": "synapse"` returns:

```json
{
  "success": false,
  "lossiness": [],
  "errors": [
    {
      "path": "format",
      "message": "'synapse' is encode-only: a Synapse schema is plain draft-07 JSON Schema - decode it with the 'jsonschema' format."
    }
  ]
}
```

The message is the workflow: a registered Synapse schema is valid draft-07 JSON Schema, so import it with the format that reads JSON Schema -

```json
{
  "format": "jsonschema",
  "schema": "{ \"$schema\": \"http://json-schema.org/draft-07/schema#\", \"title\": \"Biopsy\", \"type\": \"object\", \"properties\": { \"stage\": { \"type\": \"string\", \"enum\": [\"stageI\", \"stageII\", \"stageIII\"] } }, \"required\": [\"stage\"] }"
}
```

- and the round trip closes: project → `synapse` export → (register, bind, validate in Synapse) → `jsonschema` import back into a project. The asymmetry of keys is not an asymmetry of capability; it is an accurate description of what a Synapse schema *is*.

## Roles, in one paragraph

Stateless routes (`schema/map`, `plan/execute`) and the read-only export need **Viewer** on the scoping project. Anything that writes to a project (`schema/import`, and `schema/mapImport` without dry-run) needs **Admin**. One override: `mapping.kind = "ai"` needs **Editor or Admin** membership even on otherwise-Viewer routes, because it invokes the server-side Anthropic API - and it is declined honestly, in the `errors` channel, when no server key is configured.

## Failure modes worth knowing before they happen

- **Unknown target format** - the error lists the encode formats, which is the fastest way to check spelling: `jsonschema | shex | avro | jsonld | sql | osi | osi-json | owl | linkml | protobuf | odcs | synapse`.
- **Non-semver `synapseVersion`** - the call *succeeds*, the `$id` carries `0.0.1`, and a `SemanticNarrowing` record at `#/$id` names the version you sent. If your pipeline depends on the version segment, assert on that path (the automation article shows the exact `jq` gate).
- **Source dialect other than draft-07** - down-converted, recorded at `#/$schema`; a source document's own `$id` is replaced and recorded at `#/$id`.
- **Constraints missing from the output** - check `lossiness` for a `ConstraintRelaxation` at that exact path before suspecting a bug; the Synapse `JsonSchema` object simply has no field for keywords like `multipleOf`, `exclusiveMinimum`, `patternProperties`, or `dependencies`.

That is the whole REST surface for this profile: one route with full identity control, two stateless routes with defaults, an import path that goes through `jsonschema`, and a ledger on every response that tells you what the subset cost before Synapse tells you the hard way.
