Test SafarAPI with realistic data, no production effects
The sandbox is a fully production‑like environment running on the same infrastructure as production. Isolation is enforced at the database level: sandbox data lives in a dedicated PostgreSQL schema (safariat_sandbox), never mixed with production rows. sk_test_* keys route to the sandbox schema; sk_live_* keys route to production. The routing is a hard SQL boundary, not an application filter.
Self‑service signup at console.safarapi.com/signup. Magic link in your inbox within 30 seconds. Sandbox key visible once on the activation page — copy it then.
The sandbox mirrors the production catalogue: every adventure, agency, rate pack, and category visible to your sk_live_* key has a clone in the sandbox schema. The clone refreshes every Sunday at 03:00 UTC, so a new prod adventure shows up in your sandbox within the week.
Resource
Source
Notes
Adventures
Full production catalogue
Slugs suffixed -sandbox (cosmetic — helps you spot sandbox URLs in logs)
Rate packs
Full clone
Same prices and schedules as prod
Agencies
Full clone
Real agency names — your bank can show realistic partner data to QA
Categories
Full clone
desert, mountain, beach, culture, urban, etc.
The Sunday refresh never touches your sandbox bookings, quotes, or idempotency keys. Your integration history stays intact across weeks.
The sandbox simulates real payment behaviours based on the traveler email you pass on POST /bookings:
Email
Behaviour
test.success@bank.example
Booking confirmed, voucher generated, email sent to sandbox sink
test.cancellation@bank.example
Booking confirmed, but cancellation API behaves with tier 1 refund
test.insufficient_credit@bank.example
POST /bookings returns 403 partner.credit.limit.exceeded
test.amount_mismatch@bank.example
POST /bookings returns 422 payment.amount.mismatch
test.duplicate_ref@bank.example
Second POST /bookings with same partner_reference returns 409
Any other email behaves like test.success@bank.example. Real email addresses are accepted but no email is ever sent in sandbox — all messages route to sandbox-sink@safariat.ma.
The console exposes a “Reset sandbox data” button under Settings. Clicking it erases all bookings, quotes, and idempotency keys created with your test key in the last 30 days. The cloned catalogue (adventures, rate packs, agencies) remains untouched.
Sandbox isolation is enforced at the PostgreSQL level, not at the application level:
Dedicated schema — every sandbox table lives in safariat_sandbox.*, structurally identical to but separate from safariat.* (production).
Per‑request routing — each authenticated request opens its database connection with SET search_path TO <schema>, public. A sk_test_* key forces search_path = safariat_sandbox, public; a sk_live_* key forces safariat, public. The mapping is set by an authentication filter that runs before any business logic touches the database.
Connection hygiene — every connection returning to the pool has its search_path reset to the production default, eliminating cross‑request leak risk. Validated by an automated test that performs 100 sandbox checkouts followed by a live checkout against a real PostgreSQL instance.
Audit trail — the partner_api_audit_log table records every request with its environment tag (LIVE or TEST), preserved indefinitely.
This satisfies the “where is my test data stored?” question for Bank Al‑Maghrib externalisation reviews. See security/overview for the broader compliance posture.
A sandbox key is active for 30 days of inactivity. If you don’t make any request for 30 days, the key is auto‑disabled. Re‑activate it with a single click from the console.There’s no calendar expiration — as long as you keep using it, your sandbox key works indefinitely.
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"SafarAPI","description":"Developer documentation for the SafarAPI partner channel — quote, book and settle Moroccan adventures from your app.","colors":{"primary":"#f2460d","light":"#fb923c","dark":"#c2410c"},"logo":{"light":"https://mintcdn.com/safariat/uq0i7jK0LmN_yZ-e/logo/safarapi-light.svg?fit=max\u0026auto=format\u0026n=uq0i7jK0LmN_yZ-e\u0026q=85\u0026s=6e54d448368f1737309eebb08e5e571c","dark":"https://mintcdn.com/safariat/uq0i7jK0LmN_yZ-e/logo/safarapi-dark.svg?fit=max\u0026auto=format\u0026n=uq0i7jK0LmN_yZ-e\u0026q=85\u0026s=007f52ec9befa6071011db06b432b61d"},"favicon":"https://mintcdn.com/safariat/uq0i7jK0LmN_yZ-e/favicon.svg?fit=max\u0026auto=format\u0026n=uq0i7jK0LmN_yZ-e\u0026q=85\u0026s=a5d5d7ecbe37e9205bda58ca868f2853","api":{"playground":{"display":"interactive","proxy":true}},"navbar":{"links":[{"href":"/status","label":"Status"},{"href":"https://console.safarapi.com","label":"Console"}],"primary":{"type":"button","label":"Get sandbox access","href":"https://console.safarapi.com/signup"}},"footer":{"socials":{"website":"https://safarapi.com","linkedin":"https://www.linkedin.com/company/safarapi"}},"search":{"prompt":"Search the SafarAPI docs — quotes, bookings, webhooks, settlements"},"seo":{"metatags":{"og:title":"SafarAPI — Morocco travel API for banks \u0026 fintechs","og:description":"Quote, book and settle curated Moroccan travel from your banking app. Sandbox in minutes, production in 7 days.","og:type":"website"},"indexing":"navigable"},"fonts":{"heading":{"family":"Space Grotesk"},"body":{"family":"Inter"}},"styling":{"eyebrows":"section","codeblocks":{"theme":"github-dark-dimmed"},"latex":false},"errors":{"404":{"redirect":false,"title":"Page not found","description":"This page does not exist. Start from the [welcome guide](/introduction/welcome), the [quickstart](/introduction/quickstart), or the [API reference](/api-reference/openapi)."}},"contextual":{"options":["copy","view","chatgpt","claude"],"display":"header"},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"safariat","hasAudioNarration":false,"actualSubdomain":"safariat","customDomains":["developers.safarapi.com"],"gitSource":{"type":"github","owner":"adnanahouzi","repo":"maroc-voyages-r-v-s","deployBranch":"main","contentDirectory":"docs-portal","isPrivate":true,"mountPath":"","label":""},"chroma":{"liveDeploymentHistoryId":"6a9c766b0d95020accd6ba30","collectionId":"ffbff38d-bd54-4930-96e6-24689d9a11de","schemaVersion":"v3","latestBuiltDeploymentHistoryId":"6a9c766b0d95020accd6ba30"},"entitlements":{"AI_CHAT":{"status":"DISABLED"}},"buildId":"6a9c766b0d95020accd6ba30","clientVersion":"0.0.4076","searchFilterCounts":[{"language":"default","tag":"API Reference","count":67},{"language":"default","tag":"Endpoints","count":58},{"language":"default","tag":"Guides","count":22},{"language":"default","tag":"API concepts","count":10},{"language":"default","tag":"Catalog","count":8},{"language":"default","tag":"Webhooks","count":7},{"language":"default","tag":"Bookings","count":6},{"language":"default","tag":"Dashboard","count":5},{"language":"default","tag":"Settlements","count":5},{"language":"default","tag":"Introduction","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":"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":"llms-txt-v2","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":"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":"comment-mode","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"googleFontsInlined":true},"customCss":[{"filePath":"style.css","content":":root{--safarapi-ink: #1a1a1a;--safarapi-rule: #ececec;--safarapi-accent: #f2460d}.dark{--safarapi-ink: #ededed;--safarapi-rule: #2a2a2a;--safarapi-accent: #fb923c}body{-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}main p,main li{line-height:1.68}main h2{margin-top:2.5rem;letter-spacing:-0.01em}main h3{margin-top:1.75rem}main a:not([class]){color:var(--safarapi-accent);text-decoration:none;border-bottom:1px solid transparent;transition:border-color 120ms ease}main a:not([class]):hover{border-bottom-color:currentColor}pre,code{border:1px solid var(--safarapi-rule);border-radius:6px}pre{box-shadow:none}table{border-collapse:collapse}table th,table td{border-color:var(--safarapi-rule)}a:focus-visible,button:focus-visible{outline:2px solid var(--safarapi-accent);outline-offset:2px}"}],"page":{"pagePath":"introduction/sandbox","slug":"/introduction/sandbox","pageMetadata":{"title":"Sandbox","description":"Test SafarAPI with realistic data, no production effects","href":"/introduction/sandbox"},"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: \"Test SafarAPI with realistic data, no production effects\"\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={a:`a`,code:`code`,li:`li`,p:`p`,strong:`strong`,tbody:`tbody`,td:`td`,th:`th`,thead:`thead`,tr:`tr`,ul:`ul`,..._provideComponents(),...props.components},{Heading,Table,Tip}=_components;if(!Heading)_missingMdxReference(`Heading`,true);if(!Table)_missingMdxReference(`Table`,true);if(!Tip)_missingMdxReference(`Tip`,true);return _jsxs(_Fragment,{children:[_jsxs(_components.p,{children:[`The sandbox is a fully production‑like environment running on the `,_jsx(_components.strong,{children:`same infrastructure`}),` as production. Isolation is enforced at the database level: sandbox data lives in a `,_jsx(_components.strong,{children:`dedicated PostgreSQL schema`}),` (`,_jsx(_components.code,{children:`safariat_sandbox`}),`), never mixed with production rows. `,_jsx(_components.code,{children:`sk_test_*`}),` keys route to the sandbox schema; `,_jsx(_components.code,{children:`sk_live_*`}),` keys route to production. The routing is a hard SQL boundary, not an application filter.`]}),`\n`,_jsx(Heading,{level:`2`,id:`how-to-get-a-sandbox-key`,children:`How to get a sandbox key`}),`\n`,_jsxs(_components.p,{children:[`Self‑service signup at `,_jsx(_components.a,{href:`https://console.safarapi.com/signup`,children:`console.safarapi.com/signup`}),`. Magic link in your inbox within 30 seconds. Sandbox key visible once on the activation page — copy it then.`]}),`\n`,_jsx(Heading,{level:`2`,id:`what’s-in-the-sandbox`,children:`What’s in the sandbox`}),`\n`,_jsxs(_components.p,{children:[`The sandbox mirrors the `,_jsx(_components.strong,{children:`production catalogue`}),`: every adventure, agency, rate pack, and category visible to your `,_jsx(_components.code,{children:`sk_live_*`}),` key has a clone in the sandbox schema. The clone refreshes every Sunday at 03:00 UTC, so a new prod adventure shows up in your sandbox within the week.`]}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Resource`}),_jsx(_components.th,{children:`Source`}),_jsx(_components.th,{children:`Notes`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Adventures`}),_jsx(_components.td,{children:`Full production catalogue`}),_jsxs(_components.td,{children:[`Slugs suffixed `,_jsx(_components.code,{children:`-sandbox`}),` (cosmetic — helps you spot sandbox URLs in logs)`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Rate packs`}),_jsx(_components.td,{children:`Full clone`}),_jsx(_components.td,{children:`Same prices and schedules as prod`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Agencies`}),_jsx(_components.td,{children:`Full clone`}),_jsx(_components.td,{children:`Real agency names — your bank can show realistic partner data to QA`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Categories`}),_jsx(_components.td,{children:`Full clone`}),_jsx(_components.td,{children:`desert, mountain, beach, culture, urban, etc.`})]})]})]}),`\n`,_jsx(Tip,{children:_jsxs(_components.p,{children:[`The Sunday refresh `,_jsx(_components.strong,{children:`never touches your sandbox bookings, quotes, or idempotency keys`}),`. Your integration history stays intact across weeks.`]})}),`\n`,_jsx(Heading,{level:`2`,id:`pre-defined-test-travellers`,children:`Pre-defined test travellers`}),`\n`,_jsxs(_components.p,{children:[`The sandbox simulates real payment behaviours based on the `,_jsx(_components.strong,{children:`traveler email`}),` you pass on `,_jsx(_components.code,{children:`POST /bookings`}),`:`]}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Email`}),_jsx(_components.th,{children:`Behaviour`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`test.success@bank.example`})}),_jsx(_components.td,{children:`Booking confirmed, voucher generated, email sent to sandbox sink`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`test.cancellation@bank.example`})}),_jsx(_components.td,{children:`Booking confirmed, but cancellation API behaves with tier 1 refund`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`test.insufficient_credit@bank.example`})}),_jsxs(_components.td,{children:[_jsx(_components.code,{children:`POST /bookings`}),` returns `,_jsx(_components.code,{children:`403 partner.credit.limit.exceeded`})]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`test.amount_mismatch@bank.example`})}),_jsxs(_components.td,{children:[_jsx(_components.code,{children:`POST /bookings`}),` returns `,_jsx(_components.code,{children:`422 payment.amount.mismatch`})]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.code,{children:`test.duplicate_ref@bank.example`})}),_jsxs(_components.td,{children:[`Second `,_jsx(_components.code,{children:`POST /bookings`}),` with same `,_jsx(_components.code,{children:`partner_reference`}),` returns `,_jsx(_components.code,{children:`409`})]})]})]})]}),`\n`,_jsx(Tip,{children:_jsxs(_components.p,{children:[`Any other email behaves like `,_jsx(_components.code,{children:`test.success@bank.example`}),`. Real email addresses are accepted but `,_jsx(_components.strong,{children:`no email is ever sent`}),` in sandbox — all messages route to `,_jsx(_components.code,{children:`sandbox-sink@safariat.ma`}),`.`]})}),`\n`,_jsx(Heading,{level:`2`,id:`rate-limits-in-sandbox`,children:`Rate limits in sandbox`}),`\n`,_jsxs(_components.p,{children:[`100 requests per minute per key. Sufficient for integration testing; if you need more for load testing, `,_jsx(_components.a,{href:`mailto:hello@safarapi.com`,children:`contact us`}),`.`]}),`\n`,_jsx(Heading,{level:`2`,id:`resetting-your-sandbox`,children:`Resetting your sandbox`}),`\n`,_jsxs(_components.p,{children:[`The console exposes a `,_jsx(_components.strong,{children:`“Reset sandbox data”`}),` button under Settings. Clicking it erases all bookings, quotes, and idempotency keys created with your test key in the last 30 days. The cloned catalogue (adventures, rate packs, agencies) remains untouched.`]}),`\n`,_jsx(Heading,{level:`2`,id:`what’s-different-from-production`,children:`What’s different from production`}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Aspect`}),_jsx(_components.th,{children:`Sandbox`}),_jsx(_components.th,{children:`Production`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Settlement invoices`}),_jsx(_components.td,{children:`Not generated`}),_jsx(_components.td,{children:`Generated monthly`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Email to traveller`}),_jsxs(_components.td,{children:[`Sink (`,_jsx(_components.a,{href:`mailto:sandbox-sink@safariat.ma`,children:`sandbox-sink@safariat.ma`}),`)`]}),_jsx(_components.td,{children:`Real recipient`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Webhook payload`}),_jsx(_components.td,{children:_jsx(_components.code,{children:`event.test = true`})}),_jsx(_components.td,{children:_jsx(_components.code,{children:`event.test = false`})})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Rate limit`}),_jsx(_components.td,{children:`100 req/min`}),_jsx(_components.td,{children:`600 req/min (negotiable)`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Credit limit`}),_jsx(_components.td,{children:`50 000 MAD default`}),_jsx(_components.td,{children:`Per‑contract value`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Pricing rules`}),_jsx(_components.td,{children:`Zero margin (passthrough)`}),_jsx(_components.td,{children:`Your negotiated rules`})]})]})]}),`\n`,_jsx(Heading,{level:`2`,id:`isolation-guarantees-for-your-compliance-team`,children:`Isolation guarantees (for your compliance team)`}),`\n`,_jsxs(_components.p,{children:[`Sandbox isolation is enforced `,_jsx(_components.strong,{children:`at the PostgreSQL level`}),`, not at the application level:`]}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Dedicated schema`}),` — every sandbox table lives in `,_jsx(_components.code,{children:`safariat_sandbox.*`}),`, structurally identical to but separate from `,_jsx(_components.code,{children:`safariat.*`}),` (production).`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Per‑request routing`}),` — each authenticated request opens its database connection with `,_jsx(_components.code,{children:`SET search_path TO \u003cschema>, public`}),`. A `,_jsx(_components.code,{children:`sk_test_*`}),` key forces `,_jsx(_components.code,{children:`search_path = safariat_sandbox, public`}),`; a `,_jsx(_components.code,{children:`sk_live_*`}),` key forces `,_jsx(_components.code,{children:`safariat, public`}),`. The mapping is set by an authentication filter that runs before any business logic touches the database.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Connection hygiene`}),` — every connection returning to the pool has its `,_jsx(_components.code,{children:`search_path`}),` reset to the production default, eliminating cross‑request leak risk. Validated by an automated test that performs 100 sandbox checkouts followed by a live checkout against a real PostgreSQL instance.`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.strong,{children:`Audit trail`}),` — the `,_jsx(_components.code,{children:`partner_api_audit_log`}),` table records every request with its `,_jsx(_components.code,{children:`environment`}),` tag (`,_jsx(_components.code,{children:`LIVE`}),` or `,_jsx(_components.code,{children:`TEST`}),`), preserved indefinitely.`]}),`\n`]}),`\n`,_jsxs(_components.p,{children:[`This satisfies the “where is my test data stored?” question for Bank Al‑Maghrib externalisation reviews. See `,_jsx(_components.a,{href:`/security/overview`,children:_jsx(_components.code,{children:`security/overview`})}),` for the broader compliance posture.`]}),`\n`,_jsx(Heading,{level:`2`,id:`time‑limited-sandbox-keys`,children:`Time‑limited sandbox keys`}),`\n`,_jsxs(_components.p,{children:[`A sandbox key is `,_jsx(_components.strong,{children:`active for 30 days of inactivity`}),`. If you don’t make any request for 30 days, the key is auto‑disabled. Re‑activate it with a single click from the console.`]}),`\n`,_jsx(_components.p,{children:`There’s no calendar expiration — as long as you keep using it, your sandbox key works indefinitely.`})]})}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":{"theme":"github-dark-dimmed"},"config":{},"pageMetadata":{"title":"Sandbox","description":"Test SafarAPI with realistic data, no production effects","href":"/introduction/sandbox"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"How to get a sandbox key","slug":"how-to-get-a-sandbox-key","depth":2,"children":[]},{"title":"What’s in the sandbox","slug":"what’s-in-the-sandbox","depth":2,"children":[]},{"title":"Pre-defined test travellers","slug":"pre-defined-test-travellers","depth":2,"children":[]},{"title":"Rate limits in sandbox","slug":"rate-limits-in-sandbox","depth":2,"children":[]},{"title":"Resetting your sandbox","slug":"resetting-your-sandbox","depth":2,"children":[]},{"title":"What’s different from production","slug":"what’s-different-from-production","depth":2,"children":[]},{"title":"Isolation guarantees (for your compliance team)","slug":"isolation-guarantees-for-your-compliance-team","depth":2,"children":[]},{"title":"Time‑limited sandbox keys","slug":"time‑limited-sandbox-keys","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":["/_mintlify/custom/safariat/f751755c840beb5a/trust/tools/generate-cobranded-pdf.js"],"subdomain":"safariat","siteName":"SafarAPI","title":"SafarAPI — Morocco travel API for banks \u0026 fintechs","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-05-18T19:06:29.347Z","originalFileLocation":"introduction/sandbox.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=\"system\"}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=\"Test SafarAPI with realistic data, no production effects\">\u003cmeta data-mint-head name=\"application-name\" content=\"SafarAPI\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/safariat/edKFf_9gmlxKUwaY/_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/safariat/edKFf_9gmlxKUwaY/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/safariat/edKFf_9gmlxKUwaY/_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/safariat/edKFf_9gmlxKUwaY/_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/safariat/edKFf_9gmlxKUwaY/_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/safariat/edKFf_9gmlxKUwaY/_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/safariat/edKFf_9gmlxKUwaY/_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/safariat/edKFf_9gmlxKUwaY/_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/safariat/edKFf_9gmlxKUwaY/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"SafarAPI — Morocco travel API for banks \u0026amp; fintechs\">\u003cmeta data-mint-head property=\"og:description\" content=\"Quote, book and settle curated Moroccan travel from your banking app. Sandbox in minutes, production in 7 days.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"SafarAPI\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://developers.safarapi.com/introduction/sandbox\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://safariat.mintlify.site/_mintlify/api/og/v2?division=Introduction\u0026amp;title=Sandbox\u0026amp;description=Test+SafarAPI+with+realistic+data%2C+no+production+effects\u0026amp;theme=3b8c95b43b12f994ae542965\u0026amp;signature=f61cf445c8ce7fa73ee5edcb259d26fc828fa1de56fdec58d230edb7d8da5f6b\">\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=\"SafarAPI — Morocco travel API for banks \u0026amp; fintechs\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"Quote, book and settle curated Moroccan travel from your banking app. Sandbox in minutes, production in 7 days.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://safariat.mintlify.site/_mintlify/api/og/v2?division=Introduction\u0026amp;title=Sandbox\u0026amp;description=Test+SafarAPI+with+realistic+data%2C+no+production+effects\u0026amp;theme=3b8c95b43b12f994ae542965\u0026amp;signature=f61cf445c8ce7fa73ee5edcb259d26fc828fa1de56fdec58d230edb7d8da5f6b\">\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/safariat/edKFf_9gmlxKUwaY/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"SafarAPI\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#f2460d\">\u003clink data-mint-head rel=\"canonical\" href=\"https://developers.safarapi.com/introduction/sandbox\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/introduction/sandbox.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://developers.safarapi.com/#organization\",\"name\":\"SafarAPI\",\"url\":\"https://developers.safarapi.com\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/safariat/uq0i7jK0LmN_yZ-e/logo/safarapi-light.svg?fit=max\u0026auto=format\u0026n=uq0i7jK0LmN_yZ-e\u0026q=85\u0026s=6e54d448368f1737309eebb08e5e571c\"}},{\"@type\":\"WebSite\",\"@id\":\"https://developers.safarapi.com#website\",\"name\":\"SafarAPI\",\"url\":\"https://developers.safarapi.com\",\"publisher\":{\"@id\":\"https://developers.safarapi.com/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://developers.safarapi.com/introduction/sandbox#webpage\",\"url\":\"https://developers.safarapi.com/introduction/sandbox\",\"name\":\"Sandbox\",\"description\":\"Test SafarAPI with realistic data, no production effects\",\"dateModified\":\"2026-05-18T19:06:29.347Z\",\"isPartOf\":{\"@id\":\"https://developers.safarapi.com#website\"},\"breadcrumb\":{\"@id\":\"https://developers.safarapi.com/introduction/sandbox#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://developers.safarapi.com/introduction/sandbox#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Introduction\",\"item\":\"https://developers.safarapi.com/introduction/welcome\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Sandbox\",\"item\":\"https://developers.safarapi.com/introduction/sandbox\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://developers.safarapi.com/introduction/sandbox#article\",\"headline\":\"Sandbox\",\"name\":\"Sandbox\",\"description\":\"Test SafarAPI with realistic data, no production effects\",\"url\":\"https://developers.safarapi.com/introduction/sandbox\",\"mainEntityOfPage\":{\"@id\":\"https://developers.safarapi.com/introduction/sandbox#webpage\"},\"dateModified\":\"2026-05-18T19:06:29.347Z\",\"publisher\":{\"@id\":\"https://developers.safarapi.com/#organization\"},\"isPartOf\":{\"@id\":\"https://developers.safarapi.com#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"SafarAPI\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"SafarAPI","nav":{"global":null,"tabs":[{"tab":"Guides","groups":[{"group":"Introduction","pages":[{"title":"Welcome to SafarAPI","href":"/introduction/welcome"},{"title":"5-minute quickstart","href":"/introduction/quickstart"},{"title":"Authentication","description":"Bearer API key + HMAC-signed writes","href":"/introduction/authentication"},{"title":"Sandbox","href":"/introduction/sandbox"}]},{"group":"Concepts","pages":[{"title":"Quotes","description":"Frozen prices for 30 minutes before booking","href":"/concepts/quotes"},{"title":"Bookings","href":"/concepts/bookings"}]},{"group":"API concepts","pages":[{"title":"Errors","href":"/api-concepts/errors"},{"title":"Idempotency","href":"/api-concepts/idempotency"},{"title":"Pagination","href":"/api-concepts/pagination"},{"title":"Rate limiting","href":"/api-concepts/rate-limiting"},{"title":"Money \u0026 currency","href":"/api-concepts/money"},{"title":"Localization","href":"/api-concepts/localization"},{"title":"Webhooks","href":"/api-concepts/webhooks"},{"title":"Settlements \u0026 reconciliation","href":"/api-concepts/settlements"},{"title":"Troubleshooting","href":"/api-concepts/troubleshooting"},{"title":"Glossary","href":"/api-concepts/glossary"}]},{"group":"Security \u0026 Compliance","pages":[{"title":"Security \u0026 Compliance","href":"/security/overview"}]},{"group":"Trust \u0026 Legal","pages":[{"title":"Data Processing Agreement","href":"/trust/dpa"},{"title":"Service Level Agreement","href":"/trust/sla"},{"title":"Security Attestations","href":"/trust/attestations"}]},{"group":"Service status","pages":[{"title":"Service status","href":"/status/index"}]},{"group":"Changelog","pages":[{"title":"Changelog","href":"/changelog/overview"}]}]},{"tab":"API Reference","pages":[{"title":"API Reference","href":"/api-reference/openapi"}]}]},"basePath":"","title":"SafarAPI — Morocco travel API for banks \u0026 fintechs","buildId":"dpl_8LJbrAiZvr1K98WQNZXCyY5AvT34","deploymentId":"dpl_8LJbrAiZvr1K98WQNZXCyY5AvT34","dynamics":[],"chunks":[]}