AdaptivMapr

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
Scheduled ingest sources reaching one engines3snowflakebigquerysupabasesqlone engine

The job

The file arrives whether or not anyone is watching.

It lands in a bucket at 02:00, or in a folder somebody shares, or behind a SQL endpoint that is only reachable from one network. Then a person downloads it, maps it, and uploads the result somewhere else — a pipeline whose only scheduler is somebody remembering.

What changes

Pull on a schedule from a bucket, a SQL endpoint, Sheets, Salesforce or HubSpot; write the mapped rows into Postgres, BigQuery, Snowflake, Databricks or a bucket. Credentials stay envelope-encrypted and are never returned in plaintext, not even to you.

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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

KindReadWriteNotes
https · url_importYes—Incremental via If-Modified-Since
s3 · gcs · azure_blobYesYesPresigned URL or stored credentials; writes land a file
sql_httpYes—Your SQL over an HTTPS endpoint; a {{since}} token makes it incremental
sql_write · supabase—YesINSERT, or upsert with on_conflict
bigquery · snowflake · databricks—YesWarehouse load via each provider’s statement API
google_sheets · salesforce · hubspotYesYesPaginated read, so a sync reports incremental: unsupported
file_upload · webhook_receiverYes—Receiver gets a one-time signing secret and an inbound URL
sftpRecord only—A sync returns 501 — the Worker runtime has raw TCP, not SSH
postgresRecord only—Config stub — a wire-protocol driver is not in v1
SFTP and postgres records are saved and encrypted but cannot sync from a Worker, and they say so with a 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
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"]
    }
  }'
response
{
  "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": []
  }
}
→ 1 839 of 1 840 rows written · 1 flagged, not written · credentials never left the connector record
  • 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 schema only when you mean a deliberate subset.
  • gateway and convert share 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 sftp

A 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 stub

You 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_mismatch

You 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 upsert

You 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 queue

A 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?
File upload, URL import, a webhook receiver, HTTPS, S3, GCS, Azure Blob, a SQL-over-HTTP endpoint, Google Sheets, Salesforce and HubSpot. SFTP records are saved and encrypted but cannot sync from a Worker — that returns 501 — and the postgres kind is a saved-config stub until a wire-protocol driver exists.
Can AdaptivMapr write the results into my database?
Yes. A destination connector takes the mapped rows and writes them: Postgres or any PostgREST endpoint, Supabase, BigQuery, Snowflake, Databricks, Google Sheets, Salesforce, HubSpot, or a file into S3, GCS or Azure Blob. POST /v1/gateway does the whole thing in one call and returns a delivery report instead of the data.
What happens if my webhook is down?
Delivery runs through a durable Cloudflare Queue. A failing delivery is retried, and one that keeps failing lands in a dead-letter queue where an admin can inspect and replay it. Nothing is silently dropped, and nothing is silently lost.
How do I stop a retry from double-applying a batch?
Every delivery carries a deterministic idempotency key — sha256(uploadId::batchIndex) — and an X-Mapr-Signature header: an HMAC-SHA256 over `${t}.${body}` with the timestamp bound in. Verify the signature, reject a stale timestamp, dedupe on the key, and a retried batch applies exactly once.
Where are connector credentials stored?
Encrypted with a key-encryption-key and held service-role-only, separate from the dashboard plane. They are never accepted in a request body — you reference a connector by id — and rotation happens in place via /rotate-secret, which records when and how often.

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.

Connectors & webhooks — AdaptivMapr — AdaptivMapr