ALLOCATOR℠ API Guide


Table of contents

  1. What is ALLOCATOR℠?
  2. Before you start
  3. Base URL, transport, and versioning
  4. Authentication
  5. Endpoint reference
  6. Request payload: structure
  7. Request payload: field-by-field
  8. Validation and error handling
  9. Response payload: field-by-field
  10. Worked end-to-end examples
  11. Pricing and billing
  12. Service Level Agreement summary
  13. Security requirements
  14. Common pitfalls and FAQ
  15. Support

1. What is ALLOCATOR℠?

ALLOCATOR℠ is a portfolio-optimization API that applies Merton continuous-time portfolio theory with constant-relative-risk-aversion (CRRA) utility to compute a household’s optimal allocation between the Empirical Global Market Portfolio (EGMP-14) and a risk-free asset.

The unit of analysis is the household — either a single individual (client) or a married couple (client + spouse). Every household asset is modelled: Social Security, Medicare, pensions, human capital (remaining labor earnings), businesses, real estate, vehicles, bank CDs, annuities, life-insurance cash values, and any other illiquid background wealth. Present values are computed using CAPM-based discount rates and sex-specific actuarial life tables, and expected returns / covariances are iteratively adjusted to an after-tax basis.

1.1 What the API gives you back

A single POST /allocator call returns:

1.2 Two kinds of users

Mode Who it is for Authentication Billing
RIA Client Registered investment advisers (B2B integrations) X-User-Token Monthly in arrears, auto-charge
Individual Client A California resident using the API for their own household X-Payment-Intent (via Stripe) Pay-per-call, captured on success

The underlying computation is identical for both. The only differences are how you authenticate and how you pay. See §4 — Authentication.

1.3 California-only (for now)

Advisory API Systems LLC is registered only in the State of California. Every /allocator request must represent a household where:

Requests from non-California households are rejected with a 400 response and rule: california_residency.


2. Before you start

2.1 What you need

2.2 Disclosures you must have read

By making any API request you agree to the User Agreement, Privacy Policy, Data Processing Agreement, and Investment Advice Disclaimer. Relevant documents:

If you are submitting a spouse block, you must have the spouse’s authority to provide their data — see §7.3 — Spouse block and User Agreement §2.7.


3. Base URL, transport, and versioning

3.1 Base URL

All endpoints are served from:

https://api.ria.us

3.2 Transport

3.3 Versioning

The API version is returned on GET /health as version: "3.2.0". Breaking changes will produce a new major version path; backward-compatible additions are rolled forward in-place. The OpenAPI specification is authoritative: if anything in this guide disagrees with GET /openapi.json, the spec wins.


4. Authentication

Authentication is via a single HTTP request header. Each request carries exactly one of the following:

Header Mode Who uses it
X-User-Token RIA Client RIAs, integrators
X-Payment-Intent Individual Client Pay-per-call, one request
X-Admin-Token Administrative Reserved; not public

Unauthenticated endpoints (/health, /openapi.json, /docs, /spec, /create-payment-intent) accept requests with no auth header at all.

4.1 X-User-Token — RIA Clients

This is a static token issued to you by Advisory API Systems. You present it on every /allocator request:

X-User-Token: asys_sk_live_your_token_here

On each successful /allocator call, a Stripe metered-usage event is recorded. At calendar month-end, Stripe automatically charges the payment method on file with your firm’s Stripe customer record on a graduated monthly tier schedule: $34.95 per call for the first 25 calls in a calendar month, $27.95 per call for calls 26–100, and $21.95 per call thereafter (see §11 — Pricing).

Per-token rate limit: 20 requests per rolling 60-minute window. Requests in excess of this limit receive a 429 response and are not billed.

Failed requests are never billed. Specifically:

4.2 X-Payment-Intent — Individual Clients (pay-per-call)

Pay-per-call is a two-step flow. First authorize a Stripe PaymentIntent for $79.95; then call /allocator with the PaymentIntent ID on the header. The card is captured only if /allocator returns 2xx.

The authorize step requires a Stripe-tokenized payment method (pm_...), which in turn requires a browser capable of completing 3-D Secure. curl alone cannot complete the authorize step for any card that triggers 3DS — use a browser or Stripe’s own SDKs for that step, then use curl for the /allocator step if you like.

The full flow is:

[Browser / Stripe.js]  →  POST /create-payment-intent   →  pi_...  (authorized)
[Your client]          →  POST /allocator  (X-Payment-Intent: pi_...)
[Server]               →  Captures card on 200  /  Cancels hold on 4xx/5xx

See §5.5 — POST /create-payment-intent and §10.1 — Pay-per-call worked example.

4.3 Security rules for tokens

From the Security Practices Documentation (incorporated into the User Agreement):


5. Endpoint reference

Six endpoints, grouped below. For each, the authoritative contract is in openapi.yaml; the summaries here are just the integrator-facing view with copy-pasteable curl.

Method Path Auth Purpose
GET /health none Liveness probe
GET /openapi.json none OpenAPI 3.1 specification (JSON)
GET /docs none Swagger UI
GET /spec none Input-validation spec (frontend-consumable)
POST /create-payment-intent none Authorize a pay-per-call Stripe PaymentIntent
POST /allocator X-User-Token or X-Payment-Intent Run the household optimization

5.1 GET /health

Returns the API’s liveness status, server timestamp (Pacific Time), developer identifier, and running version. Suitable for uptime probes.

curl -sS https://api.ria.us/health | jq

Response:

{
  "status": "healthy",
  "timestamp": "2026-04-20 11:42:37 PDT",
  "api_developer": "Advisory API Systems LLC",
  "version": "3.2.0"
}

Note: throughout this guide, jq is used just to pretty-print. You can omit it.

5.2 GET /openapi.json

Returns the full OpenAPI 3.1 specification as JSON, generated server-side from the canonical openapi.yaml on each request. Import this URL directly into Postman (“Import → Link”), Swagger UI, or any OpenAPI-aware client-generator.

curl -sS https://api.ria.us/openapi.json -o openapi.json

To import into Postman:

Postman → Import → Link → https://api.ria.us/openapi.json → Continue

5.3 GET /docs

Serves Swagger UI, loading /openapi.json. Paste an X-User-Token into the auth field at the top of the page to use “Try it out” interactively.

Open https://api.ria.us/docs in a browser.

5.4 GET /spec

Returns input_spec.yaml serialized as JSON. This is the same rules file the backend compiles into its JSON Schema validator. The web interface fetches this endpoint on load so its dropdowns, numeric min/max values, and field hints stay synchronized with validation. Useful for building your own UI.

curl -sS https://api.ria.us/spec | jq '.enums | keys'

Top-level keys: meta, enums, ranges, required, array_items, conditional, error_response.

