Insert by default, merge when you say so
How · on_conflict adds the PostgREST query parameter and Prefer: resolution=merge-duplicates. Opt-in by design: a silent overwrite of customer data is never the default.
Integration · Database
POST batched JSON rows into PostgREST — Supabase or any PostgREST deployment. Insert by default, upsert when you name the conflict columns, and the database’s own error text when something is wrong.
How it works
PostgREST does not accept raw SQL — it takes an array of row objects at a table endpoint — which is why this is its own protocol rather than a flag on the SQL destination. AdaptivMapr builds the endpoint from your project URL and table, sends rows in batches of 500 with Prefer: return=minimal, and reports what landed. Naming conflict columns turns the insert into an upsert; leaving them out means a re-run can never silently overwrite what is already there.
<project-url>/rest/v1/<table>, or your own mount path if the base URL already ends in one.written_rows, batches and failed_batches. Every batch failing is a 502 with PostgREST’s own message, not a success.What you get
How · on_conflict adds the PostgREST query parameter and Prefer: resolution=merge-duplicates. Opt-in by design: a silent overwrite of customer data is never the default.
How · Pointless bandwidth for any payload, and for PHI an avoidable second copy in a second place. The response is a count, not your data.
How · POST /v1/connectors/{id}/test names which check it actually ran, so the UI never implies more assurance than was obtained. Nothing invents rows in a customer table.
How · normalizeSecretField() folds every provider spelling — private_key, token, secret_access_key, account_key, service_account_json — into one auth_value field, which is KEK-envelope-encrypted before the row is written. A GET masks it to a 4-character hint. If encryption fails the field is dropped rather than stored in plaintext.
Configuration
Prefer a key scoped to that one table — or an RLS policy that limits it — over a full service-role key. The create response says exactly that, because it is the difference between a credential that can insert and one that can do anything.
| Key | Required | What it is |
|---|---|---|
| url | Required | The PROJECT url, e.g. https://<ref>.supabase.co. A table endpoint is refused — drop the /rest/v1/<table> suffix and set table instead. |
| table | Required | Target table. Must be a bare identifier. Overridable per call with destination.table. |
| auth_valuesecret | Required | A service-role or table-scoped API key / PostgREST JWT. Encrypted at rest. |
| schema | Optional | A non-public Postgres schema, sent as the Content-Profile header. |
In code
Name the input and the destination. The target columns come from the table itself, every row is validated, and only the ones that pass are written.
/v1/connectorsSave the connector. The secret is encrypted before it reaches Postgres.session/v1/connectors/{id}/testMake a real call and report what was actually proven.session/v1/connectors/{id}/schemaRead the target’s own columns — metadata only, never row data.bearer/v1/gatewayAny input in, this destination populated, a delivery report out.bearer/v1/connectors/{id}/rotate-secretReplace the credential in place; the old one becomes unrecoverable.session/rest/v1 appended.dry_run reports the batch count without sending anything.{
"kind": "supabase",
"name": "Clinical warehouse",
"config": {
"url": "https://abcdefgh.supabase.co",
"table": "lab_results",
"schema": "clinical",
"auth_value": "eyJhbGciOi…"
}
}curl https://api.adaptivmapr.com/v1/gateway \
-H "Authorization: Bearer $MAPR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": { "url": "https://acme.example.com/exports/labs.xlsx" },
"destination": {
"connector_id": "con_e07b…",
"on_conflict": ["patient_id", "loinc_code", "taken_at"]
}
}'{
"schema_id": "lab_results_v1",
"source": "destination",
"row_count": 1840,
"errors": [
{ "row_index": 812, "field": "loinc_code", "code": "loinc_format", "message": "expected NNNNN-N" }
],
"destination": {
"connector_id": "con_e07b…",
"kind": "supabase",
"table": "lab_results",
"schema_source": "destination",
"preflight": "not_needed",
"protocol": "postgrest",
"written_rows": 1839,
"batches": 4,
"failed_batches": []
}
}Limits & failure modes
| Code | When | What to do |
|---|---|---|
400 config_invalid | The URL is a table endpoint rather than the project root. | A very common paste error. Drop the /rest/v1/<table> suffix and set config.table — the endpoint is built for you. |
400 table_required | No table on the connector and none in the request. | Set config.table or pass destination.table. |
502 destination_write_failed | Every batch was rejected. | Returned with PostgREST’s own message — an RLS violation and a missing column read very differently, and you need to know which. |
422 schema_destination_mismatch | A supplied schema names a column the table does not have. | Caught in pre-flight, BEFORE anything is written. |
400 ssrf_blocked | The configured host resolves to a private, link-local or loopback address. | Every outbound request is DNS-resolved and checked before it is made, on the scheduled path and the on-demand path alike. The reason is returned with the code. |
Not applicable: this is a write destination, not a source. What limits repetition here is the write mode — a plain insert by default, an upsert only when you name the conflict columns — so re-running a delivery does not quietly rewrite rows you did not intend to touch.
X-PHI and X-Region to phi-cloud so a regulated run lands on an in-region, BAA-eligible model, it costs +20% on the whole charge, and it is locked until the workspace accepts the BAA in Settings → Security & Data. An explicit PHI ask without an acceptance is 403 agreement_required, never a silent downgrade. A standard run keeps the workspace’s region pin — the region decides where compute may run, and the sandbox refuses a region-less run.What it costs
Moving bytes is not a line item. A sync that pulls a file and a destination write that lands the rows are both part of one map, and the map is what the wallet sees. There is no free tier and no subscription — top up from $10, a balance shared across the phi-cloud suite.
| Charge | Rate | Notes |
|---|---|---|
| Every map | $0.001 | A flat per-map fee — a few tokens — charged even when the run was fully deterministic or hit the layout cache and used no AI at all. |
| AI, only when it ran | at cost × 2 | Layer-5 cleanup, any-to-any convert and structural reshape bill the phi-cloud tokens actually consumed. Bring your own model key and it is × 0.5. |
| PHI / enterprise routing | +20% | Multiplies the whole charge, flat fee included — and only when the run genuinely got that routing. Locked until the workspace accepts the BAA in-app. |
Questions
Verified against lib/destinations.ts · lib/introspect.ts · app/api/v1/connectors/route.ts · app/api/v1/connectors/[id]/test/route.ts
Supabase is a trademark of Supabase, Inc.; PostgREST is an independent open-source project. Named here to describe interoperability only — no affiliation, endorsement or partnership is claimed.
Ready when you are
Start with a $10 prepaid wallet. Every map draws a few tokens; in schema-only mode only headers and a few sample rows ever leave you.