SharedKey, encrypted at rest
How · signAzureRequest() builds the canonicalized string per call. Rotation happens in place via /rotate-secret, which overwrites the old key with no recovery window by design.
Integration · Object storage
SharedKey auth against a storage account and container. Read the newest blob since the last run and map it; or write the mapped result back as one timestamped blob.
How it works
Azure signs differently from the other two — SharedKey over a canonicalized request rather than SigV4 or an OAuth token — and answers its listing in its own XML dialect. Everything above that is identical: list the prefix, take the newest blob modified since the last successful run, parse it by its own name, map it. The account key lives in the connector record, encrypted, and is never accepted in a request body.
What you get
How · signAzureRequest() builds the canonicalized string per call. Rotation happens in place via /rotate-secret, which overwrites the old key with no recovery window by design.
How · The watermark is last_synced_at, overridable with ?since=. It advances only when the run succeeded.
How · config.endpoint_suffix changes the host the signer targets; the SSRF guard still resolves and checks it like any other outbound request.
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
| Key | Required | What it is |
|---|---|---|
| container | Required | Container name. bucket is accepted as an alias. |
| account | Required | Storage account name. Required, and checked at save time. |
| auth_valuesecret | Required | The account key. account_key is accepted as an alias and folded into this field before storage. |
| endpoint_suffix | Optional | Non-public cloud suffix, for a sovereign or government Azure environment. |
| prefix | Optional | Blob prefix scoping the read and the write. |
| format | Optional | Override extension sniffing. |
| url | Optional | Legacy presigned/SAS URL form. Read-only. |
In code
Save the account and container once; pull it on demand with a bearer key, or let the scheduled cadence on the record do it.
/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}/syncPull now. Accepts ?since=<iso> to override the watermark.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.xlsx export needs no configuration at all.AuthenticationFailed is the actionable part.{
"kind": "azure_blob",
"name": "Payroll drop",
"template_id": "payroll",
"config": {
"account": "acmepayroll",
"container": "exports",
"prefix": "monthly/",
"account_key": "…"
}
}curl -X POST https://api.adaptivmapr.com/v1/connectors/con_ab77…/sync \
-H "Authorization: Bearer $MAPR_API_KEY"{
"ok": true,
"status": "parsed",
"upload_id": "upl_5c02…",
"row_count": 412,
"accepted": 412,
"format": "xlsx",
"incremental": "incremental"
}Limits & failure modes
| Code | When | What to do |
|---|---|---|
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. |
400 config_invalid | A credential or identifier the provider needs is missing or malformed. | Refused at save time rather than at 3am during a scheduled run. The message names the exact key. |
400 config_invalid | No account, or no account key. | Both are checked before the record is created; a container with no way to sign for it is never saved. |
413 fetch_too_large | The chosen blob exceeds 25 MiB. | Split it upstream. The ceiling is the parser’s memory envelope, not an Azure limit. |
502 fetch_failed | Azure refused the list or the get. | Its own message is passed through and the watermark is left where it was. |
Real, by blob modified time — the listing is filtered against the watermark and only the newest survivor is read. An empty result is status: "not_modified", a success.
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/objectStore.ts · lib/cloudAuth.ts · lib/connectorSyncRunner.ts · app/api/v1/connectors/route.ts
Azure and Azure Blob Storage are trademarks of Microsoft Corporation. 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.