# EXR Commercial Lease Commission Calculation Guide

This guide is self-contained context for EXR Back Office, Vault, or another
financial consumer that needs to read or independently reproduce the commercial
lease commission calculation from EXR's public Deal data.

It applies **only** to `lease` and `sublease` transactions. It does not define a
sales-deal commission engine and should not be used to infer commission for
`sale`, `loan_debt`, or `valuation_advisory` deals.

Use the public Developer API under `/api/v1`, not the authenticated in-product
routes. The public API returns parsed financial objects and stable money
representations. The complete, current field shape is available from
`GET /api/v1/openapi.json`.

## 1. Access and source-of-truth rules

### Required API access

- Read deals through `GET /api/v1/deals` (page with `limit` and the opaque
  `cursor`) or `GET /api/v1/deals/{id}`.
- `deal.created` and `deal.updated` webhooks carry the same full public deal
  object, including financials when the delivery is authorized for them.
- A token needs `deals:financials:read` for the `financials` object. If
  `financials` is **absent**, that is an authorization limitation, not proof
  that the deal has no financial data. Do not substitute zeroes.

### Precedence for consuming values

1. For an existing EXR deal, prefer the server-computed
   `financials.grossCommissionCents`, `financials.gciCents`,
   `financials.team[].payoutCents`, and
   `financials.team[].installments` as the operational values.
2. Use `financials.leaseTerms` as the calculator input record and the formulas
   below for independent parity checks, audits, previews, or a calculation
   before EXR has returned its computed values.
3. Treat `financials.potentialCommissionCents` as a headline/stored value, not
   an independent calculator result. EXR replaces it with calculated gross
   commission when a lease calculation produces a billable result; it can
   otherwise remain a manually entered estimate.
4. Never use `financials.commissionSchedule` to calculate the lease
   calculator's gross commission. It is a separate agreement-style description
   (explained in section 4).

### When values are absent, zero, or incomplete

| Condition | Public financial interpretation |
| --- | --- |
| `financials` absent | The caller lacks the financial-read scope. Stop rather than guessing. |
| `leaseTerms` is `null` | The stored calculator field was empty or not readable as JSON. This says nothing about the caller's scope because, with insufficient scope, the entire `financials` object would be absent. |
| Transaction is not `lease` or `sublease` | `grossCommissionCents` and `gciCents` are `null` by design; calculator terms are not applicable. |
| Lease/sublease has a readable but partial terms object | EXR can still expose calculated fields, often as `"0"`. A zero is not proof that all commercial terms are complete. Inspect the inputs and rates. |
| Sliding calculation has no positive starting rent, no rows, or no positive row rate | It has no billable sliding commission. |
| Fixed calculation has a zero gross result | It has no billable fixed commission. |

The calculator tolerates older and partial records. Missing optional inputs use
the defaults in this guide. Unknown or invalid enum values also fall back to
those defaults rather than creating a new calculation mode.

The formal public contract defines `leaseTerms` as an object or `null`.
Historical malformed storage can expose other successfully decoded JSON values
even though they are outside that contract. Treat such a value as invalid
calculator input; do not reinterpret it as a lease schedule. The server-computed
summary remains authoritative.

Team payouts are derived from a zero GCI input when no lease calculation is
available. Consequently, a team member can have `payoutCents = "0"` even while
gross and GCI are `null`. With a valid payment schedule this can include
zero-valued labeled installments; otherwise `installments` is empty. Do not
interpret that derived zero as evidence that lease terms exist.

## 2. Universal units, precision, and rounding

These conventions apply before any formula is evaluated.

| Value category | Representation and interpretation |
| --- | --- |
| Public summary money | Integer **cents serialized as a string**. For example, `"12500000"` is $125,000.00. Preserve it as an integer; divide by 100 only for display. |
| `leaseTerms` money inputs | Non-negative integer cents represented as numeric fields: for example, `startingRentAnnualCents` and `fixedAmountCents`. |
| Rates | Basis points (bps): 10,000 bps = 100%; 325 bps = 3.25%. Commission, referral, and co-brokerage rates preserve one decimal bps, which is 0.001 percentage-point precision. Escalation is whole bps. |
| Square footage | Whole square feet. |
| Dates | ISO `YYYY-MM-DD`; an empty string inside lease terms means “not entered.” |
| Missing values | `null` means not entered, not zero. Do not turn `null` into zero when aggregating. |