5.5 POST /create-payment-intent

Authorizes a Stripe PaymentIntent in manual-capture mode for $79.95. The card is captured only on a successful /allocator response; any other outcome cancels the hold.

Request body:

{ "payment_method_id": "pm_1PExAmPlEtOkEn123" }

curl:

curl -sS -X POST https://api.ria.us/create-payment-intent \
  -H "Content-Type: application/json" \
  -d '{"payment_method_id":"pm_1PExAmPlEtOkEn123"}' | jq

Success response (200):

{
  "status": "success",
  "payment_intent_id": "pi_3NXyZ1AbCdEfGhIj0KlMnOpQ",
  "client_secret": "pi_3NXyZ1AbCdEfGhIj0KlMnOpQ_secret_AbCdEfGh",
  "amount": 79.95
}

After receiving this response, the browser must confirm the client_secret with stripe.confirmCardPayment() (this is where 3-D Secure happens). Then present the payment_intent_id on the X-Payment-Intent header of the /allocator request.

Failure modes:

HTTP error Meaning
400 Missing body or payment_method_id not starting pm_
402 Payment Failed Stripe declined the card
500 Internal Stripe / server error

5.6 POST /allocator

The main endpoint. Accepts a complete household profile and returns the allocation, portfolio statistics, balance sheet, income statement, and strategy recommendations.

Request body: see §6 — Request payload: structure.

curl — RIA Client (X-User-Token):

curl -sS -X POST https://api.ria.us/allocator \
  -H "Content-Type: application/json" \
  -H "X-User-Token: $ALLOCATOR_TOKEN" \
  -d @household.json | jq

curl — Individual Client (X-Payment-Intent):

curl -sS -X POST https://api.ria.us/allocator \
  -H "Content-Type: application/json" \
  -H "X-Payment-Intent: $ALLOCATOR_PI" \
  -d @household.json | jq

Response (200): see §9 — Response payload: field-by-field.

Failure modes (common):

HTTP error Cause
400 validation_failed Schema / array-item / cross-field rule violation
400 Asset validation failed Real-estate address or VIN failed external validation
401 Unauthorized Invalid X-User-Token (only when that header was supplied)
402 Payment Required Missing / invalid / expired / captured X-Payment-Intent
500 internal_error Server-side exception; retry with exponential backoff

6. Request payload: structure

The top-level shape is:

{
  "client": {
    "personal_info": { ... },
    "financial_info": { ... }
  },
  "spouse": {
    "personal_info": { ... },
    "financial_info": { ... }
  },
  "household_total_annual_expenses": 12000
}

The smallest request that can succeed is a single, unmarried client with one SSA earnings record. Everything else — employments, businesses, retirement accounts, real estate, vehicles, bank CDs, life insurance — is optional.

6.1 Minimal single-client payload

{
  "client": {
    "personal_info": {
      "name": { "first_name": "Henry", "last_name": "Mason" },
      "address": {
        "street_number": "4248",
        "street_name": "La Salle Ave",
        "city": "Culver City",
        "state": "CA",
        "zip_code": "90232"
      },
      "email_address": "[email protected]",
      "dob": "1930-06-18",
      "gender": "male",
      "married": false
    },
    "financial_info": {
      "max_premium": 150000,
      "household_allocatable_wealth": 2131650.27,
      "tax_filing_status": "single",
      "quarters_of_coverage": 88,
      "ssa_earnings_record": [
        { "year": 1974, "earnings": 2044 }
      ]
    }
  }
}

6.2 Married-household payload

Add a spouse block with personal_info and financial_info. married is implicit on the spouse side (submitting a spouse block is a representation of spousal authority — User Agreement §2.7).

{
  "client": {
    "personal_info": {
      "name": { "first_name": "John", "last_name": "Smith" },
      "address": { "street_number": "4240", "street_name": "Baldwin Ave",
                   "city": "Culver City", "state": "CA", "zip_code": "90232" },
      "email_address": "[email protected]",
      "dob": "1980-02-10",
      "gender": "male",
      "married": true
    },
    "financial_info": {
      "max_premium": 142000,
      "household_allocatable_wealth": 1000000,
      "tax_filing_status": "married filing jointly",
      "quarters_of_coverage": 92,
      "ssa_earnings_record": [ { "year": 2023, "earnings": 180610 } ]
    }
  },
  "spouse": {
    "personal_info": {
      "name": { "first_name": "Jane", "last_name": "Smith" },
      "address": { "street_number": "4240", "street_name": "Baldwin Ave",
                   "city": "Culver City", "state": "CA", "zip_code": "90232" },
      "email_address": "[email protected]",
      "dob": "1982-02-04",
      "gender": "female"
    },
    "financial_info": {
      "max_premium": 144000,
      "tax_filing_status": "married filing jointly",
      "quarters_of_coverage": 16,
      "ssa_earnings_record": [ { "year": 2017, "earnings": 80000 } ]
    }
  },
  "household_total_annual_expenses": 12000
}

6.3 Field categories

The household profile maps onto several categories of input:

Category Location in payload Example fields
Identity .personal_info.{name,address,dob,gender,email_address,phone}
Marital status client.personal_info.married boolean
Wealth to allocate client.financial_info.household_allocatable_wealth number ≥ 0
Risk tolerance client.financial_info.max_premium and, if married, spouse.financial_info.max_premium ($50k, $300k) exclusive
Tax filing .financial_info.tax_filing_status enum
Social Security history .financial_info.ssa_earnings_record[] array of {year, earnings}
Social Security claiming .financial_info — claiming-age / benefit / optimization fields see §7.2
Human capital .financial_info.employments[] employer, income, retire age, occupation category
Business capital .financial_info.businesses[] EIN, net income, category, ownership
DC plans (IRAs) .financial_info.retirement.defined_contribution_plans.accounts_IRA[]
DC plans (401(k)) .financial_info.retirement.defined_contribution_plans.accounts_401k[]
DB plans .financial_info.retirement.defined_benefit_plans.pensions[]
Annuities .financial_info.retirement.annuities.contracts[]
Life insurance .financial_info.life_insurance.policies[]
Real estate .financial_info.real_estate[]
Vehicles .financial_info.vehicles[]
Bank CDs .financial_info.bank_CDs[]
Miscellaneous income .financial_info.other_annual_income (+ other_income_ownership)
Miscellaneous wealth .financial_info.other_background_wealth (+ other_background_wealth_ownership)
Household expenses top-level household_total_annual_expenses

7. Request payload: field-by-field

This section is organized by location in the JSON tree. Ranges and enums shown below come from input_spec.yaml — the single source of truth for validation — and are exactly what GET /spec returns.

7.1 client.personal_info

Required on every request.

