API REFERENCE

View as Markdown

Beta

SAMI v2 Term Conventions

SAMI is PitchPoint’s own format for describing a loan. A loan is submitted as an array of terms. The same array shape is used by Stage Data, Start a Workflow and Reconfirm a Workflow, and it is echoed back on Read a Workflow.

The four conventions below trap almost every first integration. None of them is enforced by the schema, so getting one wrong generally produces a submission that is quietly missing information rather than an error you can see. Key casing is the exception and is forgiven; the rest are not.

Build a Loan Step by Step describes the common fields, and Value Vocabularies lists the values for the enum-looking ones. Neither is the full catalog. Ask your Pitchpoint Account Representative for it if you need every field a branch accepts.

A term is an object with exactly one key

That key names which of the eleven kinds of fact it carries; its value holds the fields.

{ "person": { "firstName": "Jane", "lastName": "Sample" } }

Never two keys, and never zero. {"person":{…},"property":{…}} and {} each fail the whole request with VALIDATION_ERROR. A misspelled branch next to a real one, such as {"participant": {…}, "propety": {…}}, counts as two keys. Two borrowers are two person terms, not one term with two people in it.

The message gives the term’s index and its key count, not the keys:

{
  "messages": [
    {
      "code": "VALIDATION_ERROR",
      "text": "every element of terms must carry exactly one term key; terms[0] carries 2",
      "timestamp": "2026-09-24T19:54:08.112Z"
    }
  ]
}
Branch key Carries
person A borrower or co-borrower. Every other person on the loan is a participant.
property The loan’s property, with its address and valuations. Products that analyze a property, such as FEMA flood, target it.
participant A party to the transaction other than a borrower.
company A business that is itself a subject of the loan, used mainly by the bank-account monitoring products.
loanTerms The loan’s headline terms.
loanToValue LTV and CLTV ratios.
transactionDetail Purchase price and related figures.
loanDetail Further loan attributes.
refinance Refinance-specific facts.
housingExpense One housing expense. Repeat it, one per expense type.
closing Closing details.

Order carries no meaning, and no term references another — there are no identifiers to wire up between them. The one exception: when a branch that should appear once appears twice, the last one is kept (see below).

Which branches may repeat

Any branch may appear more than once, but only some should.

Branch Repeat?
person, participant, company Yes. One term per party.
housingExpense Yes, and it is meant to — one term per expense type.
loanTerms, loanToValue, transactionDetail, loanDetail, refinance, closing Send at most one. See below.
property Send at most one. It is the loan’s subject property. A borrower’s other real estate goes in that person’s properties.

A second loan-level term, or a second property term, replaces the first. Nothing rejects it, and the last one in the array is the one recorded.

Convention 1: key casing

Keys are camelCase: lower-case first letter, and an acronym anywhere else keeps its capitals.

Key Not  
firstName   the ordinary case
countyFIPSCode countyFipsCode the acronym stays upper
noteRatePercent    

Six keys are irregular. They are the complete list — there are no others:

ssn · dob · yob · ein · ltvRatioPercent · cltvRatioPercent

The rule governs keys only. Values are documented in their own case, so an identifier type is documented as SSN or EIN. Whether a value’s case matters depends on the field; see Value Vocabularies.

Casing is not enforced. firstname and FIRSTNAME both land on firstName, and a read gives the value back under the canonical spelling. The spellings above are what the model uses and what you get back — not what the service matches on.

The branch key is stricter than the fields inside it. Person is accepted where PERSON is refused, and an unrecognized branch key fails the whole submission rather than being quietly dropped — see What is actually validated below.

Convention 2: arrays are flat

Repeated things are plain JSON arrays. There is no container object wrapping them.

"residences": [ { "…": "…" } ]

not

"residences": { "residence": [ { "…": "…" } ] }

This applies to residences, employers, properties, bankAccounts, licenses, identifiers, consentTokens, valuations, courtDetails and items.

An empty array means exactly what an omitted key means, and a read gives you the omission rather than []. terms is the one exception: it is the only array whose bounds are enforced, and an empty one is rejected.

Convention 3: numbers and dates are strings

Kind Form Example
Every value that is not a boolean, including amounts, counts and rates JSON string "450000.00", "6"
Percentages as a percent, not a fraction "6.375", never "0.06375"
Dates mm/dd/yyyy, not ISO-8601 "01/31/1980", never "1980-01-31"
Any field ending Indicator, plus monitorCompany a real JSON boolean true. "true" is read as the boolean, but "yes" fails the submission
presentHousingExpense a JSON boolean; any other value is read as JavaScript reads it true. See below