All calculations operate in cents. At every `round(...)` in this guide, round to
the nearest whole cent; an exact half-cent rounds up. Keep each specified
intermediate result as an integer cent value before using it in the next step.

This is important: the sum of rounded amortized free-rent slices can differ by
a few cents from the one-time total free-rent valuation, and gross commission
is the sum of **rounded row commissions**, not one percentage applied to an
unrounded aggregate.

## 3. Public deal and financial field dictionary

### Deal context needed to interpret financials

| Field | Meaning and use |
| --- | --- |
| `transactionType` | `lease` or `sublease` activates this guide. Other transaction types do not use this calculator. |
| `representation` | Deal-side context such as `landlord_rep`, `tenant_rep`, `sublessor_rep`, or `sublessee_rep`. It does not change the formulas below. |
| `status` | Pipeline status. Commission invoice and payout status are operationally meaningful after `deal_closed`; status does not change the calculation. |
| `parties.tenant`, `parties.landlord` | Party display context. |
| `property.address`, `property.unitNumber` | Property display context. |
| `expectedCloseDate`, `actualCloseDate` | Deal dates. `actualCloseDate` can supply the displayed date for a “Lease Execution” payment milestone; it does not change the commission arithmetic. |

### `financials` summary and computed outputs

| Field | Unit | Input or output | Meaning |
| --- | --- | --- | --- |
| `potentialCommissionCents` | string cents or `null` | Stored headline | Manual estimate when no billable lease calculation replaces it; for a calculated lease it is normally the gross commission. |
| `grossCommissionCents` | string cents or `null` | Server-computed output | Gross lease commission before referral and co-brokerage. |
| `gciCents` | string cents or `null` | Server-computed output | EXR GCI: gross less referral and co-brokerage. It is a residual, not a separate calculator input. |
| `ownerSplitBps` | bps | Team allocation input | Owner's deal-team split. |
| `team[]` | array | Inputs plus computed outputs | Each member has identity/display fields and `splitBps`; EXR computes the member payout and, when valid, its milestone installments. |
| `leaseTerms` | object or `null` | Calculator inputs | The per-year lease rent and commission terms defined in the next sections. |
| `commissionSchedule` | object or `null` | Agreement-style data | Separate agreement/document schedule; not a source for lease-calculator math. |
| `paymentSchedule` | object or `null` | Payout-timing input | Separate agreement-style milestone schedule used only to divide each team payout into installments. |

For every `team[]` member:

| Field | Unit | Meaning |
| --- | --- | --- |
| `userId`, `name`, `email` | text/null | Member identity and display information. |
| `splitBps` | bps | Share used to compute that member's GCI payout. Deal-team splits are expected to total 10,000 bps across the team. |
| `payoutCents` | string cents or `null` | Rounded member share of GCI. It is derived, not an additional amount to add to GCI. |
| `installments[]` | ordered array | Optional derived `{label, amountCents}` breakdown of that member's payout. An empty array means show the member's payout as one lump sum. |

### `leaseTerms`: inputs and defaults

`leaseTerms` is the authoritative public input record for the lease calculator.
All fields below are calculator inputs unless marked display-only.