7.2 client.financial_info

Required fields: max_premium, tax_filing_status, household_allocatable_wealth, quarters_of_coverage. ssa_earnings_record is optional — omit it for clients with no SSA-covered employment history (e.g., living on inheritance, expecting only spousal benefits).

Risk tolerance and wealth

Tax and Social Security

Tip. Supply the SSA earnings record exactly as it appears on the client’s official SSA statement (ssa.gov). The SSA statement already excludes non-covered earnings (from 2025 onward the Windfall Elimination Provision and Government Pension Offset are gone — the Social Security Fairness Act was signed January 5, 2025).

Other income / other wealth

Housing context

7.3 Spouse

Required when client.personal_info.married == true.

User Agreement §2.7 — by submitting a spouse block you represent that you have legal authority to provide the spouse’s data, to receive the Form ADV and disclosures on their behalf, and to enter into the User Agreement on their behalf. The spouse is a Client of the Company and must receive the User Agreement, Privacy Policy, and ADV disclosures and be informed of the five-business-day right to terminate.

7.4 Array items

Every array item has its own required/optional field set. The rules below match input_spec.yamlarray_items.

7.4.1 ssa_earnings_record[]

Required: year, earnings.

{ "year": 2024, "earnings": 183730 }

7.4.2 employments[]

Required: employer_name, employee_id.

{
  "employer_name": "Acme Software Company",
  "employee_id": "328",
  "location": { "city": "Culver City", "state": "CA" },
  "job_title": "Software Engineer",
  "occupation_category": 6,
  "income": 184000,
  "expected_retirement_age": 65
}

7.4.3 businesses[]

Required: business_name, ein, business_category, net_income, ownership.

{
  "business_name": "John's Hardware LLC",
  "ein": "78-5566676",
  "business_category": 13,
  "net_income": 130000,
  "ownership": "community"
}

7.4.4 retirement.defined_contribution_plans.accounts_IRA[]

Required: account_id, account_type, balance.

{
  "account_id": "IRA-123456789",
  "account_type": "traditional",
  "custodian": "Fidelity Investments",
  "balance": 177500
}

7.4.5 retirement.defined_contribution_plans.accounts_401k[]

Required: account_id, account_type, balance.

{
  "account_id": "778899",
  "account_type": "traditional",
  "employer": "Acme Software Company",
  "custodian": "Custodial Company of America",
  "balance": 284365.15
}

7.4.6 retirement.defined_benefit_plans.pensions[]

Required: pension_id, plan_type, type, status, annual_amount.

{
  "pension_id": "P8863760",
  "plan_type": "defined_benefit",
  "type": "government",
  "employer": "City of Eureka",
  "administrator": "HHTR",
  "status": "vested",
  "annual_amount": 68000
}

7.4.7 retirement.annuities.contracts[]

Required: issuer, contract_number, base_payment_amount, ownership.

{
  "issuer": "Pac Life",
  "contract_number": "ANN-66780",
  "base_payment_amount": 1000,
  "life": true,
  "term_years": 0,
  "ownership": "community",
  "investment_in_contract": 10000,
  "is_qualified": false,
  "deferral_period": 10,
  "benefit_growth_rate": 0.06,
  "has_cola": true,
  "cola_rate": 0.035,
  "is_joint_life": true,
  "survivor_percentage": 1
}

Conditional rules:

Important modelling note: deferred annuity income does not appear in current-income totals. It goes into income_statement.future_income[] with starts_in_years. Only immediate annuity payments count as current income.

7.4.8 life_insurance.policies[]

Required: insurer, policy_number, policy_type, face_amount, ownership.

{
  "insurer": "NW Life",
  "policy_number": "INS-5548",
  "policy_type": "whole",
  "face_amount": 100000,
  "net_surrender_value": 25000,
  "annual_premium": 1000,
  "ownership": "community"
}

7.4.9 real_estate[]

Required: address, city, state, zip, type, purchase_price, purchase_year, purchase_month.

{
  "address": "4240 Baldwin Ave",
  "city": "Culver City",
  "state": "CA",
  "zip": "90232",
  "type": "Multi-Family",
  "bedrooms": 4,
  "bathrooms": 2,
  "sqft": 2000,
  "units": 2,
  "mortgage_balance": 180000,
  "purchase_price": 340000,
  "purchase_year": 2008,
  "purchase_month": 1,
  "monthly_payment": 1400,
  "ownership": "community",
  "owner_occupied": true
}

7.4.10 vehicles[]

Required: vin, mileage, purchase_price, purchase_year, purchase_month.

{
  "vin": "1N4AL3AP4EC144655",
  "mileage": 70382,
  "purchase_price": 17000,
  "purchase_year": 2015,
  "purchase_month": 3,
  "amount_owed": 4500,
  "monthly_payment": 350,
  "ownership": "community"
}

Valuation methodology. Vehicle valuation derives from a comparable-listings analysis. We aggregate price statistics over active private-party listings matching the user’s vehicle by year, make, model, and trim, weighting toward listings with similar mileage where the data supports it, and falling back to exponential depreciation when no comparable listings are available.

7.4.11 bank_CDs[]

Required: account_balance, current_apy.

{
  "institution": { "name": "Chase", "branch": "5th and Spring" },
  "account_number": "CD-66580",
  "title": "36 Month CD",
  "principal": 36000,
  "account_balance": 36000,
  "currency": "USD",
  "issue_date": "2025-04-12",
  "maturity_date": "2029-04-12",
  "current_apy": 0.046,
  "auto_renew": false,
  "compounding_method": "daily",
  "ownership": "community"
}

7.5 Community property and ownership

ownership on every asset that supports it is one of:

Value Meaning
individual Titled in one person’s name only
joint Jointly titled (e.g., JTWROS). Only meaningful when married
community Community property under state law. Only meaningful when married AND in a community-property state (AZ, CA, ID, LA, NV, NM, TX, WA, WI)

When client.personal_info.married == false, every asset’s ownership MUST be individual. joint and community are rejected for unmarried clients.

For married couples with ownership: community or ownership: joint, the API attributes the asset’s contribution symmetrically — 50/50 between spouses — across all asset types and income categories. This matters for Social Security calculations, tax filing, and the income statement.

7.6 Separate property

client_separate_allocatable_wealth and spouse_separate_allocatable_wealth are only permitted when:

For MFJ or unmarried clients, both separate-wealth fields must be absent or zero. Their sum must not exceed household_allocatable_wealth. Violation surfaces as rule: separate_property_only_when_separate_returns.


8. Validation and error handling

Every non-2xx response uses the same envelope:

