Skip to Content
ReferenceConfig V3Config surface

GET /api/config-surface: the machine-readable config catalog

Every page under reference/config-v3/ and reference/event-flow-manifests/ describes the config syntax by hand, for a human reading the docs. GET /api/config-surface describes the same syntax as data: the closed set of step actions, transforms, when conditions and event source types your org can use, plus the full JSON Schema of the config document itself. It is the same catalog the in-app editor’s live validation and error messages are generated from, so it never drifts from what a PATCH /draft will actually accept.

Use it to:

  • Discover the full action/transform/source vocabulary without reading step-actions.md top to bottom.
  • Drive tooling: a linter, an IDE plugin, a codegen script for your own config authoring pipeline.
  • Validate a connector config document structurally before you push it (see below).

Calling it

It is a plain authenticated GET, same Bearer contract as every other endpoint in the config plane. An org API key works well here since this is typically called from a script rather than an interactive session:

curl -s https://app.plugsync.com/api/config-surface \ -H "Authorization: Bearer $ORG_API_KEY" | jq 'keys_unsorted' # -> ["actions", "transforms", "when", "sources", "json_schema", "plugin_embedded_fields"]

No path parameters, no query parameters, no request body. The response is the same for every org: it describes what the platform supports, not anything org-specific.

Response structure

actions

One entry per step action (upsert, find, transform, plugin, …):

{ "name": "transform", "description": "Project the field mappings of one schema into another (source schema -> canonical, or canonical -> step output).", "category": "mapping", "min_version": "3.0", "fields": [ {"name": "entity", "type": "ref_entity", "required": true, "description": "", "default": null, "enum_values": null}, {"name": "to_schema", "type": "ref_schema", "required": true, "description": "Destination schema; 'canonical' writes $.canonical.", "default": null, "enum_values": null}, {"name": "from_schema", "type": "ref_schema", "required": true, "description": "Source schema whose field_mappings are applied.", "default": null, "enum_values": null} ], "required_checks": [ {"fields": ["entity", "to_schema", "from_schema"], "message": "transform step requires entity, to_schema and from_schema", "mode": "any_falsy", "equals": null} ], "limits": [], "examples": [] }
  • min_version is "3.0" or "3.1": the 5 bulk actions (upsert_many, create_many, update_many, delete_many, associate_many) require version: "3.1" on the connector config.
  • required_checks names the fields the runtime rejects a step for omitting, and the exact error message it raises. These are enforced at draft-save/publish time, not by the JSON Schema below.
  • limits documents a hard architectural constraint on that action (a wall, not a bug): text meant to be shown verbatim in a UI or error message.
  • examples, when present, are complete valid steps for shapes that are not obvious from the field table alone.

This is the same data step-actions.md narrates by hand, action by action, with worked examples and edge cases the catalog itself does not carry.

transforms

One entry per transform usable inside a field_mappings entry’s transform list:

{"name": "lowercase", "kind": "builtin", "description": "Convert string to lowercase. Non-strings pass through.", "args": []}
{ "name": "split", "kind": "parametric", "description": "Split a string on a given separator and take an index. Lenient.", "args": [ {"name": "sep", "type": "string", "required": true, "description": "Separator to split on (NOT whitespace)."}, {"name": "index", "type": "any", "required": false, "description": "Index of the part to keep (default 0)."}, {"name": "default", "type": "any", "required": false, "description": "Value returned when the index is out of range (instead of the unchanged input)."} ] }

kind: "builtin" takes no arguments; kind: "parametric" declares its args the same way an action declares fields.

when

One entry per when condition variant (exists, found, equals, gt, lt, in, not, all):

{"key": "exists", "value_type": "jsonpath", "description": "True if the $-ref resolves to a non-null value."}

value_type says what shape the condition’s value takes: a JSONPath string, an arbitrary object, a nested condition, or a list of nested conditions. See step-actions.md for the full semantics of each.

sources

One entry per event source type (event_api, webhook, poller, scheduled, hubspot, rest, file):