| Field | Unit / allowed values | Default and active condition |
| --- | --- | --- |
| `sqft` | whole SF or `null` | Total rentable SF. Values are limited to 100,000,000 SF; empty or zero becomes `null`. Used only for PPSF display, not for rent or commission math. |
| `groundFloorSqft`, `basementSqft` | whole SF or `null` | Retail breakdown, display-only. The total `sqft`, not the breakdown, drives PPSF. |
| `startingRentAnnualCents` | annual integer cents or `null` | Year-1 full-year base rent. Empty or zero becomes `null`. Required for a normal rent-based schedule. |
| `termMonths` | whole months or `null` | `null` means every `years[]` row is a full 12 months. A valid value produces a partial final row when it is not divisible by 12. |
| `leaseStart`, `leaseEnd` | ISO date or empty string | Optional display/term-entry dates. They can derive a displayed term but do not override a saved row schedule during arithmetic. |
| `years[]` | ordered rows | One row per calculated lease year. It sets row count, escalation, by-year free rent, and sliding rates. |
| `freeRentMode` | `by_year` or `amortized` | Defaults to `by_year`. Selects which free-rent inputs are active. |
| `freeRentUnit` | `months` or `weeks` | Defaults to `months`. Applies to both the per-row and total free-rent quantities. |
| `freeRentMonthsTotal` | non-negative number in the selected unit | Used only in `amortized` mode. Defaults to 0; limited to 600 entered units. |
| `commissionBasis` | `net_rent` or `base_rent` | Defaults to `net_rent`. Used by sliding and fixed-percent calculations. |
| `commissionCalc` | `sliding` or `fixed` | Defaults to `sliding`. Selects gross-commission method. |
| `fixedMode` | `months`, `amount`, or `percent` | Defaults to `months`; used only when `commissionCalc` is `fixed`. |
| `fixedMonths` | non-negative number | Used only by fixed-months mode; capped at 600. |
| `fixedAmountCents` | non-negative integer cents | Used only by fixed-amount mode. |
| `fixedPercentBps` | bps | Used only by fixed-percent mode. |
| `referralMode` | `percent` or `dollar` | Defaults to `percent`; selects the active referral input. |
| `referralFeeBps` | 0–10,000 bps | Used only in percent referral mode; defaults to 0. |
| `referralAmountCents` | non-negative integer cents | Used only in dollar referral mode; defaults to 0. |
| `coBrokerageMode` | `percent` or `dollar` | Defaults to `percent`; selects the active co-brokerage input. |
| `coBrokerageBps` | 0–10,000 bps | Used only in percent co-brokerage mode; defaults to 0. |
| `coBrokerageAmountCents` | non-negative integer cents | Used only in dollar co-brokerage mode; defaults to 0. |
| `commissionPaidBy` | `landlord`, `tenant`, `both`, or empty | Defaults to empty. It can assist billing-recipient entry but never changes commission math. |
| `billTo`, `billToAddress`, `billToEmail` | text, up to 2,000 characters each | Default empty. Billing display fields only; never affect commission math. |

Each `years[]` row is ordered by index: first row is Year 1.

All cents input fields are limited to 9,007,199,254,740,991 cents and
non-negative values. Whole-cent fields are rounded to whole cents during input
normalization.

| Row field | Unit | Default / use |
| --- | --- | --- |
| `escalationBps` | whole bps, 0–1,000,000 | Increase from the prior **full-year** base rent. Year 1's value is ignored. |
| `freeRentMonths` | non-negative number in `freeRentUnit` | Used only in `by_year` mode. Fractions are allowed and are capped to the months covered by that row. |
| `commissionBps` | bps, up to 1,000,000 | Per-row rate for sliding calculation. Ignored by fixed-months and fixed-amount calculations; replaced by `fixedPercentBps` in fixed-percent calculation. |

## 4. Do not confuse the three financial schedules

The public financial object contains three distinct concepts:

| Field | Purpose | Units and relationship to the calculator |
| --- | --- | --- |
| `financials.leaseTerms` | Lease calculator input schedule | The only schedule used to derive rent rows, gross commission, referral, co-brokerage, and GCI. Its money is cents and its rates are bps. |
| `financials.commissionSchedule` | Agreement-style commission description | A separate tagged schedule: sliding year brackets, fixed agreement terms, or free-form custom text. Its percentage values are entered **strings** such as `"5"` for 5%, and its fixed dollar text is not a cents field. It does not replace, normalize, or drive `leaseTerms`. |
| `financials.paymentSchedule` | When a commission is paid | Structured milestone rows or free-form custom payment text. Structured percentages are entered strings such as `"50"` for 50%. It divides a team member's already-calculated GCI payout; it never changes gross commission or GCI. |

