Authentication
Three credentials, each with one job. The design goal: nothing that ships inside an app binary can spend your money or read your data.
| Credential | Lives | Does |
|---|---|---|
pk_live_… | In your app | Identifies the app to the SDK. Safe to ship — it can't mint sessions or read anything. |
sk_live_… | Your server only | Mints session tokens. Shown once at issuance. Rotate from the portal; the old key keeps working for 24 hours. |
vst_… | In flight | One short-lived token per member action, 15-minute expiry. What the SDK actually presents. |
Test and live keys
Every app can hold both kinds of key pair, issued separately in the portal. They behave identically through the whole flow — same endpoints, same webhooks, same SDK — with one difference:
| Keys | Billing |
|---|---|
pk_test_… / sk_test_… | Never billed. Use for development, CI, and staging builds. |
pk_live_… / sk_live_… | A completed scan counts toward your monthly active scanning users. Issuing live keys requires your billing profile and accepted terms. |
The mode lives on the key, not the app — a scan bills exactly when the session that ran it was minted with a live secret key and the scan completed. Failed and abandoned scans are never billed, in either mode.
Minting sessions
POST /v1/sessions with your secret key. One token authorizes one scan session; the expiry is what makes key revocation take effect quickly on devices.
curl https://api.visualizeme.ai/v1/sessions \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"host_user_ref": "user_842",
"scopes": ["scan.create"]
}'| Field | Meaning |
|---|---|
host_user_ref | Your stable ID for the member. Required. See below. |
scopes | Optional. Defaults to scan.create, which is all a normal scan needs. |
host_user_ref is your billing unit — get it right. Visualize bills per distinct host_user_ref per month (a monthly active scanning user). So it must be your own stable ID for the signed-in member, the same value every time for the same person and across their devices — set by your server from its own authentication, never taken from the app. Get it wrong two ways and it costs you: a value that changes per session or device over-counts (you pay more); the same value shared across different people under-counts and mixes their results together.Refusals you should handle
| Status | Code | Why |
|---|---|---|
| 401 | publishable_key_used | You sent the pk by mistake. Sessions need the secret key, server-side. |
| 403 | key_revoked | The key was rotated past its grace window or revoked. Deploy the replacement. |
| 429 | quota_exceeded | The app hit its daily session ceiling — an abuse guard. Contact support if a real workload hit it. |
Device attestation
The SDK enrolls each installation with Apple App Attest before its first scan — proof the request comes from a genuine iPhone running your signed build, not a script or an emulator. It is fully automatic; there is no attestation API for you to call. Two things you do control:
Bundle ID and Team ID in the portal must exactly match the app you build and ship. If they differ, attestation is refused and scans fail — and the error names attestation, not the bundle, so it's easy to chase the wrong thing. Copy both from Xcode's Signing & Capabilities.| Requirement | Detail |
|---|---|
| App identifiers | Your app's Bundle ID and 10-character Apple Team ID must be set on the app in the portal, exactly as Xcode shows them. Attestation binds to that pair — a mismatch refuses enrollment with 403 attestation_invalid, and fixing the portal values fixes it. |
| A real device | App Attest does not exist in the Simulator; the SDK runs a stub there so development still works end to end. Real scans and enrollment need a physical iPhone. |
Enrollment happens once per installation and survives app updates; reinstalls re-enroll automatically. If a device is ever blocked server-side, its next scan simply fails to start — revocation needs nothing from your code.
What you call, and what the SDK calls
Your integration touches exactly three things. Everything else on the API — attestation, model manifests and downloads, model keys, mesh fitting, scan events — is device plumbing the SDK drives for you; those endpoints only accept session tokens from attested devices, and there is nothing useful to call them with from a server.
| Yours | Purpose |
|---|---|
POST /v1/sessions | Your backend, per member action, with the secret key. |
| Webhooks | scan.completed / scan.failed — and scan.results_available if enabled. |
DELETE /v1/results | Member deletion requests, only with results delivery enabled. |
Want the machine-readable contract? Download the Session API OpenAPI spec — import it into Postman or generate a client. It covers exactly the server-side operations above.