ADR 0007: Row-Level Tenancy With Environment-Prefixed Credentials
How partner data is isolated: a tenant on every record, sandbox tenants distinguishable by prefix, and per-tenant rate limiting and idempotency scoping.
Arc serves its own customers and partner fintechs from one deployable. Partner data must not leak across boundaries, and every partner needs a sandbox that is genuinely isolated from live.
Decision
A tenant on every record and every event. The event envelope carries tenantId; ledger accounts, journals, outbox rows and idempotency keys are all tenant-scoped.Sandbox tenants are prefixed (sbx_…) and detectable with isSandboxTenant, which is what gates sandbox-only behaviour such as magic-amount failure injection and POST /v1/sandbox/reset.Credentials are environment-prefixed: ak_test_ / sk_test_ versus ak_live_ / sk_live_.Rate limits and idempotency keys are scoped per tenant, so one partner cannot exhaust another’s budget or collide with another’s keys.
Consequences
Good. One deployable, one migration path, one set of code to reason about. A key pasted into the wrong environment is visibly wrong rather than quietly wrong. Sandbox behaviour is decided by tenant, not by a request flag, so the sandbox exercises the same code path production does.Costs. Isolation is enforced by discipline in queries rather than by physical separation — a missing tenant predicate is a leak, and nothing in the schema prevents one. Postgres row-level security would make it structural and is not implemented. Noisy-neighbour effects are bounded by rate limiting but not eliminated.Sandbox reset does not clear the ledger — it clears quotes and transfers only. That is a known gap, not a design position.
Alternatives
A database per tenant — the strongest isolation, and unworkable for a simulation and heavy for a small partner base. Worth revisiting at scale for large partners.A schema per tenant — migrations multiply by tenant count; the operational cost outweighs the isolation benefit at this size.A separate deployment for sandbox — doubles the infrastructure and, worse, allows sandbox and live to drift apart in behaviour, which defeats the purpose of a sandbox.
{"docsConfig":{"theme":"palm","$schema":"https://mintlify.com/docs.json","name":"Arc","description":"Arc is a simulation of cross-border stablecoin and fiat infrastructure for the EU↔Africa corridor — and a field guide to how stablecoin payments actually work.","colors":{"primary":"#14B8A6","light":"#2DD4BF","dark":"#0F766E"},"logo":{"light":"https://mintcdn.com/arc-doc/m0ZoL8ScOCykrqmD/logo/arc-light.svg?fit=max\u0026auto=format\u0026n=m0ZoL8ScOCykrqmD\u0026q=85\u0026s=90e2e1d537056757a14f4f611ff78d11","dark":"https://mintcdn.com/arc-doc/m0ZoL8ScOCykrqmD/logo/arc-dark.svg?fit=max\u0026auto=format\u0026n=m0ZoL8ScOCykrqmD\u0026q=85\u0026s=58a04971d1ea14b20bd347e971014244","href":"/"},"favicon":"https://mintcdn.com/arc-doc/m0ZoL8ScOCykrqmD/logo/mark.svg?fit=max\u0026auto=format\u0026n=m0ZoL8ScOCykrqmD\u0026q=85\u0026s=24cce491a91f6bde7dfc139f0b283c8e","appearance":{"default":"dark"},"navbar":{"links":[{"href":"/start/quickstart","label":"Run it locally"},{"href":"https://github.com/el-uno/fintech_arc/blob/main/docs/BUILD_PLAN.md","label":"Build plan"}],"primary":{"type":"github","href":"https://github.com/el-uno/fintech_arc"}},"footer":{"socials":{"github":"https://github.com/el-uno/fintech_arc"}},"search":{"prompt":"Search the corridor"},"fonts":{"heading":{"family":"Inter"},"body":{"family":"Inter"}},"styling":{"eyebrows":"breadcrumbs","codeblocks":"dark","latex":true},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"arc-doc","hasAudioNarration":false,"actualSubdomain":"arc-doc","customDomains":["arc.udokaam.dev"],"gitSource":{"type":"github","owner":"udoka-am","repo":"fintech_arc","deployBranch":"main","contentDirectory":"apps/docs-site","isPrivate":false,"mountPath":"","label":""},"entitlements":{},"buildId":"6aea22181e900d99c6189d28:success","clientVersion":"0.0.4128","searchFilterCounts":[{"language":"default","tag":"Architecture","count":23},{"language":"default","tag":"IRL Scenarios","count":16},{"language":"default","tag":"Guide","count":10},{"language":"default","tag":"Decisions","count":9},{"language":"default","tag":"Practice","count":7},{"language":"default","tag":"The corridor problem","count":6},{"language":"default","tag":"The core","count":5},{"language":"default","tag":"Foundations","count":5},{"language":"default","tag":"Work through it","count":5},{"language":"default","tag":"Start here","count":4}]},"assistantConfig":{"enableAskAiButton":false,"enableStarterQuestions":false,"enableContextualStarterQuestions":false,"enableAskAiSearchBar":true},"basePath":"","hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-rbac-roles","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"rbac-expansion","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"full-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-dual-write","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"approve-review-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-metadata","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"sso-setup-deadline-banner","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-serve","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"live-agents","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"googleFontsInlined":false},"customCss":[{"filePath":"style.css","content":"/* cyrillic-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');\n unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;\n}\n/* cyrillic */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');\n unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');\n unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');\n unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;\n}\n/* vietnamese */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');\n unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');\n unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');\n unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n/* cyrillic-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');\n unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;\n}\n/* cyrillic */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');\n unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');\n unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');\n unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;\n}\n/* vietnamese */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');\n unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');\n unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');\n unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n/* cyrillic-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');\n unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;\n}\n/* cyrillic */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');\n unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');\n unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');\n unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;\n}\n/* vietnamese */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');\n unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');\n unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');\n unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n/* cyrillic-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 700;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');\n unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;\n}\n/* cyrillic */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 700;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');\n unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 700;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');\n unicode-range: U+1F00-1FFF;\n}\n/* greek */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 700;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');\n unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;\n}\n/* vietnamese */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 700;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');\n unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 700;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');\n unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n font-family: 'Inter';\n font-style: normal;\n font-weight: 700;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');\n unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n/* cyrillic-ext */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx3cwhsk.woff2) format('woff2');\n unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;\n}\n/* cyrillic */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxTcwhsk.woff2) format('woff2');\n unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxPcwhsk.woff2) format('woff2');\n unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;\n}\n/* vietnamese */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx_cwhsk.woff2) format('woff2');\n unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx7cwhsk.woff2) format('woff2');\n unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 400;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxDcwg.woff2) format('woff2');\n unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n/* cyrillic-ext */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx3cwhsk.woff2) format('woff2');\n unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;\n}\n/* cyrillic */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxTcwhsk.woff2) format('woff2');\n unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxPcwhsk.woff2) format('woff2');\n unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;\n}\n/* vietnamese */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx_cwhsk.woff2) format('woff2');\n unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx7cwhsk.woff2) format('woff2');\n unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 500;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxDcwg.woff2) format('woff2');\n unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n/* cyrillic-ext */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx3cwhsk.woff2) format('woff2');\n unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;\n}\n/* cyrillic */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxTcwhsk.woff2) format('woff2');\n unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;\n}\n/* greek */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxPcwhsk.woff2) format('woff2');\n unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;\n}\n/* vietnamese */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx_cwhsk.woff2) format('woff2');\n unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;\n}\n/* latin-ext */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx7cwhsk.woff2) format('woff2');\n unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;\n}\n/* latin */\n@font-face {\n font-family: 'JetBrains Mono';\n font-style: normal;\n font-weight: 600;\n font-display: swap;\n src: url(/_mintlify/assets/fonts/google/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxDcwg.woff2) format('woff2');\n unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;\n}\n:root{--arc-teal: #2dd4bf;--arc-teal-deep: #14b8a6;--arc-amber: #f59e0b;--arc-rose: #fb7185;--arc-ground: #0d1117;--arc-raised: #151b23;--arc-sunken: #090d13;--arc-hairline: #232b36;--arc-ink: #e6edf3;--arc-ink-dim: #9aa7b4;--arc-ink-faint: #6b7785;--arc-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace}.light{--arc-ground: #ffffff;--arc-raised: #f7f9fa;--arc-sunken: #eef2f5;--arc-hairline: #dfe5ea;--arc-ink: #0d1117;--arc-ink-dim: #4a5561;--arc-ink-faint: #78838f}body{font-feature-settings:'cv05', 'ss01', 'tnum';letter-spacing:-0.006em}h1,h2,h3,h4{letter-spacing:-0.024em;font-weight:650}h1{letter-spacing:-0.032em}h2::before{content:'▚ ';color:var(--arc-teal);font-family:var(--arc-mono);font-weight:500;opacity:0.55}table{font-variant-numeric:tabular-nums}table td:not(:first-child),table th:not(:first-child){text-align:right;font-family:var(--arc-mono);font-size:0.86em}table td:first-child{font-family:var(--arc-mono);font-size:0.86em}code,pre,kbd{font-family:var(--arc-mono);font-feature-settings:'liga' 0}:not(pre)>code{border:1px solid var(--arc-hairline);border-radius:4px;background:var(--arc-sunken);color:var(--arc-teal);padding:0.1em 0.36em;font-size:0.86em;font-weight:500}pre{border:1px solid var(--arc-hairline);border-radius:6px;box-shadow:none}.arc-eyebrow{display:block;font-family:var(--arc-mono);font-size:0.7rem;font-weight:600;letter-spacing:0.14em;text-transform:uppercase;color:var(--arc-teal);margin-bottom:0.6rem}.arc-eyebrow--amber{color:var(--arc-amber)}.arc-eyebrow--dim{color:var(--arc-ink-faint)}.arc-stats{display:grid;grid-template-columns:repeat(auto-fit, minmax(150px, 1fr));gap:1px;background:var(--arc-hairline);border:1px solid var(--arc-hairline);border-radius:6px;overflow:hidden;margin:1.75rem 0}.arc-stat{background:var(--arc-raised);padding:1rem 1.1rem}.arc-stat__value{display:block;font-family:var(--arc-mono);font-size:1.45rem;font-weight:600;line-height:1.1;color:var(--arc-teal);font-variant-numeric:tabular-nums}.arc-stat__label{display:block;margin-top:0.4rem;font-size:0.78rem;line-height:1.4;color:var(--arc-ink-dim)}.arc-stat__source{display:block;margin-top:0.35rem;font-family:var(--arc-mono);font-size:0.66rem;letter-spacing:0.04em;color:var(--arc-ink-faint)}.arc-claim{border-left:2px solid var(--arc-teal);background:linear-gradient(90deg, rgba(45, 212, 191, 0.06), transparent 70%);padding:0.9rem 1.1rem;margin:1.6rem 0;border-radius:0 5px 5px 0}.arc-gap{border-left:2px solid var(--arc-amber);background:linear-gradient(90deg, rgba(245, 158, 11, 0.06), transparent 70%);padding:0.9rem 1.1rem;margin:1.6rem 0;border-radius:0 5px 5px 0}.arc-claim p:last-child,.arc-gap p:last-child{margin-bottom:0}.arc-symptom{font-size:1.08rem;line-height:1.6;color:var(--arc-ink);border:1px solid var(--arc-hairline);border-radius:6px;background:var(--arc-sunken);padding:1.15rem 1.3rem;margin:1.5rem 0 2rem}.arc-symptom::before{content:'SYMPTOM';display:block;font-family:var(--arc-mono);font-size:0.66rem;letter-spacing:0.16em;color:var(--arc-rose);margin-bottom:0.55rem}.arc-cite{font-size:0.82rem;color:var(--arc-ink-faint);font-family:var(--arc-mono)}.mermaid{background:var(--arc-sunken);border:1px solid var(--arc-hairline);border-radius:6px;padding:1rem}"}],"page":{"pagePath":"decisions/0007-multi-tenancy-model","slug":"/decisions/0007-multi-tenancy-model","pageMetadata":{"title":"ADR 0007: Row-Level Tenancy With Environment-Prefixed Credentials","sidebarTitle":"ADR 0007","description":"How partner data is isolated: a tenant on every record, sandbox tenants distinguishable by prefix, and per-tenant rate limiting and idempotency scoping.","icon":"users-between-lines","keywords":["multi-tenancy","tenant isolation","sandbox environment","API key prefixes","per-tenant rate limiting"],"href":"/decisions/0007-multi-tenancy-model"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"How partner data is isolated: a tenant on every record, sandbox tenants distinguishable by prefix, and per-tenant rate limiting and idempotency scoping.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={code:`code`,em:`em`,p:`p`,strong:`strong`,..._provideComponents(),...props.components},{Heading,Info}=_components;if(!Heading)_missingMdxReference(`Heading`,true);if(!Info)_missingMdxReference(`Info`,true);return _jsxs(_Fragment,{children:[_jsx(`span`,{className:`arc-eyebrow`,children:`ADR 0007 · Accepted · Phase 7`}),`\n`,_jsx(Info,{children:_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Status:`}),` Accepted · `,_jsx(_components.strong,{children:`Phase:`}),` 7 · `,_jsx(_components.strong,{children:`Supersedes:`}),` none · `,_jsx(_components.strong,{children:`Superseded by:`}),` none`]})}),`\n`,_jsx(Heading,{level:`2`,id:`context`,children:`Context`}),`\n`,_jsx(_components.p,{children:`Arc serves its own customers and partner fintechs from one deployable. Partner data must not leak across boundaries, and every partner needs a sandbox that is genuinely isolated from live.`}),`\n`,_jsx(Heading,{level:`2`,id:`decision`,children:`Decision`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`A tenant on every record and every event.`}),` The event envelope carries `,_jsx(_components.code,{children:`tenantId`}),`; ledger accounts, journals, outbox rows and idempotency keys are all tenant-scoped.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Sandbox tenants are prefixed`}),` (`,_jsx(_components.code,{children:`sbx_…`}),`) and detectable with `,_jsx(_components.code,{children:`isSandboxTenant`}),`, which is what gates sandbox-only behaviour such as magic-amount failure injection and `,_jsx(_components.code,{children:`POST /v1/sandbox/reset`}),`.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Credentials are environment-prefixed`}),`: `,_jsx(_components.code,{children:`ak_test_`}),` / `,_jsx(_components.code,{children:`sk_test_`}),` versus `,_jsx(_components.code,{children:`ak_live_`}),` / `,_jsx(_components.code,{children:`sk_live_`}),`.`]}),`\n`,_jsx(_components.p,{children:`Rate limits and idempotency keys are scoped per tenant, so one partner cannot exhaust another’s budget or collide with another’s keys.`}),`\n`,_jsx(Heading,{level:`2`,id:`consequences`,children:`Consequences`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Good.`}),` One deployable, one migration path, one set of code to reason about. A key pasted into the wrong environment is `,_jsx(_components.em,{children:`visibly`}),` wrong rather than quietly wrong. Sandbox behaviour is decided by tenant, not by a request flag, so the sandbox exercises the same code path production does.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Costs.`}),` Isolation is enforced by discipline in queries rather than by physical separation — a missing tenant predicate is a leak, and nothing in the schema prevents one. Postgres row-level security would make it structural and is not implemented. Noisy-neighbour effects are bounded by rate limiting but not eliminated.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Sandbox reset does not clear the ledger`}),` — it clears quotes and transfers only. That is a known gap, not a design position.`]}),`\n`,_jsx(Heading,{level:`2`,id:`alternatives`,children:`Alternatives`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.em,{children:`A database per tenant`}),` — the strongest isolation, and unworkable for a simulation and heavy for a small partner base. Worth revisiting at scale for large partners.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.em,{children:`A schema per tenant`}),` — migrations multiply by tenant count; the operational cost outweighs the isolation benefit at this size.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.em,{children:`A separate deployment for sandbox`}),` — doubles the infrastructure and, worse, allows sandbox and live to drift apart in behaviour, which defeats the purpose of a sandbox.`]})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"codeStyling":"dark","config":{},"pageMetadata":{"title":"ADR 0007: Row-Level Tenancy With Environment-Prefixed Credentials","sidebarTitle":"ADR 0007","description":"How partner data is isolated: a tenant on every record, sandbox tenants distinguishable by prefix, and per-tenant rate limiting and idempotency scoping.","icon":"users-between-lines","keywords":["multi-tenancy","tenant isolation","sandbox environment","API key prefixes","per-tenant rate limiting"],"href":"/decisions/0007-multi-tenancy-model"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"Context","slug":"context","depth":2,"children":[]},{"title":"Decision","slug":"decision","depth":2,"children":[]},{"title":"Consequences","slug":"consequences","depth":2,"children":[]},{"title":"Alternatives","slug":"alternatives","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"subdomain":"arc-doc","siteName":"Arc","title":"ADR 0007: Row-Level Tenancy With Environment-Prefixed Credentials - Arc","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-08-14T01:20:45.553Z","originalFileLocation":"decisions/0007-multi-tenancy-model.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"dark\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"How partner data is isolated: a tenant on every record, sandbox tenants distinguishable by prefix, and per-tenant rate limiting and idempotency scoping.\">\u003cmeta data-mint-head name=\"application-name\" content=\"Arc\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/arc-doc/8qf7D6aInLMuoIah/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/arc-doc/8qf7D6aInLMuoIah/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/arc-doc/8qf7D6aInLMuoIah/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/arc-doc/8qf7D6aInLMuoIah/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/arc-doc/8qf7D6aInLMuoIah/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/arc-doc/8qf7D6aInLMuoIah/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/arc-doc/8qf7D6aInLMuoIah/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/arc-doc/8qf7D6aInLMuoIah/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/arc-doc/8qf7D6aInLMuoIah/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"ADR 0007: Row-Level Tenancy With Environment-Prefixed Credentials - Arc\">\u003cmeta data-mint-head property=\"og:description\" content=\"How partner data is isolated: a tenant on every record, sandbox tenants distinguishable by prefix, and per-tenant rate limiting and idempotency scoping.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"Arc\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://arc.udokaam.dev/decisions/0007-multi-tenancy-model\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://arc-doc.mintlify.site/_mintlify/api/og/v2?division=Decisions\u0026amp;title=ADR+0007%3A+Row-Level+Tenancy+With+Environment-Prefixed+Credentials\u0026amp;description=How+partner+data+is+isolated%3A+a+tenant+on+every+record%2C+sandbox+tenants+distinguishable+by+prefix%2C+and+per-tenant+rate+limiting+and+idempotency+scoping.\u0026amp;theme=a62ca8cb35008bd1421320f2\u0026amp;signature=e9af8041ab0531283957e8603fecee55bf20b91c7062f9e507b987482356d116\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"ADR 0007: Row-Level Tenancy With Environment-Prefixed Credentials - Arc\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"How partner data is isolated: a tenant on every record, sandbox tenants distinguishable by prefix, and per-tenant rate limiting and idempotency scoping.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://arc-doc.mintlify.site/_mintlify/api/og/v2?division=Decisions\u0026amp;title=ADR+0007%3A+Row-Level+Tenancy+With+Environment-Prefixed+Credentials\u0026amp;description=How+partner+data+is+isolated%3A+a+tenant+on+every+record%2C+sandbox+tenants+distinguishable+by+prefix%2C+and+per-tenant+rate+limiting+and+idempotency+scoping.\u0026amp;theme=a62ca8cb35008bd1421320f2\u0026amp;signature=e9af8041ab0531283957e8603fecee55bf20b91c7062f9e507b987482356d116\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/arc-doc/8qf7D6aInLMuoIah/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"Arc\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#14B8A6\">\u003clink data-mint-head rel=\"canonical\" href=\"https://arc.udokaam.dev/decisions/0007-multi-tenancy-model\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/decisions/0007-multi-tenancy-model.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://arc.udokaam.dev/#organization\",\"name\":\"Arc\",\"url\":\"https://arc.udokaam.dev\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/arc-doc/m0ZoL8ScOCykrqmD/logo/arc-light.svg?fit=max\u0026auto=format\u0026n=m0ZoL8ScOCykrqmD\u0026q=85\u0026s=90e2e1d537056757a14f4f611ff78d11\"}},{\"@type\":\"WebSite\",\"@id\":\"https://arc.udokaam.dev#website\",\"name\":\"Arc\",\"url\":\"https://arc.udokaam.dev\",\"publisher\":{\"@id\":\"https://arc.udokaam.dev/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://arc.udokaam.dev/decisions/0007-multi-tenancy-model#webpage\",\"url\":\"https://arc.udokaam.dev/decisions/0007-multi-tenancy-model\",\"name\":\"ADR 0007: Row-Level Tenancy With Environment-Prefixed Credentials\",\"description\":\"How partner data is isolated: a tenant on every record, sandbox tenants distinguishable by prefix, and per-tenant rate limiting and idempotency scoping.\",\"dateModified\":\"2026-08-14T01:20:45.553Z\",\"isPartOf\":{\"@id\":\"https://arc.udokaam.dev#website\"},\"breadcrumb\":{\"@id\":\"https://arc.udokaam.dev/decisions/0007-multi-tenancy-model#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://arc.udokaam.dev/decisions/0007-multi-tenancy-model#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Decisions\",\"item\":\"https://arc.udokaam.dev/decisions\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"ADR 0007: Row-Level Tenancy With Environment-Prefixed Credentials\",\"item\":\"https://arc.udokaam.dev/decisions/0007-multi-tenancy-model\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://arc.udokaam.dev/decisions/0007-multi-tenancy-model#article\",\"headline\":\"ADR 0007: Row-Level Tenancy With Environment-Prefixed Credentials\",\"name\":\"ADR 0007: Row-Level Tenancy With Environment-Prefixed Credentials\",\"description\":\"How partner data is isolated: a tenant on every record, sandbox tenants distinguishable by prefix, and per-tenant rate limiting and idempotency scoping.\",\"url\":\"https://arc.udokaam.dev/decisions/0007-multi-tenancy-model\",\"mainEntityOfPage\":{\"@id\":\"https://arc.udokaam.dev/decisions/0007-multi-tenancy-model#webpage\"},\"dateModified\":\"2026-08-14T01:20:45.553Z\",\"keywords\":\"multi-tenancy, tenant isolation, sandbox environment, API key prefixes, per-tenant rate limiting\",\"publisher\":{\"@id\":\"https://arc.udokaam.dev/#organization\"},\"isPartOf\":{\"@id\":\"https://arc.udokaam.dev#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"Arc\",\"publisher\":{\"@type\":\"Organization\",\"name\":\"Arc\"},\"provider\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","agentPointerHref":"/decisions/0007-multi-tenancy-model.md","siteName":"Arc","nav":{"global":null,"tabs":[{"tab":"Guide","pages":[{"title":"Guide","href":""}]},{"tab":"Architecture","groups":[{"group":"Foundations","icon":"layer-group","pages":[{"title":"Arc Architecture Overview: Six Contexts, One Deployable","sidebarTitle":"Overview","icon":"layer-group","href":"/architecture/overview"},{"title":"Why Every Monetary Value in Arc Is a Bigint Amount","sidebarTitle":"Money","icon":"calculator","href":"/architecture/money"},{"title":"Contexts, Events, and the Boundary That Fails Builds","sidebarTitle":"Events","icon":"inbox","href":"/architecture/contexts-and-events"},{"title":"Platform and Security: Signing, Idempotency, Secrets","sidebarTitle":"Platform","icon":"shield-halved","href":"/architecture/security"},{"title":"The Last Mile API: Partner Onboarding and Sandbox","sidebarTitle":"Partner platform","icon":"plug","href":"/architecture/partner-platform"}]},{"group":"The core","icon":"book-open","pages":[{"title":"The Arc Ledger: Double-Entry Accounting With No Tolerance","sidebarTitle":"Ledger","icon":"scale-balanced","href":"/architecture/ledger"},{"title":"The Chain Layer: One Interface, Five Different Physics","sidebarTitle":"Chain Layer","icon":"link","href":"/architecture/chain-layer"},{"title":"The Settlement Saga: Five Steps, Five Compensations","sidebarTitle":"Settlement Saga","icon":"rotate-left","href":"/architecture/settlement-saga"},{"title":"Risk and Compliance: A Blocking Gate on Money Movement","sidebarTitle":"Compliance","icon":"shield","href":"/architecture/compliance"},{"title":"Reconciliation: Balanced Books Are Not Correct Books","sidebarTitle":"Reconciliation","icon":"scale-unbalanced","href":"/architecture/reconciliation"}]},{"group":"Proof","icon":"flask","pages":[{"title":"What the Arc Test Suite Proves and How It Was Verified","sidebarTitle":"Testing","icon":"flask","href":"/architecture/testing"}]},{"group":"Flows","icon":"route","pages":[{"title":"Consumer Remittance: €1,000 From Munich to M-Pesa Kenya","sidebarTitle":"Consumer Flow","icon":"user","href":"/flows/consumer-remittance"},{"title":"Enterprise Payout: NGN Virtual Account to SEPA Instant","sidebarTitle":"Enterprise Flow","icon":"building","href":"/flows/enterprise-payout"},{"title":"Reversal: Four Compensating Journals, in Reverse Order","sidebarTitle":"Reversal","icon":"rotate-left","href":"/flows/reversal"}]},{"group":"Decisions","icon":"gavel","pages":[{"title":"Architecture Decision Records: How Arc Was Designed","sidebarTitle":"Overview","icon":"gavel","href":"/decisions/index"},{"title":"ADR 0001: Money as Integer Minor Units, Enforced by Lint","sidebarTitle":"ADR 0001","icon":"calculator","href":"/decisions/0001-money-as-integer-minor-units"},{"title":"ADR 0002: Modular Monolith Over Microservices, Enforced","sidebarTitle":"ADR 0002","icon":"cubes","href":"/decisions/0002-modular-monolith"},{"title":"ADR 0003: A Chain-Agnostic Driver With a Deterministic Simulator","sidebarTitle":"ADR 0003","icon":"link","href":"/decisions/0003-chain-abstraction"},{"title":"ADR 0004: A Compensating Saga Rather Than Two-Phase Commit","sidebarTitle":"ADR 0004","icon":"diagram-project","href":"/decisions/0004-saga-over-two-phase-commit"},{"title":"ADR 0005: Idempotency Keys, and Why a Reused Key Is a Conflict","sidebarTitle":"ADR 0005","icon":"rotate-right","href":"/decisions/0005-idempotency-strategy"},{"title":"ADR 0006: A Transactional Outbox for At-Least-Once Delivery","sidebarTitle":"ADR 0006","description":"Why events are staged in the same transaction as the state change they describe, and why every handler must therefore be idempotent.","icon":"inbox","href":"/decisions/0006-transactional-outbox"},{"title":"ADR 0007: Row-Level Tenancy With Environment-Prefixed Credentials","sidebarTitle":"ADR 0007","icon":"users-between-lines","href":"/decisions/0007-multi-tenancy-model"},{"title":"ADR 0008: Double-Entry Accounting, Enforced by the Database","sidebarTitle":"ADR 0008","description":"Why Arc uses classical double-entry with balances derived from entries, and why the balance rule is enforced in Postgres as well as in application code.","icon":"scale-balanced","href":"/decisions/0008-double-entry-ledger"}]}]},{"tab":"IRL Scenarios","pages":[{"title":"IRL Scenarios","href":"/stories"}]},{"tab":"Practice","pages":[{"title":"Practice","href":"/practice"}]}]},"basePath":"","title":"ADR 0007: Row-Level Tenancy With Environment-Prefixed Credentials - Arc","buildId":"dpl_5tKr6cUUeuVJdEZ9JAEY3h4soMJ8","deploymentId":"dpl_5tKr6cUUeuVJdEZ9JAEY3h4soMJ8","dynamics":[],"chunks":[]}