A service account, scoped to one bucket
How · gcpAccessToken() signs a JWT with the key and exchanges it for a token scoped to storage. The key itself is KEK-encrypted at rest and masked on every read.
Integration · Object storage
A service-account key, a bucket and a prefix. AdaptivMapr lists, takes the newest object since the last run, parses it by extension and maps it — and writes the mapped result back as one timestamped file.
How it works
The GCS connector is the same shape as the S3 one — list a prefix, take the newest object modified since the last successful run, parse it by its own key, map it — with a different signature at the bottom. Auth is a service-account key file exchanged for a scoped access token; the JSON API answers the listing, and the response is normalized into the same object shape every provider returns, so nothing downstream branches on which cloud it came from.
client_email and private_key and exchanged for a scoped access token — refused at save time if either is missing.config.prefix; objects modified after the watermark survive and the newest is taken.What you get
How · gcpAccessToken() signs a JWT with the key and exchanges it for a token scoped to storage. The key itself is KEK-encrypted at rest and masked on every read.
How · Overridable per call with ?since=<iso>. Nothing new comes back as status: "not_modified" — success with zero rows.
How · The key template is export-{date}-{time}.{ext}; pass destination.key to name it yourself. Both tokens are substituted at write time.
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
The key file is validated by parseGcpCredentials() BEFORE the record is saved, so a paste that lost its newlines fails now rather than on the first scheduled run.
| Key | Required | What it is |
|---|---|---|
| bucket | Required | Bucket name. |
| service_account_jsonsecret | Required | The whole service-account key file. Must parse and carry client_email and private_key or the save is refused. Stored as auth_value. |
| prefix | Optional | Key prefix scoping the read and the write. |
| format | Optional | Override extension sniffing. |
| url | Optional | Legacy presigned-URL form. Read-only. |
In code
The connector holds the service-account key. The call that uses it carries a bearer token and an id — nothing else.
/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.sessionnot_modified answer is deliberately a 200. A scheduler that treats “no new file” as an error will page someone every quiet night.400 config_invalid immediately rather than a 3am failure.{
"kind": "gcs",
"name": "Warehouse drop",
"template_id": "orders",
"config": {
"bucket": "acme-analytics-drop",
"prefix": "orders/",
"service_account_json": "{\"client_email\":\"mapr@acme.iam.gserviceaccount.com\",\"private_key\":\"-----BEGIN PRIVATE KEY-----\\n…\"}"
}
}curl -X POST "https://api.adaptivmapr.com/v1/connectors/con_31ab…/sync?since=2026-09-01T00:00:00Z" \
-H "Authorization: Bearer $MAPR_API_KEY"{
"ok": true,
"status": "not_modified",
"upload_id": null,
"row_count": 0,
"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 | The service-account JSON does not parse, or lacks client_email / private_key. | Checked at save time by parseGcpCredentials(), so an unusable connector is never created. |
413 fetch_too_large | The chosen object exceeds 25 MiB. | Split the export upstream; the ceiling matches the parser’s own memory envelope. |
401 auth_failed | The token exchange is refused. | Usually a revoked key or a missing role on the bucket. The provider message is passed through. |
Real, by object modified time. The listing is filtered against the watermark and only the newest survivor is read. A failed run leaves the watermark where it was, so the next tick covers the same window again rather than skipping the file that failed.
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
Google Cloud Storage is a trademark of Google LLC. 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.