For a structured `paymentSchedule`, the rows are ordered and contain a
milestone label, percent, and optional expected date. The expected date is a
billing helper only. The displayed expected date for “Lease Execution” follows
the deal's `actualCloseDate`; it does not affect an installment amount.

## 5. Calculation specification

### 5.1 Normalize the term and build rows

Use this sequence:

1. Let `N` be `years[].length`. It is the number of schedule rows.
2. Normalize `termMonths` to a whole number. It is valid only when it is from
   1 through 720 and `ceil(termMonths / 12) = N`.
3. If `termMonths` is valid, rows 1 through `N - 1` cover 12 months and row
   `N` covers `termMonths - 12 × (N - 1)` months. If it is absent or invalid,
   every row covers 12 months.
4. Lease start/end dates are optional. When both are present, an inclusive
   lease duration can be derived by treating the day after the end date as the
   boundary, taking calendar-month difference plus the nearest partial month
   (day difference divided by 30.44), then limiting the result to 1–720
   months. This is a term-entry aid; row count and valid `termMonths` control
   the arithmetic.

For row `i` (Year 1 is `i = 1`), let `A` be
`startingRentAnnualCents` and `m_i` be that row's months:

- Full-year rate for Year 1: `F_1 = A`.
- For each later row:
  `F_i = round(F_(i-1) × (10,000 + escalationBps_i) / 10,000)`.
- The escalation chain always uses full-year rates, even if the last row is
  partial.
- Row base rent:
  `B_i = F_i` for a 12-month row; otherwise
  `B_i = round(F_i × m_i / 12)`.
- Displayed monthly base rent: `round(F_i / 12)`. This remains the full-year
  monthly rate even in a partial final row.
- Displayed base PPSF: `round(F_i / sqft)` cents per SF when total `sqft` is
  positive; otherwise it is unavailable. PPSF is informational and never
  changes the commission calculation.

For data entry, an annual dollar rent is converted to integer annual cents.
A monthly dollar rent is multiplied by 12 to become annual cents. An annual
PPSF entry is multiplied by total SF and then converted to annual cents.
Once read from the API, `startingRentAnnualCents` is the authoritative rent
input for the formulas above.

### 5.2 Free rent and net rent

`freeRentUnit = months` uses the entered number directly. With
`freeRentUnit = weeks`, convert entered units to month-equivalents using:

`free-rent months = entered weeks × 12 / 52`

Equivalently, one week is 3/13 of a month. Do not use four weeks as one month.

#### By year

This is the default mode. For each row:

1. Convert `years[i].freeRentMonths` from the selected unit to months.
2. Cap it at that row's `m_i` months, then retain the capped quantity for
   display in the selected unit.
3. Calculate row free-rent value:
   `R_i = round(F_i / 12 × capped free-rent months)`.
4. Calculate row net rent:
   `Net_i = max(0, B_i - R_i)`.

The cap means free rent cannot exceed the months actually covered by the row.
For example, 60 entered weeks in a full-year row caps to 52 weeks, or 12
months.

#### Amortized over term

Only `freeRentMonthsTotal` is active in this mode. It is capped at 600 entered
units. Apply this sequence once:

1. Convert total entered units to month-equivalents.
2. Value total free rent at the Year-1 monthly base rate:
   `R_total = round(F_1 / 12 × total free-rent months)`.
3. Divide it over the **number of rows**, not the number of lease months:
   `R_slice = round(R_total / N)`.
4. Put the same `R_slice` on every row, including a partial final row.
5. For each row, calculate `Net_i = max(0, B_i - R_slice)`.

The stored/free-rent total reports the entered total quantity, while the
schedule reports the rounded dollar slice. Therefore `sum(R_i)` can be a few
cents different from `R_total`. The schedule's total net rent uses the rounded
row slices.

### 5.3 Select commissionable rent

For every row, net rent is displayed regardless of the chosen basis:

- `net_rent` (default): `C_i = Net_i`.
- `base_rent`: `C_i = B_i`; free rent still appears in net-rent display but is
  ignored for commission.

The basis applies to sliding and fixed-percent calculations. Fixed-months and
fixed-amount calculations do not use a row commission basis.

### 5.4 Calculate gross commission

Choose exactly one calculation branch.