{
  "status": "error",
  "error": "validation_failed",
  "message": "Request failed validation (1 issue). Your card has NOT been charged.",
  "details": [
    {
      "field": "client.financial_info.max_premium",
      "message": "must be > 50000",
      "rule": "schema.exclusiveMinimum"
    }
  ]
}

8.1 HTTP status codes

Code When it is returned
400 Malformed JSON, schema violation, array-item rule violation, cross-field rule violation, or RentCast/VIN-decoder asset-validation failure
401 Invalid X-User-Token (only when that header was supplied)
402 Missing / invalid / expired / already-captured X-Payment-Intent
500 Internal server error during optimization

A missing auth header on /allocator surfaces as 402, not 401, because pay-per-call is the default mode.

8.2 The three rule families

The rule field in details[] falls into exactly one of three families.

  1. schema.<keyword> — JSON Schema violation. <keyword> is the schema keyword that failed: required, type, minimum, maximum, exclusiveMinimum, exclusiveMaximum, enum, pattern, minItems, minLength, etc.

  2. array_item_rules.<array_name> — conditional rule on an array item. Examples: array_item_rules.real_estate (a Multi-Family property with units outside 2–4), array_item_rules.annuity (a life: false annuity without term_years).

  3. Named cross-field rules:

8.3 Example error responses

Missing required field:

{
  "status": "error",
  "error": "validation_failed",
  "message": "Request failed validation (1 issue). Your card has NOT been charged.",
  "details": [
    { "field": "client.financial_info",
      "message": "must have required property 'max_premium'",
      "rule": "schema.required" }
  ]
}

Out-of-range:

{
  "status": "error",
  "error": "validation_failed",
  "message": "Request failed validation (1 issue). Your card has NOT been charged.",
  "details": [
    { "field": "client.financial_info.max_premium",
      "message": "must be > 50000",
      "rule": "schema.exclusiveMinimum" }
  ]
}

Conditional array rule:

{
  "status": "error",
  "error": "validation_failed",
  "message": "Request failed validation (1 issue). Your card has NOT been charged.",
  "details": [
    { "field": "client.financial_info.real_estate[0].units",
      "message": "units must be between 2 and 4 for Multi-Family",
      "rule": "array_item_rules.real_estate" }
  ]
}

California residency:

{
  "status": "error",
  "error": "validation_failed",
  "message": "Request failed validation (1 issue). Your card has NOT been charged.",
  "details": [
    { "field": "client.personal_info.address.state",
      "message": "Client state must be 'CA' (service is California-only).",
      "rule": "california_residency" }
  ]
}

Invalid token:

{
  "status": "error",
  "error": "Unauthorized",
  "message": "Invalid or missing X-User-Token"
}

Missing payment intent:

{
  "status": "error",
  "error": "Payment Required",
  "message": "X-Payment-Intent header required for pay-per-call requests."
}

8.4 Card-safety semantics for pay-per-call

This design ensures a pay-per-call user is never left with a pending hold on their card, and never sees a charge they did not receive results for.


9. Response payload: field-by-field

A successful /allocator response is a single JSON object with the following top-level keys.

Null values. Any field that is not applicable to a particular response — spouse-side fields when the client is unmarried, total-portfolio statistics when the household is insolvent (see §9.3), or any computed quantity the model declines to report because it would be misleading at a boundary — is returned as JSON null. Treat null as “not applicable” and guard before arithmetic or formatting (e.g., (value ?? 0).toFixed(...) rather than value.toFixed(...)).

9.1 client_personal_information

Echo of identifying fields so callers (and their logs) can correlate requests and responses without retaining the full request body.

{
"client_first_name": "John",
"client_last_name": "Smith",
"client_email_address": "[email protected]"
}

9.2 household_max_premium, household_gamma

{
  "household_max_premium": 143000,
  "household_gamma": 5.93
}

9.3 household

{
"total_allocatable_wealth":    1000000,
"total_background_wealth":  10828578.74,
"total_wealth":             11828578.74,
"optimal_allocation": {
  "empirical_global_market_portfolio": 1,
  "risk_free_asset":                   0
},
"optimal_portfolio": {
  "allocatable": {
    "before_tax": {
      "expected_return": 0.213,
      "std_dev":         0.1182,
      "sharpe_ratio":    1.4975
    },
    "after_tax": {
      "expected_return": 0.189,
      "std_dev":         0.1113,
      "sharpe_ratio":    1.4987
    }
  },
  "total": {
    "before_tax": {
      "expected_return": 0.1012,
      "std_dev":         0.0695,
      "sharpe_ratio":    0.939
    },
    "after_tax": {
      "expected_return": 0.0624,
      "std_dev":         0.0688,
      "sharpe_ratio":    0.5864
    }
  }
}
}

9.4 empirical_global_market_portfolio_contents

An array of exactly 14 items (the EGMP-14 components), ordered by descending weight. Each item has these fields:

The specific tickers, asset-class labels, and weights are returned at runtime in the /allocator response and are not enumerated here.

9.5 optimal_risk_free_allocation

{
"household": {
  "name": "Risk-Free Asset",
  "household_allocation": 0,
  "amount_to_invest": 0,
  "description": "Optimal household allocation to risk-free asset"
}
}

The implementation choice (short-dated Treasury, money-market fund, savings account, CD ladder, etc.) is left to the user. The Methodology Disclosure describes the assumptions in detail.

9.6 strategy_recommendations

A map of strategy-display-name → strategy object. Which strategies appear depends on the household profile: a household with no business income will not receive Solo 401(k) or Mega Backdoor Roth recommendations; a household over the MAGI phase-out will receive Backdoor Roth instead of Roth IRA; and so on.

"strategy_recommendations": {
  "Roth IRA": {
    "short_description": "Individual retirement account funded with after-tax dollars; qualified withdrawals of earnings are tax-free. 2026 contribution limit is $7,500 ($8,600 with catch-up for age 50+).",
    "long_description": "https://www.ria.us/strategies/Roth_IRA.html",
    "rationale": "Provides tax-free growth and tax-free withdrawals in retirement. No RMDs during the original owner's lifetime.",
    "risk_factors": [
      "Income phase-out for 2026: $153,000-$168,000 (single), $242,000-$252,000 (MFJ)",
      "5-year holding period required for qualified withdrawals"
    ],
    "irs_citation": "IRC §408A; SECURE 2.0 Act (catch-up now indexed to inflation)"
  },
  "Tax Loss Harvesting": { ... },
  "1031 Exchange": { ... }
}

Each strategy object has:

Field Type Notes
short_description string Summary with 2026 statutory limits
long_description string (URL) Link to the long-form article on ria.us
rationale string Why it applies to this household
risk_factors string OR array May be a single string or an array of strings
irs_citation string (optional) Code section, regulation, etc.

9.7 balance_sheet