presentHousingExpense accepts any value. Send true for a housing expense the borrowers pay now, and false or nothing for one they will pay once the loan closes. Any other value is read the way JavaScript reads it in an if. null, 0 and the empty string "" mean proposed, like false and an omitted field. Every other value means present, including " ", "false", "0" and "NULL", any non-zero number, and any object or array, even {} or []. The flag reads back as true or false.

A misformatted date is accepted, and quietly altered. Dates are not validated as you stage or start a workflow. An ISO-8601 date is taken without complaint and reads back as 1980/1/31, and a value that cannot be read as a date at all is dropped, leaving the field absent. Neither is reported to you, and both surface later as a failure or a wrong answer far from the request that caused them. Read the loan back after staging if you want to see what was kept.

Convention 4: fields that look like enums are free text

Fifteen fields read like closed vocabularies and are not: participantType, propertyPurpose, propertyUsage, propertyType, valuationType, identifierType, loanPurpose, mortgageType, housingExpenseType, the three refinance types, and the three on a borrower’s declarations. Any string is accepted, and an unrecognized value is never rejected or reported. It is not kept as sent, though: when the loan is recorded, it becomes Other, a default, or nothing at all, depending on the field.

They are deliberately not modelled as enums so that a generated client cannot refuse a value this service accepts. Send the documented values for a field. The list is not enforced when you submit, but it is what the loan is recorded against. Several of these fields have a companion …OtherDescription field for the case where no documented value fits. Value Vocabularies lists the documented values for each one.

An unrecognized contact point type loses more than the field. The field accepts only Phone, Email, Fax or Other, ignoring case and surrounding whitespace. Any other value discards the whole contact point, and the request still succeeds. See Contact points.

Correlation ids on terms

A party term can carry its own correlationId: your identifier for that one party, so that you can recognize it when the terms are read back. It is optional. You can send one on:

  • a person, property, participant or company term;
  • each entry in a person’s residences, employers and properties;
  • a contactPoint.
{ "person": { "correlationId": "LOS-000123-BORROWER-1", "firstName": "Jane", "lastName": "Sample" } }
Sent on Echoed when the terms are read?
person, property, participant, company, and the residences, employers and properties inside a person Yes. Match an echoed term to the one you sent on its correlationId, rather than by comparing contents.
contactPoint Don’t rely on it. The value is stored, but match a contact point on the value you kept rather than on a read.
  • If you leave it out, the service generates one, and it generates a new one every time you stage. A generated id identifies a party in one snapshot only. Treat it as opaque, and send your own on any term you need to match across stages.
  • A blank value is the same as leaving it out.
  • It is not inherited. A person’s id is not the id of that person’s residences or employers.
  • Give each term its own value. Nothing checks that the values are unique, but two party terms in one loan that carry the same value can be merged into one party, and nothing tells you it happened.

The limit is 255 characters, counted after leading and trailing whitespace is trimmed. A longer value is not rejected and not shortened: it is silently ignored. The request still succeeds, and the term keeps a generated id that fits. To detect it, compare the correlationId echoed on the term with the one you sent.

Term ids and the request’s correlationId

A term’s correlationId is not the top-level correlationId you send on Start a Session, Start a Workflow, Start a Workflow in a Session and Reconfirm a Workflow. That one tags the session or the run, and Find Workflows searches the run’s. Term ids are never searched.

The two also handle an over-long value differently. A top-level correlationId longer than 255 characters is not silently replaced: the whole request is rejected with a 400 VALIDATION_ERROR, and nothing is stored.

What is actually validated

Five things are checked when you submit:

  1. terms must be present and a non-empty array. "terms": [] is rejected.
  2. Every element must be a JSON object — never null, never a bare string or number.
  3. Every element must carry exactly one key — never zero and never two. {} and {"person":{…},"property":{…}} are both rejected, with the text every element of terms must carry exactly one term key.
  4. That key must be one of the eleven branch keys above.
  5. Every value must be readable as the type its field declares. This is looser than it sounds: a number lands in a string field, and "true" lands in a boolean one. But a value that cannot be read at all, such as "yes" for an Indicator, fails the whole submission with VALIDATION_ERROR and the text terms could not be understood as a SAMI request.

Everything else is permissive. An absent field is accepted without complaint, and a field inside a term that this service does not recognize is ignored rather than refused, so a misspelled field name will not be reported back to you.

One bad term rejects the whole request. A single element carrying two keys, or an unrecognized branch key, fails the entire submission rather than being dropped while the others proceed. Nothing is recorded, and a session keeps whatever snapshot it already had.


Copyright © Pitchpoint Solutions. All rights reserved.