| Mode | Gross-commission rule |
| --- | --- |
| Sliding (default) | For each row, calculate `Q_i = round(C_i × commissionBps_i / 10,000)`. Gross is `sum(Q_i)`. Round each row first. |
| Fixed: months | `Gross = round((F_1 / 12) × fixedMonths)`. `fixedMonths` is non-negative and capped at 600. Per-row commission values are not used. |
| Fixed: amount | `Gross = fixedAmountCents`. Per-row commission values are not used. |
| Fixed: percent | For each row, set the row rate to `fixedPercentBps` and calculate `Q_i = round(C_i × fixedPercentBps / 10,000)`. Gross is `sum(Q_i)`. This deliberately follows the same per-row rounding as sliding. |

In fixed-months and fixed-amount modes, the yearly commission cells have no
independent calculation and should be treated as unavailable rather than
summed. In fixed-percent mode, they are calculated and sum to gross.

### 5.5 Apply referral, co-brokerage, and EXR GCI

All fee rates are limited to 0–10,000 bps. The order is mandatory:

1. **Referral fee** comes from gross.
   - Percent: `Referral = round(Gross × referralFeeBps / 10,000)`.
   - Dollar: `Referral = min(referralAmountCents, Gross)`.
2. Set `PostReferral = max(0, Gross - Referral)`.
3. **Co-brokerage** comes from the post-referral amount, never from gross.
   - Percent:
     `CoBrokerage = round(PostReferral × coBrokerageBps / 10,000)`.
   - Dollar:
     `CoBrokerage = min(coBrokerageAmountCents, PostReferral)`.
4. **EXR GCI** is the residual:
   `GCI = Gross - Referral - CoBrokerage`.

The dollar caps ensure GCI cannot become negative. If no downstream fees are
entered, both fees are zero and GCI equals gross.

### 5.6 Split GCI among the deal team and milestones

For each deal-team member independently:

`MemberPayout = round(GCI × member.splitBps / 10,000)`

The member payouts are derived views. Preserve EXR's returned payout values for
operational use; an independent replica should apply the same rounding to every
member rather than moving a rounding difference between members.

Then assess the payment schedule:

- Use installment breakdown only for a non-empty, structured milestone schedule
  whose parsed percentages total within 1 bps of 10,000 bps (99.99%–100.01%).
- Custom free text, no rows, or a schedule outside that tolerance produces no
  installments. The member payout is one lump sum.
- For a valid schedule, process milestones in listed order. For every member
  except the final milestone, calculate
  `round(MemberPayout × milestonePercentBps / 10,000)`.
- The final milestone is
  `MemberPayout - sum(previous installments)`. It absorbs any rounding
  remainder, so that member's installments always sum exactly to their payout.

## 6. Required worked parity example: 125-month scenario

This example uses the fields visible in the referenced commission scenario.
Any value not visible is assumed to remain at its documented default:

- 11,460 SF (PPSF is display-only).
- $350,000.00 annual starting rent = 35,000,000 cents.
- 125-month term, therefore 11 rows: ten 12-month rows and one 5-month row.
- Five months of free rent, `amortized` over the term, unit `months`.
- Fixed calculation, fixed-percent mode, 3.25% = 325 bps, on `net_rent`.
- No escalation, referral, co-brokerage, or other downstream fee.

First calculate the free-rent allocation:

- Year-1 monthly base is 35,000,000 / 12 = 2,916,666.666... cents.
- One-time free-rent value is
  `round(2,916,666.666... × 5) = 14,583,333` cents = **$145,833.33**.
- Each row's amortized slice is
  `round(14,583,333 / 11) = 1,325,758` cents = **$13,257.58**.

| Rows | Months each | Base rent / yr | Free rent / yr | Net rent / yr | Fixed 3.25% commission / row |
| --- | ---: | ---: | ---: | ---: | ---: |
| Years 1–10 (each) | 12 | $350,000.00 | $13,257.58 | $336,742.42 | $10,944.13 |
| Year 11 | 5 | $145,833.33 | $13,257.58 | $132,575.75 | $4,308.71 |

The ten full rows each use:

