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.

CredentialLivesDoes
pk_live_…In your appIdentifies the app to the SDK. Safe to ship — it can't mint sessions or read anything.
sk_live_…Your server onlyMints session tokens. Shown once at issuance. Rotate from the portal; the old key keeps working for 24 hours.
vst_…In flightOne 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:

KeysBilling
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.

bash
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"]
  }'
FieldMeaning
host_user_refYour stable ID for the member. Required. See below.
scopesOptional. 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

StatusCodeWhy
401publishable_key_usedYou sent the pk by mistake. Sessions need the secret key, server-side.
403key_revokedThe key was rotated past its grace window or revoked. Deploy the replacement.
429quota_exceededThe 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:

The #1 integration mistake: a bundle/team mismatch. The app's 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.
RequirementDetail
App identifiersYour 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 deviceApp 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.

YoursPurpose
POST /v1/sessionsYour backend, per member action, with the secret key.
Webhooksscan.completed / scan.failed — and scan.results_available if enabled.
DELETE /v1/resultsMember 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.