Testing and local sandbox
There is no hosted sandbox environment today. The supported ways to test are test-mode API keys against your deployment, and the local instance with seeded demo data, which behaves exactly like production because it is the same code.
Test mode#
API keys carry a mode. pi_test_… keys mark every decision they create with "mode": "test", keeping test traffic distinguishable in the decision log and analytics. Use test keys everywhere except production.
The local sandbox#
Run the full platform locally (see Getting started or Local Setup):
docker compose up -d
pnpm db:generate && pnpm db:migrate && pnpm db:seed
pnpm dev
The seed gives you a complete working environment:
| Item | Value |
|---|---|
| API | http://localhost:4000 |
| Dashboard | http://localhost:3000 (demo@payinference.dev / demo-password-123) |
| Merchant | m_123 |
| API key | pi_demo_key_123 (all scopes) |
| Data | A published policy, provider health snapshots, 24 h of decision history |
All providers in the local environment are mocks driven by synthetic probes. No real PSP credentials exist anywhere in the repository, and no real payment can occur.
Testing decision handling#
Exercise every instruction path in your handler. Instructions are driven by policy rules, retry context, and provider health, so drive them deliberately:
blockandstep_up: these come from matched policy rules, so publish a test policy with low thresholds, for example{ "when": { "amount_gte": 1 }, "then": { "type": "step_up" } }, or a rule onrisk_score_gtecombined with atransaction.risk_scorein your request. Sendingrisk_signalsalone raises the advisoryriskobject in the response but does not change the instruction.hold: publish a rule with{ "then": { "type": "hold" } }; hold outranks step_up when both match.retry: publish aretry_on_timeoutrule and includeprevious_attemptwithoutcome: "timeout"in the request.failover: includeprevious_attemptwithoutcome: "timeout"naming a provider whose cached health state is degraded or worse.blockvia retry suppression: publish ano_retryrule listing a reason code, then sendprevious_attempt.provider_reason_codematching it.use_default_route: make every offered provider ineligible (for example arequire_min_healthrule with a score of 100) while the policy defines adefault_provider.
Verify each handled decision lands in the decision log with the outcome you reported: GET /v1/decisions/:decisionId.
Simulating policies#
Test a policy against sample transactions without publishing or affecting live decisions:
curl -s http://localhost:4000/v1/policies/simulate \
-H "content-type: application/json" \
-H "x-api-key: pi_demo_key_123" \
-d '{
"merchant_id": "m_123",
"transaction": { "amount": 75000, "currency": "EUR", "country": "DE", "payment_method": "card" },
"available_providers": ["stripe", "adyen"]
}'
The response shows the resulting action, matched rules, excluded providers, and per-provider evaluations, using the same engine and cached health as the live path.
Unit testing your integration#
Inject a fake fetch into the Node SDK to test your handler without any network:
import { PayInferenceClient } from '@payinference/sdk-node';
const client = new PayInferenceClient({
apiKey: 'pi_test_x',
fetch: async () =>
new Response(
JSON.stringify({
decision_id: 'dec_test',
merchant_id: 'm_123',
mode: 'test',
action: 'route',
route: { primary_provider: 'stripe', fallback_provider: null },
provider_health: {},
policy: { policy_id: null, policy_version: null, matched_rules: [] },
model: { model_version: 'test', route_scores: {} },
reason_codes: [],
decision_latency_ms: 1,
ttl_ms: 3000,
}),
{ status: 200 },
),
});
Test your outage path too: point baseUrl at an unroutable address and assert that your fallback executes.
Latency verification#
pnpm benchmark preloads the cache, fires 1,000 decision requests at the running API, verifies health was served from cache, and prints average, p50, p95, p99, and max latency. Local targets: average under 25 ms, p95 under 50 ms, zero errors. Run it against your own deployment to validate the budget from your network position.