`round(33,674,242 cents × 325 / 10,000) = 1,094,413 cents`

The partial final row uses:

`round(13,257,575 cents × 325 / 10,000) = 430,871 cents`

Therefore:

- Total base rent = **$3,645,833.33**.
- The original one-time free-rent valuation is **$145,833.33**, but 11 rounded
  schedule slices total **$145,833.38**.
- Total schedule net rent is **$3,499,999.95**.
- Gross commission is
  `10 × $10,944.13 + $4,308.71` = **$113,750.01**.
- With zero referral and co-brokerage, EXR GCI is also **$113,750.01**.

The five-cent free-rent allocation difference and one-cent gross outcome are
intentional consequences of EXR's row-by-row rounding rules. Do not replace
this with one calculation on aggregate net rent.

## 7. Compact parity examples

### Sliding scale with escalation

Starting rent is $120,000.00/year (12,000,000 cents), no free rent, and both
Year 1 and Year 2 use 5.00% (500 bps). Year 2 escalates 10.00% (1,000 bps).

| Row | Base rent | Commission |
| --- | ---: | ---: |
| Year 1 | $120,000.00 | `round(12,000,000 × 500 / 10,000)` = $6,000.00 |
| Year 2 | $132,000.00 | `round(13,200,000 × 500 / 10,000)` = $6,600.00 |

Gross is **$12,600.00**. The escalation is chained from the prior full-year
rate before commission is calculated.

### By-year versus amortized free rent

Use a two-year $120,000.00/year lease, 10% Year-2 escalation, 5% net
commission each year, and one month of total free rent.

- **By year:** place the month in the escalated Year 2. Year 1 commission is
  $6,000.00. Year 2 base is $132,000.00, free rent is $11,000.00, net is
  $121,000.00, and commission is $6,050.00. Gross = **$12,050.00**.
- **Amortized:** value the month at Year-1 monthly rent ($10,000.00) and put
  $5,000.00 on each row. Year 1 commission is $5,750.00 and Year 2 commission
  is $6,350.00. Gross = **$12,100.00**.

The result differs because by-year free rent uses that year's rent while
amortized free rent always uses the Year-1 monthly rate and spreads its dollar
value by row.

### Referral then co-brokerage

For a $10,000.00 gross commission, referral is 10% and co-brokerage is 50%:

1. Referral = $1,000.00 (10% of gross).
2. Post-referral amount = $9,000.00.
3. Co-brokerage = $4,500.00 (50% of $9,000.00).
4. EXR GCI = $4,500.00.

Taking co-brokerage from gross instead would be incorrect. A $12,000 dollar
referral would instead cap at the $10,000 gross, leaving $0 for both
co-brokerage and GCI.

### Team and milestone rounding

Let GCI be 1,000,001 cents ($10,000.01), with a 60% lead and 40% collaborator.
Their independently rounded payouts are $6,000.01 and $4,000.00. Use valid,
ordered milestones of 33.34%, 33.33%, and 33.33%:

| Member | Payout | Milestone 1 | Milestone 2 | Final milestone (remainder) |
| --- | ---: | ---: | ---: | ---: |
| Lead, 60% | $6,000.01 | $2,000.40 | $1,999.80 | $1,999.81 |
| Collaborator, 40% | $4,000.00 | $1,333.60 | $1,333.20 | $1,333.20 |

The final installment is not independently percentage-rounded. It is the
remainder after prior installments so each member's installment total exactly
matches that member's payout.

## 8. Vault handoff semantics

- Financial meaning is available only when the deal includes the scoped
  `financials` object.
- Only lease and sublease records use this calculation.
- Public `*Cents` summary strings mean integer cents; `leaseTerms` retains the
  input units documented above.
- EXR's computed gross, GCI, team payouts, and installments are the primary
  operational handoff values. Sections 5.1–5.6 define the independent parity
  interpretation and its required operation order.
- `leaseTerms`, `commissionSchedule`, and `paymentSchedule` remain separate.
  Agreement text does not supply a calculator rate, and payment milestones do
  not create a second commission calculation.

These semantics give Vault exact EXR operational values and a clear audit basis
without prescribing a particular implementation.