A Merton-framework economic balance sheet (not GAAP). Assets include the present value of future income streams; the single liability is the present value of future expenses.

"balance_sheet": {
  "assets": [
    { "name": "Market",                  "type": "Allocatable Wealth", "value": 1000000 },
    { "name": "Riskless",                "type": "Allocatable Wealth", "value": 0 },
    { "name": "Total Allocatable Wealth", "type": "Allocatable Wealth", "value": 1000000, "is_subtotal": true },
    { "name": "Traditional 401(k)s", "type": "Retirement Savings", "value": 284365.15 },
    ...
    { "name": "Social Security Net PV", "type": "Capitalized Income Streams",
      "subcategory": "Social Security & Medicare", "value": 320406.95 },
    ...
  ],
  "liabilities": [
    { "name": "Total Annual Household Expenses PV", "type": "Expenses", "value": 464886.59 }
  ],
  "net_worth": 11724078.74
}

Asset type values:

Type Contents
Allocatable Wealth Market, Riskless, and a total
Retirement Savings IRA and 401(k) balances, by sub-type, plus a total
Capitalized Income Streams Human capital PV, business capital PV, SS & Medicare & SSI, pensions, annuities, other income PV
Tangible Background Assets Real-estate equity, vehicle equity, and totals
Other Background Assets Bank CDs, life-insurance net surrender value, other_background_wealth

net_worth equals total_wealth from the household block, to rounding.

Subtotal rows carry "is_subtotal": true. Any entry whose name begins with “Total” — in either balance_sheet.assets[] or income_statement.income[] (see §9.9) — has is_subtotal: true. Examples include Total Allocatable Wealth, Total Human Capital, Total Social Security & Medicare, Total Retirement Accounts, Total Human Capital Income, and Total Earned Income. These rows are duplicates of the within-category sum and are included for display convenience. Consumers computing sum(assets[*].value) (or sum(income[*].annual_amount)) should skip rows where is_subtotal === true to avoid double-counting; net_worth and net_income are computed without them.

9.8 retirement_accounts_by_type

Household totals by retirement-account type, summed across client and spouse. All seven keys are always present; types the household does not have are returned as 0.

{
"traditional_ira":  187500,
"roth_ira":         134000,
"sep_ira":           40000,
"simple_ira":        22000,
"traditional_401k": 284365.15,
"roth_401k":         28000,
"solo_401k":         12000
}

9.9 income_statement

"income_statement": {
  "income": [
    { "name": "High-skill Employment Income", "type": "Earned Income",
      "subcategory": "Human Capital", "annual_amount": 184000 },
    ...
    { "name": "Pension Income",         "type": "Retirement Income", "annual_amount": 173739.60 },
    { "name": "Social Security Income", "type": "Retirement Income", "annual_amount":   4380 },
    { "name": "RMD Income",             "type": "Retirement Income", "annual_amount":   8228.23 },
    { "name": "SSI Income",             "type": "Public Benefits",
      "annual_amount": 14844,
      "note": "Supplemental Security Income (federal FBR + CA SSP); non-taxable per 42 U.S.C. § 1382a" },
    { "name": "Investment Income",      "type": "Asset-Based Income","annual_amount":  62518.78 },
    { "name": "Imputed Rent (Owner-Occupied)", "type": "Asset-Based Income",
      "annual_amount": 81960, "note": "Non-taxable economic benefit of owner-occupied housing" }
  ],
  "expenses": [
    { "name": "Total Annual Real Estate Expenses",     "type": "Recurring",
      "annual_amount": 119551.50 },
    { "name": "Total Annual Mortgage Payments",        "type": "Recurring",
      "annual_amount": 16800 },
    { "name": "Total Annual Vehicle Loan Payments",    "type": "Recurring",
      "annual_amount": 4200 },
    { "name": "Total Annual Life Insurance Premiums",  "type": "Recurring",
      "annual_amount": 1000 },
    { "name": "Total Annual Household Expenses",       "type": "Recurring",
      "annual_amount": 12000 }
  ],
  "net_income": 189275.12,
  "future_income": [
    {
      "name": "Deferred Annuity Income (Future)",
      "type": "Deferred",
      "annual_amount": 1790.85,
      "deferred_cola_portion": 1790.85,
      "deferred_joint_survivor_portion": 1790.85,
      "starts_in_years": 10,
      "note": "Not included in current income; will begin after deferral period"
    },
    {
      "name": "Anna Social Security",
      "type": "Future Retirement Income",
      "annual_amount": 9938.70,
      "starts_at_age": 67,
      "starts_in_years": 32,
      "starts_in_year": 2058,
      "benefit_type": "spousal",
      "note": "Projected Social Security benefit; capitalized PV is on balance sheet"
    },
    {
      "name": "Ben Social Security",
      "type": "Future Retirement Income",
      "annual_amount": 24648.03,
      "starts_at_age": 67,
      "starts_in_years": 30,
      "starts_in_year": 2056,
      "benefit_type": "own_worker",
      "note": "Projected Social Security benefit; capitalized PV is on balance sheet"
    }
  ]
}

Income type values: Earned Income, Asset-Based Income, Retirement Income, Public Benefits, Other Income. Expense type: Recurring. Future-income type values: Deferred (deferred annuities) and Future Retirement Income (projected SS benefits for pre-retirement clients/spouses).

Public Benefits covers means-tested cash assistance — Supplemental Security Income (SSI) per 42 U.S.C. § 1381 et seq., including the California State Supplementary Payment (SSP) for aged / blind / disabled recipients per CA W&I Code § 12000 et seq. SSI is non-taxable per 42 U.S.C. § 1382a and therefore does not enter the tax-rate calculation, but does enter net_income (it is real economic income). Eligibility is gated on the SSI resource limit ($2,000 individual / $3,000 couple as of 2025, unindexed since 1989) and on counted income after the $20/month general disregard and the $65/month + ½-remainder earned-income disregard. The line is omitted by nonzero_filter when the client does not qualify; the entire Public Benefits section disappears in that case.

For each pre-retirement client or spouse who has a positive projected SS benefit but is not yet claiming, a Future Retirement Income entry is emitted with the projected annual_amount, the starts_at_age (the claiming age), the implied starts_in_years and starts_in_year, and a benefit_type of either own_worker or spousal. SSDI cases (is_disabled_for_ss_purposes: true) are excluded because their claiming age equals current age — they appear in income[] as currently received Social Security Income. The Social Security present value remains on balance_sheet.assets[] under Social Security Net PV in either case; the future_income entries are explanatory.

future_income[] is intentionally not summed into net_income — deferred annuity and similar future streams have not started yet.

9.10 data_quality_warnings

