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
- Convention 1: key casing
- Convention 2: arrays are flat
- Convention 3: numbers and dates are strings
- Convention 4: fields that look like enums are free text
- Correlation ids on terms
- What is actually validated
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,participantorcompanyterm; - each entry in a person’s
residences,employersandproperties; - 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:
termsmust be present and a non-empty array."terms": []is rejected.- Every element must be a JSON object — never
null, never a bare string or number. - Every element must carry exactly one key — never zero and never two.
{}and{"person":{…},"property":{…}}are both rejected, with the textevery element of terms must carry exactly one term key. - That key must be one of the eleven branch keys above.
- 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 anIndicator, fails the whole submission withVALIDATION_ERRORand the textterms 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.