Capability · Connectors
It arrives, maps and lands — nobody watching.
Stop being the person who downloads the file. Point us at where it lands and at where it should end up.
- No one has to remember
- Scheduled
- Encrypted, never echoed
- KEK
The job
The file arrives whether or not anyone is watching.
What changes
What you get
Not a demo. The thing that runs every day.
Ingest
Object stores, HTTPS, SQL and SaaS
One connector abstraction over file upload, URL import, a webhook receiver, HTTPS, S3, GCS, Azure Blob, a SQL-over-HTTP endpoint, Google Sheets, Salesforce and HubSpot.
HowConnectorKind in lib/connectors.ts is the list, and a sync normalizes every one of them into the cascade's headers + rows before mapping — so the same template works whatever the source was.
Incremental
A delta where the protocol has one
Where an upstream can express “only what changed”, a scheduled sync uses it. Where it cannot, the sync says so rather than implying a delta it never asked for.
HowIf-Modified-Since for HTTPS and GCS, start-after on an S3 ListObjectsV2 URL, and a {{since}} token substituted into your SQL. Anything else comes back as incremental: "unsupported" — an honest full re-read.
Write-back
The mapped rows land in your table
A destination connector turns a mapping into a delivery: rows inserted or upserted into your database, warehouse, sheet, CRM or bucket.
HowDestinationKind covers sql_write, supabase, bigquery, snowflake, databricks, google_sheets, salesforce, hubspot and the three object stores. on_conflict opts into upsert — plain insert is the default so a re-run cannot silently overwrite your data.
One call
Any input in, your table populated
The whole product in one endpoint: hand it a file, a PDF, a query or a URL, name a destination, and get back a delivery report instead of data.
HowPOST /v1/gateway reads the destination table's own columns and maps onto those, so you do not restate a schema that would drift. A column you name that does not exist fails 422 schema_destination_mismatch BEFORE anything is written, and dry_run reports what would happen.
Secrets
Credentials never travel in a request body
Bucket keys, service-account JSON, SQL endpoint tokens and CRM credentials are encrypted at rest and referenced by connector id — never pasted into an API call.
HowConnector secrets are KEK-encrypted and held service-role-only, and destination.connector_id resolves to a record owned by your workspace. /rotate-secret rotates in place and records rotations_count and last_rotated_at.
Deliver
Signed, idempotent, and never silently dropped
Committed batches go out through a durable queue. A delivery that keeps failing lands somewhere you can see it, and a retry cannot double-apply.
HowX-Mapr-Signature: t=…, v1=… is HMAC-SHA256 over `${t}.${body}` with a 300-second replay window, and every batch carries sha256(uploadId::batchIndex) as its idempotency key. Failures land in a Cloudflare dead-letter queue with admin replay.
Reference
What each connector kind can actually do
Mapping is only useful if data can arrive on a schedule and results can leave reliably. Connectors pull from object stores, HTTPS endpoints, SQL-over-HTTP and SaaS APIs on an hourly, daily or weekly cadence, with every credential KEK-encrypted. On the way out, mapped rows are written into your warehouse or database — or delivered as HMAC-signed, idempotent webhook batches through a durable queue.
- 01
Register once
A connector record holds the config and its KEK-encrypted secrets, service-role only. You reference it by id afterwards — credentials never travel in a request body.
- 02
Pull on a cadence
A Worker cron fires hourly, daily or weekly syncs. Where the protocol can express “only what changed”, the sync uses it; where it cannot, it says so instead of implying a delta.
- 03
Normalize, then map
Every one of the eighteen kinds is flattened to the cascade’s headers + rows, so the same template works whatever the source was — a bucket, a query or a CRM.
- 04
Write, or deliver
Rows are inserted or upserted into your table, or the committed batch goes out through a durable queue: HMAC-signed, idempotency-keyed, with a dead-letter queue behind it.
| Kind | Read | Write | Notes |
|---|---|---|---|
| https · url_import | Yes | — | Incremental via If-Modified-Since |
| s3 · gcs · azure_blob | Yes | Yes | Presigned URL or stored credentials; writes land a file |
| sql_http | Yes | — | Your SQL over an HTTPS endpoint; a {{since}} token makes it incremental |
| sql_write · supabase | — | Yes | INSERT, or upsert with on_conflict |
| bigquery · snowflake · databricks | — | Yes | Warehouse load via each provider’s statement API |
| google_sheets · salesforce · hubspot | Yes | Yes | Paginated read, so a sync reports incremental: unsupported |
| file_upload · webhook_receiver | Yes | — | Receiver gets a one-time signing secret and an inbound URL |
| sftp | Record only | — | A sync returns 501 — the Worker runtime has raw TCP, not SSH |
| postgres | Record only | — | Config stub — a wire-protocol driver is not in v1 |
501 rather than failing a schedule quietly. Connector management is a dashboard surface; POST /v1/connectors/{id}/sync and POST /v1/gateway accept a Bearer key.Try it
A PDF into your database, in one call
Name an input and a destination connector. AdaptivMapr extracts, maps against the destination table’s own columns, validates every row and writes the ones that pass — then hands you the report.
curl https://api.adaptivmapr.com/v1/gateway \
-H "Authorization: Bearer $MAPR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": { "connector_id": "conn_4a91…" },
"destination": {
"connector_id": "conn_e07b…",
"table": "lab_results",
"on_conflict": ["patient_id", "loinc_code", "taken_at"]
}
}'{
"schema_id": "lab_results_v1",
"source": "destination",
"routing": "inline",
"row_count": 1840,
"errors": [
{ "row_index": 812, "field": "loinc_code", "code": "loinc_format", "message": "expected NNNNN-N" }
],
"destination": {
"connector_id": "conn_e07b…",
"kind": "sql_write",
"table": "lab_results",
"schema_source": "destination",
"preflight": "not_needed",
"protocol": "sql",
"written_rows": 1839,
"batches": 4,
"failed_batches": []
}
}- The target columns come from the destination table itself by default — restating a schema in the request would create a second source of truth that drifts the moment someone adds a column. Supply
schemaonly when you mean a deliberate subset. gatewayandconvertshare one compute core, so the mapping, the data-mode gate and the validators are byte-identical between them. They stay separate routes so a read can never accidentally become a write.- Scheduled syncs run on a Worker cron at an hourly, daily or weekly cadence, and a recurring source pairs naturally with layout reuse — the same header row re-maps on a lookup, with no AI, every run.
The surface
Every route this page actually has.
Creating and listing connectors is a dashboard surface — the in-process store does not survive a cold start, and surfacing that over bearer would mislead a programmatic caller. Running one, and the one-call gateway, take a key.
- POST
/v1/gatewayAny input in, your table populated out, a delivery report back. 60 requests a minute per workspace.commit scope - POST
/v1/connectors/{id}/syncPull from a connector now, parse it and persist an upload. Session cookie or bearer key; the workspace must own the connector.connectors scope - GET
/v1/connectors/{id}/schemaThe destination table’s own columns — what the gateway maps onto when you do not restate a schema.read scope - POST
/v1/connectors/{id}/rotate-secretRotate a credential in place, recording rotations_count and last_rotated_at. The old secret is replaced, not archived.connectors scope - POST
/v1/webhooks/testSend a signed test delivery to your endpoint so you can verify your signature check before real data flows.commit scope - GET
/v1/connectorsList and create connectors. Deliberately dashboard-only — bearer callers are refused rather than half-served.dashboard only
Limits & failure modes
What it refuses to do — and the code it says it with.
Eighteen connector kinds, and two of them cannot actually sync from a Worker. We would rather print that here than have a schedule fail quietly at 3am.
501 on sftpA scheduled sync on an sftp connector.
The record is saved and its secret encrypted, but the Worker runtime has raw TCP, not SSH. It returns 501 rather than failing the schedule silently.
postgres is a stubYou expect a direct wire-protocol Postgres connection.
The config saves; the connection does not exist in v1. Use sql_http for reads and sql_write or supabase for writes — both go over HTTPS.
incremental: "unsupported"A source with no delta mechanism — Sheets, Salesforce, HubSpot.
An honest full re-read, reported as one. If-Modified-Since, S3 start-after and a {{since}} token in your SQL are the three places a real delta exists.
422 schema_destination_mismatchYou name a column the destination table does not have.
Refused BEFORE anything is written, and dry_run reports what would have happened. A partial write into the wrong shape is the failure mode this exists to prevent.
Insert, not upsertYou omit on_conflict.
Plain INSERT is the default, on purpose: a re-run must not silently overwrite rows you already have. Opt into upsert explicitly.
Dead-letter queueA webhook delivery keeps failing.
It lands in a Cloudflare DLQ an admin can inspect and replay. Nothing is dropped silently — and the idempotency key makes the replay safe to apply.
What it costs
One prepaid wallet, drawn down per call.
No free tier. Top up from $10 — the balance is shared across the phi-cloud suite — and every operation draws it down at the rate below. An optional $6/mo plan grants a credit that resets each billing cycle instead; overage falls back to the wallet.
A scheduled sync
$0.001
Each mapped file draws the flat per-map fee. A recurring source usually hits the layout cache, so that fee is the whole bill.
Delivery
Free
Queueing, signing, retrying and dead-lettering a webhook batch costs no tokens. Write-back into your table is compute, not AI.
When a source drifts
cost × 2
A changed header row misses the cache and re-runs the cascade — the only time a nightly sync touches the metered layer at all.
A PHI/enterprise-routed run multiplies the whole charge by 1.2 (+20%), flat fee included — and only when the run genuinely got that routing. PHI stays locked until the workspace accepts the BAA in Settings → Security & Data. Full pricing
Questions
The ones asked before signing.
What it costs, what leaves your network, and the claims we will not make.
Which sources can I ingest from today?
Can AdaptivMapr write the results into my database?
What happens if my webhook is down?
How do I stop a retry from double-applying a batch?
Where are connector credentials stored?
Keep reading
The rest of the same engine.
One key, from a messy file to your schema.
Top up a $10 prepaid wallet and start mapping. In schema-only mode only your headers and three clamped rows ever leave you.