Present only when the API detected non-fatal issues worth surfacing (e.g., two employments with the same employer_name + employee_id across client and spouse — the duplicate is deduplicated server-side, but the user should know). When no issues are found, this field is absent (not {}).

{
"employment_business": {
  "warnings": [
    "Duplicate employment detected across client and spouse and deduplicated."
  ],
  "duplicate_businesses": false,
  "duplicate_employments": true
}
}

9.11 payment_warning

Present only in the rare successful-computation / failed-capture case for pay-per-call:

"Payment capture failed after two attempts. You have NOT been charged for this request; your card authorization has been cancelled. Your allocation results are included in full. If you wish to be billed for these results, please contact [email protected] and reference your request timestamp."

10. Worked end-to-end examples

10.1 Pay-per-call worked example

The full flow from a single, unmarried California client’s perspective.

Step 1 — (browser) create a PaymentMethod.

In the browser, using Stripe Elements and your publishable key, call stripe.createPaymentMethod({ type: 'card', card, billing_details: {...} }) and keep the resulting paymentMethod.id (pm_...).

Step 2 — authorize the PaymentIntent.

Send the pm_... to /create-payment-intent:

PM="pm_1PExAmPlEtOkEn123"

curl -sS -X POST https://api.ria.us/create-payment-intent \
  -H "Content-Type: application/json" \
  -d "{\"payment_method_id\":\"${PM}\"}" \
  | tee /tmp/pi_response.json | jq

Response:

{
  "status": "success",
  "payment_intent_id": "pi_3NXyZ1AbCdEfGhIj0KlMnOpQ",
  "client_secret":     "pi_3NXyZ1AbCdEfGhIj0KlMnOpQ_secret_AbCdEfGh",
  "amount": 79.95
}

Step 3 — (browser) confirm the PaymentIntent.

Call stripe.confirmCardPayment(clientSecret) in the browser. If the card triggers 3-D Secure, Stripe.js shows the challenge UI; otherwise, the confirmation resolves immediately. Extract the payment_intent_id for the next step.

Step 4 — build the household JSON.

Save this as henry.json:

{
  "client": {
    "personal_info": {
      "name": { "first_name": "Henry", "last_name": "Mason" },
      "address": {
        "street_number": "4248",
        "street_name": "La Salle Ave",
        "city": "Culver City",
        "state": "CA",
        "zip_code": "90232"
      },
      "email_address": "[email protected]",
      "dob": "1930-06-18",
      "gender": "male",
      "married": false,
      "phone": { "home_phone": "310-839-0358", "mobile_phone": "310-776-2415" }
    },
    "financial_info": {
      "max_premium": 150000,
      "household_allocatable_wealth": 2131650.27,
      "tax_filing_status": "single",
      "quarters_of_coverage": 88,
      "ssa_earnings_record": [
        { "year": 1974, "earnings": 2044 },
        { "year": 1975, "earnings": 1407 },
        { "year": 1980, "earnings": 1306 },
        { "year": 1981, "earnings": 1796 }
      ],
      "actual_monthly_benefit": 365,
      "actual_claiming_age": 65,
      "optimize_claiming": false,
      "retirement": {
        "defined_contribution_plans": {
          "accounts_401k": [
            {
              "account_id": "401K-778899",
              "account_type": "traditional",
              "employer": "County of Podunk",
              "custodian": "Podunk Insurance Company",
              "balance": 73231.29
            }
          ]
        },
        "defined_benefit_plans": {
          "pensions": [
            {
              "pension_id": "RETPEN",
              "plan_type": "defined_benefit",
              "type": "government",
              "employer": "County of Coconut",
              "administrator": "Coconut Administrator",
              "status": "retired",
              "annual_amount": 126600.48
            },
            {
              "pension_id": "MAN",
              "plan_type": "defined_benefit",
              "type": "government",
              "employer": "U.S. Armed Forces",
              "administrator": "Uncle Sam",
              "status": "retired",
              "annual_amount": 24998.40
            },
            {
              "pension_id": "Chain Gang",
              "plan_type": "defined_benefit",
              "type": "government",
              "employer": "State of Happiness",
              "administrator": "Happy Administration",
              "status": "retired",
              "annual_amount": 22140.72
            }
          ]
        }
      },
      "real_estate": [
        {
          "address": "4248 La Salle Ave",
          "city": "Culver City",
          "state": "CA",
          "zip": "90232",
          "type": "Single Family",
          "bedrooms": 5,
          "bathrooms": 5,
          "sqft": 2627,
          "mortgage_balance": 100000,
          "purchase_price": 60000,
          "purchase_year": 1974,
          "purchase_month": 3,
          "monthly_payment": 850,
          "ownership": "individual",
          "owner_occupied": true
        }
      ],
      "vehicles": [
        {
          "vin": "1N4AL3AP4EC144655",
          "mileage": 70978,
          "purchase_price": 17000,
          "purchase_year": 2015,
          "purchase_month": 3,
          "amount_owed": 4500,
          "monthly_payment": 350,
          "ownership": "individual"
        }
      ]
    }
  }
}

Step 5 — call /allocator:

PI="pi_3NXyZ1AbCdEfGhIj0KlMnOpQ"

curl -sS -X POST https://api.ria.us/allocator \
  -H "Content-Type: application/json" \
  -H "X-Payment-Intent: ${PI}" \
  -d @henry.json \
  | tee henry_response.json \
  | jq '{ gamma: .household_gamma,
          alloc: .household.optimal_allocation,
          total_wealth: .household.total_wealth }'

Condensed response:

{
  "gamma": 5.93,
  "alloc": {
    "empirical_global_market_portfolio": 1,
    "risk_free_asset": 0
  },
  "total_wealth": 11828578.74
}

The card is captured ($79.95) automatically on the 200 response.

10.2 RIA Client worked example (married couple)

export ALLOCATOR_TOKEN="asys_sk_live_your_token_here"

curl -sS -X POST https://api.ria.us/allocator \
  -H "Content-Type: application/json" \
  -H "X-User-Token: ${ALLOCATOR_TOKEN}" \
  -d @smith_household.json \
  | jq '{
      gamma:       .household_gamma,
      alloc:       .household.optimal_allocation,
      metrics:     .household.optimal_portfolio,
      rfa:         .optimal_risk_free_allocation.household,
      accounts:    .retirement_accounts_by_type,
      net_worth:   .balance_sheet.net_worth,
      net_income:  .income_statement.net_income
    }'

A typical output looks like:

