Skip to main content
Two-factor authentication (2FA) adds a second check to withdrawals. When 2FA is on, every withdrawal and every factor change needs a proof from a second factor, in addition to the request signature. Renaming a factor is the only change that needs no proof. A factor is one of these:
  • An authenticator app (TOTP) that shows a 6-digit code.
  • A passkey or a security key (WebAuthn). These work only in the Pacifica app. See Passkeys and security keys.
API clients should use an authenticator app.

Turn on 2FA with an authenticator app

  1. Call Begin TOTP enrollment with a label. The response has an enrollment_id, a base32 secret, and an otpauth_uri.
  2. Add the secret to an authenticator app. Show otpauth_uri as a QR code, or type secret in by hand. Codes use SHA-1, 6 digits, and a 30-second period.
  3. Within 10 minutes, call Finish TOTP enrollment with the enrollment_id and the app’s current code.
  4. Save the recovery_keys from the response. Pacifica shows them only once.
2FA is now on. Get two-factor status returns "protected_actions": ["withdrawal"]. The code that finishes enrollment is used up. Wait for the next code before you send your first proof.

Proofs

A proof is a JSON object. Its type field selects the factor. Put the proof in the request body: Do not sign the proof. Sign the request without it, then add the proof to the body. Each proof works once:
  • A TOTP code works once. After a proof passes, wait for the next code.
  • A recovery key works once.
  • A WebAuthn challenge works once.
Each action accepts these proofs: Renaming a factor needs no proof. A recovery key on a withdrawal returns status 422.

Who can sign

An agent wallet cannot add, remove, or rename factors. It cannot regenerate recovery keys or turn 2FA off. A subaccount gets status 403 from the main-account-only endpoints. These endpoints use the normal signing scheme. Each page shows the data object to sign. Some request fields are not signed: code on Finish TOTP enrollment, credential on Finish WebAuthn registration, and every proof. Sign UUID values in lowercase, as the API returns them.

Limits

  • Proof attempts. Each signer can make 5 proof attempts per 5 minutes. A proof that passes resets this count. The account key and each agent wallet have separate counts, so an agent wallet that sends wrong proofs locks out only itself.
  • Wrong proofs. Each signer can send 30 wrong proofs per 24 hours. After that, Pacifica refuses every proof from that signer with status 429 until 24 hours after its first wrong proof. A proof that passes does not reset this count.
  • WebAuthn challenges. Each signer can request up to 60 challenges per 10 minutes for each action. A WebAuthn proof with an unknown, expired, or used challenge does not count as a proof attempt.
  • Factors. An account can have up to 8 factors. It can have only one authenticator app. A new authenticator app replaces the old one when you finish its enrollment.
  • Enrollments. You can start up to 20 enrollments per 10 minutes. An enrollment lasts 10 minutes. A TOTP enrollment allows 5 wrong codes. After that, start again.
  • Retrying a begin call. While 2FA is on, the begin enrollment calls need a proof. After the proof passes, you can repeat the same begin request, with the same label (and kind for WebAuthn), for 5 minutes without a new proof. This helps when a user cancels an authenticator prompt.
  • Factor changes. Adding a factor, removing a factor, regenerating recovery keys, and disabling 2FA end every enrollment in progress and every 5-minute retry window. A finish call then returns status 422 with two-factor settings changed. Start again. Renaming a factor does not end them.
Pacifica checks the factor limit, the enrollment limit, and whether a factor can be removed before it checks the proof. A request that fails these checks does not use up your code.

Recovery keys

  • Your first factor comes with 10 recovery keys. Later factors do not add keys. Their finish response has an empty recovery_keys list.
  • Pacifica shows recovery keys only once. Store them somewhere safe.
  • A key is 8 groups of 4 hex characters, for example 3f9a-1c0b-77de-a4e2-0b19-5c6d-e8f0-2a7b. Case and hyphens do not matter.
  • Each key works once. Get two-factor status returns recovery_keys_remaining.
  • A recovery key authorizes factor changes only. It never authorizes a withdrawal.
  • If you lose your authenticator app, send a recovery key as the proof to Begin TOTP enrollment or to Disable two-factor authentication.
  • Regenerate recovery keys replaces the whole set. Disabling 2FA deletes it.

Withdrawals

When 2FA is on, every withdrawal needs a proof, including withdrawals that an agent wallet signs. Send an idempotency_key, and send the proof in two_factor. For details, see Request withdrawal and Withdraw spot asset.

Passkeys and security keys

Passkeys and security keys use WebAuthn. Pacifica binds them to its own domain, and it accepts them only from the Pacifica app’s origin. Pacifica also rejects an assertion that another site requested through an embedded frame, that is, an assertion with crossOrigin or topOrigin set in its client data. So in practice, you can register and use a passkey only in the Pacifica app. A script or bot cannot make a passkey proof. API clients should use an authenticator app. A WebAuthn proof needs a challenge from Begin WebAuthn authentication. The challenge is bound to the exact request that the proof authorizes.

Errors

2FA errors carry an error_id. Match on error_id, not on error. See Error codes.
error values with status 422:

Code Example (Python)

This example turns on 2FA with an authenticator app, then withdraws with a TOTP proof. The signing steps follow Signing.