API-DIRECT-CALL¶
Login + OTP via JSON API. Replaces INIT → HOME → PRE-LOGIN → LOGIN → OTP-TRIGGER → OTP-FILL for api-direct banks (OneZero, Pepper, PayBox) — six browser phases collapse into one headless phase with a declarative step list.
| Always-on? | api-direct banks only |
| Owner slots | apiMediator, login, api |
| Source | ApiDirectCallPhase.ts + ApiDirectCallActions.ts + Flow/ |
Unified api-direct primitives¶
Every api-direct bank reuses the same building blocks below the phase, so adding a new device-bound or symmetric-signing bank is config-only — no mediator code.
Signer (discriminated union)¶
| Kind | Used by | Algorithm | Signature placement |
|---|---|---|---|
| Asymmetric | Pepper | ECDSA-P256 / RSA-2048 | Header-attached |
| Symmetric | PayBox | AES-CBC-PKCS7 | Body — RFC-6901 pointer |
Banks declare the algorithm + canonical-string parts + key-ref in their PipelineBankConfig.headless.signer literal; the mediator dispatches without bank knowledge.
JsonValueTemplate¶
Declarative body literal with $ref tokens:
| Token | Resolves to |
|---|---|
$literal: "v" | Literal value "v" |
$ref: creds.<field> | The credential at that key |
$ref: carry.<slot> | The flow's per-step carry slot |
$ref: config.<dotted.path> | The bank's config object |
One hydration engine serves both API-DIRECT-CALL and API-DIRECT-SCRAPE step bodies — no per-bank imperative body assembly.
Carry derivation¶
seedCarryFromCredsmirrors creds into carry slots at flow init.sha256-prefix-16derives a stable identifier from another creds field (PayBox uses this to bind its long-term JWT to a phone-deriveddeviceId16Hex— warm-start-stable without the caller persisting state).derivedCarryjoins parts with separators + truncation for OTP-encryption keys.
CryptoField pre-hook¶
Per-step optional encryption hook: takes a value from carry (e.g. the SMS OTP), AES-encrypts it with a key from config.secrets.* or carry.<slot>, writes the ciphertext into the outbound body at an RFC-6901 pointer, and scrubs the plaintext. PayBox uses this to encrypt the OTP into /pin with a fresh IV at /pinIv.
Phone normaliser¶
Every api-direct bank declares its wire format in PipelineBankConfig.headless.phoneNumberFormat:
| Bank | Format | Example |
|---|---|---|
| OneZero | international-plus | +972000000000 |
| Pepper | international-flat | 972000000000 |
| PayBox | international-dash | 972-000000000 |
Callers always pass digits-only international form; the ACTION-stage mediator rewrites once before the flow runs.
Sub-step contract¶
| Hook | What it does |
|---|---|
.pre | Resolve apiDirectCallConfig from the bank's PipelineDescriptor; seed carry. |
.action | Run the declarative step list: each step has a body template + optional crypto hook + optional signer. The mediator hydrates, signs, sends, parses response, updates carry. OTP step calls credentials.otpCodeRetriever mid-flow. |
.post | Validate the final carry has the required slots (auth token, user id). |
.final | Publish the final carry via setSessionContext; build IApiFetchContext; commit api + login slots. |
Failure modes¶
errorType | Cause |
|---|---|
INVALID_PASSWORD | Bank rejected credentials |
INVALID_OTP | Wrong code |
TWO_FACTOR_RETRIEVER_MISSING | OTP step reached without callback |
TIMEOUT | A step's HTTP call didn't complete |
GENERIC | Signer key missing, response shape drift, or a cryptoField failure |