{
  "gamma": 5.93,
  "alloc": {
    "empirical_global_market_portfolio": 1,
    "risk_free_asset": 0
  },
  "metrics": {
    "allocatable": {
      "before_tax": {
        "expected_return": 0.213,
        "std_dev":         0.1182,
        "sharpe_ratio":    1.4975
      },
      "after_tax": {
        "expected_return": 0.189,
        "std_dev":         0.1113,
        "sharpe_ratio":    1.4987
      }
    },
    "total": {
      "before_tax": {
        "expected_return": 0.1012,
        "std_dev":         0.0695,
        "sharpe_ratio":    0.939
      },
      "after_tax": {
        "expected_return": 0.0624,
        "std_dev":         0.0688,
        "sharpe_ratio":    0.5864
      }
    }
  },
  "rfa": {
    "name": "Risk-Free Asset",
    "household_allocation": 0,
    "amount_to_invest":     0,
    "description": "Optimal household allocation to risk-free asset"
  },
  "accounts": {
    "traditional_ira":  187500,
    "roth_ira":         134000,
    "sep_ira":           40000,
    "simple_ira":        22000,
    "traditional_401k": 284365.15,
    "roth_401k":         28000,
    "solo_401k":         12000
  },
  "net_worth":  11724078.74,
  "net_income":    592352.82
}

(To inspect the portfolio components themselves, run jq '.empirical_global_market_portfolio_contents' smith_response.json against the full response — those fields are intentionally left out of the summary block above.)

A full married-couple request payload (with two earners, two businesses, all retirement-account types, a deferred joint-life annuity with COLA, life insurance, real estate, a vehicle, and a bank CD) is inlined in the married_couple example in openapi.yaml — import that file into Postman to get a ready-to-send request.

10.3 Python example — end-to-end

#!/usr/bin/env python3
"""
Minimal ALLOCATOR client. Reads a household payload from household.json,
calls /allocator with an X-User-Token, prints the key summary figures.
"""
import json
import os
import sys
import requests

BASE = "https://api.ria.us"
TOKEN = os.environ["ALLOCATOR_TOKEN"]    # export ALLOCATOR_TOKEN=...

def allocator(payload: dict) -> dict:
    r = requests.post(
        f"{BASE}/allocator",
        headers={
            "Content-Type": "application/json",
            "X-User-Token": TOKEN,
        },
        json=payload,
        timeout=150,  # SLA p99 is 120s; give a small cushion
    )
    if r.status_code >= 400:
        # Structured error envelope is documented; surface it cleanly
        try:
            err = r.json()
        except ValueError:
            r.raise_for_status()
        raise RuntimeError(
            f"HTTP {r.status_code}: {err.get('error')}: {err.get('message')}\n"
            + json.dumps(err.get("details", []), indent=2)
        )
    return r.json()

def main(path: str) -> None:
    with open(path) as f:
        payload = json.load(f)
    resp = allocator(payload)

    alloc = resp["household"]["optimal_allocation"]
    print(f"γ (risk aversion)                    : {resp['household_gamma']:.3f}")
    print(f"Total wealth                          : ${resp['household']['total_wealth']:,.2f}")
    print(f"Allocatable wealth                    : ${resp['household']['total_allocatable_wealth']:,.2f}")
    print(f"Market / risk-free split              : {alloc['empirical_global_market_portfolio']:.2%} / {alloc['risk_free_asset']:.2%}")
    print(f"Allocatable Sharpe (after-tax)        : {resp['household']['optimal_portfolio']['allocatable']['after_tax']['sharpe_ratio']:.4f}")
    print(f"Allocatable Sharpe (before-tax)       : {resp['household']['optimal_portfolio']['allocatable']['before_tax']['sharpe_ratio']:.4f}")
    print(f"Risk-free rate (before / after tax)   : {resp['risk_free_rate']['before_tax']:.4f} / {resp['risk_free_rate']['after_tax']:.4f}")
    print("\nTop 3 EGMP-14 holdings:")
    for comp in resp["empirical_global_market_portfolio_contents"][:3]:
        print(f"  {comp['ticker']:<5s}  ({comp['proxy_for']:<25s})  "
              f"${comp['household_amount_to_invest']:>12,.2f}  ({comp['weight']:.2%})")

    warnings = resp.get("data_quality_warnings")
    if warnings:
        print("\nData-quality warnings present — review before acting:")
        print(json.dumps(warnings, indent=2))

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Usage: allocator.py household.json", file=sys.stderr)
        sys.exit(2)
    main(sys.argv[1])

Save as allocator.py, then:

export ALLOCATOR_TOKEN="asys_sk_live_your_token_here"
python3 allocator.py household.json

10.4 Postman quickstart

  1. Open Postman.
  2. Import → Linkhttps://api.ria.us/openapi.json → Continue.
  3. In the imported collection, set a collection variable ALLOCATOR_TOKEN to your token value.
  4. Under the /allocator request, select the Auth tab and either:
  5. Pick the married_couple example body from the request Examples dropdown.
  6. Send.

10.5 Two ways to read the Swagger UI “Try it out”

At https://api.ria.us/docs:

  1. Paste an X-User-Token into the “Authorize” prompt at the top. (The UI does not know about X-Payment-Intent; pay-per-call must go through the /create-payment-intent flow described above.)
  2. Expand /allocator, click Try it out, paste a valid request body (or keep the pre-filled example), click Execute.
  3. The rendered response includes the full JSON plus the exact curl command that was issued.

11. Pricing and billing

From User Agreement §5.1: “User shall pay the per-API-Use fees set forth in Schedule A to this Agreement.” The current Schedule A is summarized below.

11.1 What counts as an “API Use”

From User Agreement §1.3: each individual request made to the API (whether via programmatic API call or web interface submission) that results in a response, whether successful or unsuccessful due to data errors (excluding system errors caused by Company infrastructure).

In practice:

11.2 RIA Clients — graduated monthly tiers

From User Agreement §5.2(a) and Schedule A:

Each successful /allocator call is recorded as a Stripe metered-usage event against your Stripe customer record. The per-call price is determined by your cumulative successful-call count for the calendar month:

Successful API Uses in calendar month Fee per API Use in that tier
1 through 25 $34.95
26 through 100 $27.95
101 and above $21.95

Worked example. Sixty (60) successful API Uses in a calendar month: (25 × $34.95) + (35 × $27.95) = $873.75 + $978.25 = $1,852.00 for the month, exclusive of taxes. Tier boundaries do not roll over to the next calendar month.

Per-token rate limit: 20 requests per rolling 60-minute window. Requests in excess of this limit receive a 429 response and are not billed.

11.3 Individual Clients — pay-per-call

From User Agreement §5.2(b) and Schedule A:

11.4 Price changes

Per User Agreement §5.5 — pricing changes (including modifications to Schedule A) require 60 days’ written notice. Continued use after the effective date constitutes acceptance.

11.5 No conflicts, no third-party compensation

