Skip to main content

Credentials

Every customer operation is authenticated with an API key and nothing else. Session credentials are rejected: these routes are API-key only.
Keys are environment-bound. A sk_test_… key against the live host fails with 401 INVALID_API_KEY, and so does the reverse — the failure is deliberately indistinguishable from an invalid key.
API-key scopes are stored when a key is issued but are not enforced on the Business API today. Do not design a permission model that depends on scoping a key to customers only. This is an open item in CONTRACT-DECISIONS.md.

Prerequisites the service checks

Two checks run before any customer work happens, in this order:
  1. The client exists. If the key authenticates but its business has been removed, you get 404 client_not_found.
  2. The client is a business. Customer management is a business-client feature; anything else gets 409 client_not_a_business.
There is no feature flag, tier gate, or product entitlement on customers. If your key works and your client is a business, all five operations are available.

Environments

Sandbox and live are separate deployments with separate databases. A customer created in sandbox does not exist in live, and identifiers do not carry across.
Never copy real identity numbers, real phone numbers, or real addresses into sandbox. Use clearly synthetic values, as every example in these pages does.

Rate limits

Limits are per 60-second window and are counted twice for each request — once against your business, once against the individual key. Both must pass. Two further limits apply to every authenticated request regardless of route: 300 per minute per source IP, and 1,200 per minute per credential. Exceeding any of them returns 429 with retryAfterSeconds in the body. There is no Retry-After header yet — read the body. See rate limits for backoff guidance. Bulk import arithmetic follows directly: at 120 writes per minute, ten thousand customers takes about 83 minutes at a steady rate. Pace the job rather than absorbing 429s.

Webhooks

Customers emit two events, both carrying the complete customer object as their data: Archiving emits nothing. Verification outcomes arrive as customer.kyc.approved, customer.kyc.rejected and customer.kyc.suspended, each carrying the customer object plus a kyc object. They follow the verification for as long as it exists, so the same customer can send more than one. See Identity and verification. Three things to get right in the receiver:
  1. Deduplicate on the Sakawave-Webhook-Id header. The body has only event and data; there is no ID inside it.
  2. The event payload is a snapshot, not a delta. It tells you the customer’s state at that moment, not what changed. If you need the diff, compare against your own copy.
  3. Do not treat the event as the source of truth. Delivery is a single attempt with manual resend and no automatic retry. Reconcile by listing customers.
See verify signatures for complete handlers.

Request identifiers

Send X-Request-ID on every call. It comes back on the response and is the fastest way for support to find your request. If you omit it, or send something malformed, Sakawave generates one.

Configuration checklist

  • Sandbox and live keys stored in a secrets manager, never in source control
  • Base URL configured per environment, never hardcoded per call site
  • externalReference chosen from a stable identifier in your own system
  • Write throughput paced under 120 per minute
  • X-Request-ID set from your own trace or correlation ID
  • Webhook endpoint configured separately in each environment, deduplicating on the header ID
  • Error handling branches on code, never on message