# Conventions

Source: https://docs.pitchpointsolutions.com/api/sami_terms/conventions/

**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](https://docs.pitchpointsolutions.com/api/session/stage_session_sami/), [Start a Workflow](https://docs.pitchpointsolutions.com/api/workflow/start_workflow/) and [Reconfirm a Workflow](https://docs.pitchpointsolutions.com/api/workflow/reconfirm_workflow/), and it is echoed back on [Read a Workflow](https://docs.pitchpointsolutions.com/api/workflow/get_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.

> **Before Starting:** [Build a Loan Step by Step](https://docs.pitchpointsolutions.com/api/sami_terms/build_a_loan/) describes the common fields, and [Value Vocabularies](https://docs.pitchpointsolutions.com/api/sami_terms/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.

```json
{ "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:

```json
{
  "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 &mdash; 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 &mdash; 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`. |

> **Warning:** **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 &mdash; there are no others:

`ssn` &middot; `dob` &middot; `yob` &middot; `ein` &middot; `ltvRatioPercent` &middot; `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](https://docs.pitchpointsolutions.com/api/sami_terms/vocabularies/#at-a-glance).

> **Warning:** **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 &mdash; not what the service matches on.

> **Warning:** **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 &mdash; see [What is actually validated](https://docs.pitchpointsolutions.com/api/sami_terms/conventions/#what-is-actually-validated) below.

### Convention 2: arrays are flat

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

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

not

```json
"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](https://docs.pitchpointsolutions.com/api/sami_terms/conventions/#presenthousingexpense) |

**`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`.

> **Warning:** **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](https://docs.pitchpointsolutions.com/api/sami_terms/vocabularies/) lists the documented values for each one.

> **Warning:** **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](https://docs.pitchpointsolutions.com/api/sami_terms/vocabularies/#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`.

```json
{ "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.

> **Warning:** **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](https://docs.pitchpointsolutions.com/api/session/start_session/), [Start a Workflow](https://docs.pitchpointsolutions.com/api/workflow/start_workflow/), [Start a Workflow in a Session](https://docs.pitchpointsolutions.com/api/session/start_session_workflow/) and [Reconfirm a Workflow](https://docs.pitchpointsolutions.com/api/workflow/reconfirm_workflow/). That one tags the session or the run, and [Find Workflows](https://docs.pitchpointsolutions.com/api/workflow/list_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** &mdash; never `null`, never a bare string or number.
3. Every element must carry **exactly one key** &mdash; 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.

> **Warning:** **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.