From User Agreement §10: Advisory API Systems receives no revenue-sharing, distribution fees (12b-1), placement fees, or any other compensation from any ETF sponsor or fund family. The sole source of revenue is the per-use fees set forth in Schedule A.


12. Service Level Agreement summary

The authoritative document is the Service Level Agreement. Key commitments:

12.1 Uptime

99.5% Monthly Uptime target. Service Credits on shortfall, capped at 100% of the month’s fees:

Monthly Uptime Service Credit
99.0% – 99.49% 10%
95.0% – 98.99% 25%
90.0% – 94.99% 50%
Below 90.0% 100%

Service Credits must be requested via [email protected] within 30 days of the affected month, include the API Key identifier (last 8 chars only), the affected Pacific Time windows, and the impact.

12.2 Response time targets

Metric Target Measurement
Median < 40 seconds 50th percentile
p95 < 100 seconds 95th percentile
p99 < 120 seconds 99th percentile

Complex households (extensive background assets, large SSA earnings records) tend toward the upper end of this distribution. Set client-side timeouts to at least 150 seconds to leave headroom.

12.3 Error-rate target

Less than 1% HTTP 5xx attributable to Company infrastructure.

12.4 Support

Channel Availability Response target
Email ([email protected]) 24/7 submission Next business day
Documentation / OpenAPI 24/7 self-service

Issue classification and targets:

Priority Description Initial Response Resolution Target
P1 Critical Complete outage / data corruption for all customers 1 hour 4 hours
P2 High Major functionality impaired; no workaround 4 hours 1 business day
P3 Medium Functionality impaired; workaround available 1 business day 5 business days
P4 Low Minor issues, questions, feature requests 2 business days Best effort

Business hours are Monday–Friday, 09:00–17:00 Pacific, excluding US federal holidays.

When reporting issues, include the API Key identifier (last 8 chars only — never the full token), request / response examples, timestamps in Pacific Time, and reproduction steps.


13. Security requirements

Summarized from the Security Practices Documentation; the full text is authoritative.

13.1 Transport

13.2 Token handling

13.3 Data handling

13.4 Rate limiting and abuse


14. Common pitfalls and FAQ

14.1 “My max_premium: 50000 is being rejected.”

max_premium is strictly bounded: 50000 < max_premium < 300000. Exactly 50000 or exactly 300000 is rejected. Use a value in between.

14.2 “I’m married but submitted just client — validation says spouse is required.”

When client.personal_info.married == true, a spouse block with personal_info and financial_info (including that spouse’s own max_premium, tax_filing_status, and quarters_of_coverage) is required. The spouse’s ssa_earnings_record is optional (same rules as the client side).

14.3 “I’m unmarried but my allocation includes community or joint ownership — is that allowed?”

No. When married == false, every ownership must be individual. The validator will reject the request with rule: marital_status_consistency.

14.4 “My Multi-Family property has 5 units.”

Multi-Family is 2–4 units. A 5-unit building is Apartment. Fix the type field.

14.5 “I’m a landlord but I don’t live in any of my properties.”

Set every property’s owner_occupied = false and set the top-level rents_residence = true on the person whose residence is rented. Include the annual rent in household_total_annual_expenses.

14.6 “Why is my deferred annuity missing from income_statement.income?”

By design. Deferred annuities go into income_statement.future_income[] with starts_in_years. They are not summed into current income[] or net_income. Immediate annuities (deferral_period: 0 or absent) do show up in current income.

14.7 “I want a solo_401k but I only have W-2 employment.”

solo requires business income. If your household has no entry in client.financial_info.businesses[] (and no spouse-side business either), don’t mark a 401(k) as solo.

14.8 “What about SECURE 2.0 catch-up for high earners?”

The strategy recommendations flag mandatory-Roth catch-up for prior-year wages > $150,000 (SECURE 2.0 Act §603), but the API treats account classification exactly as you submit it. Classify the catch-up contributions into a Roth 401(k) entry if that’s where they land.

14.9 “Are WEP and GPO still affecting my SSA benefit estimate?”

No. The Social Security Fairness Act (signed January 5, 2025) eliminated both the Windfall Elimination Provision and the Government Pension Offset. Supply your SSA-covered earnings as they appear on your SSA statement — the statement already excludes non-covered earnings, and there is no WEP or GPO adjustment in the API any more.

14.10 “My home is worth way more than I paid for it. Which number goes in purchase_price?”

The original purchase price. That is your cost basis. Current market value is determined by the API from the address, property type, and purchase-price / time combination.

14.11 “Do I include property taxes and homeowner’s insurance in monthly_payment?”

No. monthly_payment is principal + interest only. Taxes, homeowners insurance, HOA, utilities, maintenance, and reserves are all estimated automatically from property type and current market value. Adding them would double-count.

14.12 “Am I supposed to send my Social Security Number?”

social_security_number is optional and never used in the optimization. The firm retains it only when supplied, for client-file completeness. If you don’t need it on file, omit it.

14.13 “The response is huge. Can I ask for less?”

Not via a shape flag. Extract only the fields you care about client-side (as in the jq and Python examples above). The response shape is documented in openapi.yaml so you can write a strongly-typed deserializer that only binds the fields you need.

14.14 “My /allocator call hung for 90 seconds before returning.”

That’s within normal SLA targets for a complex household. Set HTTP client timeouts to at least 150 seconds.

14.15 “Can I reuse a pi_... from a previous successful call?”

No. Each X-Payment-Intent is single-use. The second attempt returns 402 Payment Required with "X-Payment-Intent is invalid, expired, or already captured.". Create a fresh PaymentIntent via /create-payment-intent for each call.

14.16 “How do I batch multiple households?”

Make sequential calls, one per household. Each call is a full household optimization — there is no batched endpoint.

14.17 “Which OpenAPI tooling works best?”

14.18 “I submitted my data through the web form; can I replay it via the API?”

Yes. Every request the web form submits goes to POST /allocator with the same JSON body shape documented here. Browser devtools → Network tab → right-click the request → Copy → Copy as cURL will give you a working shell command (subject to your auth being in range).

14.19 “Can I save my web form inputs and reuse them later?”

Yes. The web form’s JSON tab has two buttons:

The downloaded file is a valid /allocator request body. You can:

For RIA Clients running optimizations across many households, keeping a per-client snapshot file alongside your other client records makes annual reviews substantially faster — at the next review you only need to update the fields that have changed (asset values, ages, recent earnings, etc.) rather than re-entering Social Security earnings histories, real estate details, and pension parameters from scratch.


15. Support

When contacting support with an incident, include:


Document version: aligned with ALLOCATOR℠ API v3.2.0 and User Agreement v2.3 (April 2026). In case of conflict between this guide and either the OpenAPI specification at /openapi.json or the User Agreement, the authoritative document controls.