{ "type": "poller", "push": false, "description": "REST pull: GET on a cron schedule, one event per item; optional credential auth, static and secret headers, and page-following.", "fields": [ {"name": "inbound.url", "type": "string", "required": true, "description": "GET endpoint. ..."}, {"name": "inbound.schedule", "type": "cron", "required": false, "description": "Cron expression; empty fires every worker tick."} ], "limits": [ {"id": "poller_no_hmac_signing", "text": "Auth supports bearer/basic/api-key headers and OAuth2 client credentials via a bound credential, plus static and secret headers; creds-in-body login and HMAC-signed batch polling are not implemented."} ] }

push: true means the source is invoked by an external system (an inbound webhook or the Event API); push: false means plugsync initiates the call (a poller, a cron tick, an SFTP listing). fields covers the dotted key paths accepted inside that source type’s config object, e.g. inbound.url for a poller — the same config an entry of the config document’s own sources key carries, since a source’s definition is part of that document ({"name", "type", "config"} per entry, SourceV3 in json_schema.$defs). What is NOT in the document, and therefore not described here, is the state half of a source: its enabled flag, its secret and its runtime state. See EventSources for both halves and the API that manages them.

Credential references are fields like any other. A rest source lists outbound.credential_name and outbound.secret_headers, the latter a map of header name to the name of an api_key credential whose value is sent as that header: the config holds only the credential name, and a dry-run shows the value as <redacted>. See Secret static headers. A poller lists the same keys under inbound (inbound.credential_name, inbound.default_headers, inbound.secret_headers) for its GET. See Authenticating the GET.

json_schema

The full JSON Schema (draft 2020-12) of the config document itself: ConnectorConfigV3.model_json_schema(). This is the root object you PATCH to a connector’s /draft, with $defs holding every nested model (EntityV3, FlowV3, StepV3, FieldMapping, the WhenAll/WhenEquals/… condition variants, and so on):

curl -s https://app.plugsync.com/api/config-surface \ -H "Authorization: Bearer $ORG_API_KEY" | jq '.json_schema | keys, (."$defs" | keys)'

Where the schema can express a default value declaratively (a literal, or one computed from a zero-argument default factory), it does — so a field you never set but that the server materializes on save (e.g. an empty list for a collection field) is visible here as "default": [], not just absent.

plugin_embedded_fields

A flat list of field names, e.g. ["function_arn", "version", "capabilities"], that a published version’s plugin step carries but a draft never declares by hand. These are resolved and embedded at publish time (the plugin’s resolved function_arn, its promoted version, its capability grants); a config-authoring tool that diffs a draft against a published version should ignore this list rather than reporting it as an externally-made change on every plugin step.

Validating a config before you push it

json_schema is structural: it accepts or rejects the shape of a config document (required keys present, types right, enum values in range, StepV3 fields matching the action’s declared type). It does not encode the per-action required_checks or limits from the actions/sources sections above — those live in the runtime validator and are checked at draft-save or publish time regardless of what a structural check finds.

Two real consumers already validate against this schema before a config reaches the server:

  • The in-app config editor compiles an Ajv  validator against json_schema.$defs.StepV3 and flags structural errors on a step as you type it. This check is advisory: the backend’s own validation (including required_checks) is what actually gates a save.
  • A schema-defaults walker reads json_schema to tell a locally-edited field apart from one the server fills in with its own default, so a diff against the last published version does not report noise for fields nobody touched.

You can run the same kind of check yourself, against the whole document (not just one step), before you push:

curl -s https://app.plugsync.com/api/config-surface \ -H "Authorization: Bearer $ORG_API_KEY" | jq .json_schema > config-v3.schema.json
import json import jsonschema schema = json.load(open("config-v3.schema.json")) config = json.load(open("my-connector.json")) jsonschema.validate(config, schema) # raises jsonschema.ValidationError with the offending path

A clean run here only means the document is well-formed; it is not a substitute for the real preflight (GET .../preflight, or plugsync validate), which is what the backend actually gates a publish on and is the only place required_checks/limits are enforced.

Out of scope

Last updated on