> ## Documentation Index
> Fetch the complete documentation index at: https://docs.montereyfinancial.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Integration patterns

> Practical recipes for common workflows against the Reporting API — incremental sync, reconciliation, drill-down, backfill, autopay setup.

These recipes assume you've read [Authentication](/api-reference/authentication) and [Conventions](/api-reference/conventions) — they cover the Bearer header, pagination shape (`cursor`/`limit` → `next_cursor`), money fields (integer cents), and error envelopes. Each recipe shows the same workflow in three forms: a one-line `curl`, a Python snippet, and a TypeScript snippet using built-in `fetch` with inline types.

The host in every example is `https://api.montereyfinancial.app`. Replace `mk_your_key_here` with your real key.

## Daily incremental sync

Pull yesterday's new transactions into your warehouse. Anchor on the `effective_at` date range, paginate with `cursor`/`limit`, dedupe on `transaction_id` in your warehouse rather than tracking a high-water mark on the API side. Re-running the same window is safe — the API returns the same transaction IDs.

<CodeGroup>
  ```bash cURL theme={null}
  # Replace YESTERDAY with yesterday's date in UTC (e.g. 2026-05-27)
  curl -H "Authorization: Bearer mk_your_key_here" \
    "https://api.montereyfinancial.app/transactions?from=YESTERDAY&to=YESTERDAY&limit=500"

  # Then paginate by passing back next_cursor:
  curl -H "Authorization: Bearer mk_your_key_here" \
    "https://api.montereyfinancial.app/transactions?from=YESTERDAY&to=YESTERDAY&limit=500&cursor=NEXT_CURSOR_VALUE"
  ```

  ```python Python theme={null}
  import os
  from datetime import date, timedelta

  import httpx

  API_KEY = os.environ["MFS_API_KEY"]
  BASE = "https://api.montereyfinancial.app"
  HEADERS = {"Authorization": f"Bearer {API_KEY}"}

  def fetch_all(url: str, params: dict) -> list[dict]:
      items, cursor = [], None
      while True:
          if cursor:
              params["cursor"] = cursor
          resp = httpx.get(url, params=params, headers=HEADERS, timeout=30.0)
          resp.raise_for_status()
          page = resp.json()
          items.extend(page["items"])
          cursor = page["page"]["next_cursor"]
          if not cursor:
              return items

  yesterday = (date.today() - timedelta(days=1)).isoformat()
  transactions = fetch_all(
      f"{BASE}/transactions",
      {"from": yesterday, "to": yesterday, "limit": 500},
  )
  for tx in transactions:
      save_to_warehouse(tx)  # dedupe on tx["transaction_id"] in your DB
  ```

  ```typescript TypeScript theme={null}
  type TransactionRow = {
    transaction_id: string;
    organization_id: string;
    account_id: string;
    type: string;          // "payment" | "adjustment" | ...
    amount_cents: number;
    effective_at: string;  // YYYY-MM-DD
    status: string | null;
  };
  type PageMeta = { has_more: boolean; limit: number; next_cursor: string | null };
  type TransactionsPage = { items: TransactionRow[]; page: PageMeta };

  const API_KEY = process.env.MFS_API_KEY!;
  const BASE = "https://api.montereyfinancial.app";

  async function fetchAll(path: string, params: Record<string, string>): Promise<TransactionRow[]> {
    const items: TransactionRow[] = [];
    let cursor: string | null = null;
    do {
      const url = new URL(BASE + path);
      for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);
      if (cursor) url.searchParams.set("cursor", cursor);
      const res = await fetch(url, { headers: { Authorization: `Bearer ${API_KEY}` } });
      if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
      const data = (await res.json()) as TransactionsPage;
      items.push(...data.items);
      cursor = data.page.next_cursor;
    } while (cursor);
    return items;
  }

  const yesterday = new Date(Date.now() - 86_400_000).toISOString().slice(0, 10);
  const transactions = await fetchAll("/transactions", {
    from: yesterday,
    to: yesterday,
    limit: "500",
  });
  for (const tx of transactions) {
    saveToWarehouse(tx); // dedupe on tx.transaction_id in your DB
  }
  ```
</CodeGroup>

## Reconcile a payment to your books

This is the most concrete use of `external_contract_number`. Two directions:

**You see a payment in Monterey, want to find the matching record in your system.** Fetch the account by ID, read `external_contract_number`, look up in your DB.

**You have a contract number in your system, want all Monterey transactions for it.** List accounts filtered by your contract number (via list-and-filter, since there's no `?external_contract_number=` query parameter today), then list transactions per matching account.

<CodeGroup>
  ```bash cURL theme={null}
  # Direction 1: MFS payment → your contract number
  # Given a transaction with account_id="01J..."
  curl -H "Authorization: Bearer mk_your_key_here" \
    "https://api.montereyfinancial.app/accounts/01J..."
  # → response includes external_contract_number; look that up in your DB

  # Direction 2: your contract number → all MFS transactions for it
  # First find the account (no direct filter; scan accounts list)
  curl -H "Authorization: Bearer mk_your_key_here" \
    "https://api.montereyfinancial.app/accounts?limit=500"
  # Filter locally for the account whose external_contract_number matches yours.
  # Then list its transactions:
  curl -H "Authorization: Bearer mk_your_key_here" \
    "https://api.montereyfinancial.app/accounts/01J.../transactions?limit=500"
  ```

  ```python Python theme={null}
  import os
  import httpx

  API_KEY = os.environ["MFS_API_KEY"]
  BASE = "https://api.montereyfinancial.app"
  HEADERS = {"Authorization": f"Bearer {API_KEY}"}

  def get_account(account_id: str) -> dict:
      r = httpx.get(f"{BASE}/accounts/{account_id}", headers=HEADERS, timeout=30.0)
      r.raise_for_status()
      return r.json()

  def find_account_by_contract(your_contract_number: str) -> dict | None:
      # Walk accounts pages, filtering locally.
      cursor = None
      while True:
          params = {"limit": 500}
          if cursor:
              params["cursor"] = cursor
          r = httpx.get(f"{BASE}/accounts", params=params, headers=HEADERS, timeout=30.0)
          r.raise_for_status()
          page = r.json()
          for acct in page["items"]:
              if acct["external_contract_number"] == your_contract_number:
                  return acct
          cursor = page["page"]["next_cursor"]
          if not cursor:
              return None

  # Direction 1
  account = get_account("01J...")
  your_record = your_db.find_by_contract(account["external_contract_number"])

  # Direction 2
  account = find_account_by_contract("YOUR-CONTRACT-12345")
  if account:
      tx_resp = httpx.get(
          f"{BASE}/accounts/{account['id']}/transactions",
          params={"limit": 500},
          headers=HEADERS,
          timeout=30.0,
      )
      transactions = tx_resp.json()["items"]
  ```

  ```typescript TypeScript theme={null}
  type AccountRow = {
    id: string;
    organization_id: string;
    external_contract_number: string | null;
    external_account_number: string | null;
    current_balance_cents: number;
    status_code: string;
  };
  type AccountsPage = { items: AccountRow[]; page: { next_cursor: string | null } };

  const API_KEY = process.env.MFS_API_KEY!;
  const BASE = "https://api.montereyfinancial.app";
  const H = { Authorization: `Bearer ${API_KEY}` };

  async function getAccount(accountId: string): Promise<AccountRow> {
    const res = await fetch(`${BASE}/accounts/${accountId}`, { headers: H });
    if (!res.ok) throw new Error(`${res.status}`);
    return (await res.json()) as AccountRow;
  }

  async function findAccountByContract(yourContractNumber: string): Promise<AccountRow | null> {
    let cursor: string | null = null;
    do {
      const url = new URL(`${BASE}/accounts`);
      url.searchParams.set("limit", "500");
      if (cursor) url.searchParams.set("cursor", cursor);
      const res = await fetch(url, { headers: H });
      if (!res.ok) throw new Error(`${res.status}`);
      const page = (await res.json()) as AccountsPage;
      const match = page.items.find(a => a.external_contract_number === yourContractNumber);
      if (match) return match;
      cursor = page.page.next_cursor;
    } while (cursor);
    return null;
  }

  // Direction 1
  const account = await getAccount("01J...");
  const yourRecord = yourDb.findByContract(account.external_contract_number);

  // Direction 2
  const found = await findAccountByContract("YOUR-CONTRACT-12345");
  if (found) {
    const txRes = await fetch(`${BASE}/accounts/${found.id}/transactions?limit=500`, { headers: H });
    const transactions = (await txRes.json()).items;
  }
  ```
</CodeGroup>

## Drill from organization → account → transaction

The resource-composition pattern. Useful for customer service tools and "everything about this customer" views. Three hops: pick an organization, list its accounts, then list a specific account's transactions.

For org-wide aggregates (total payments this month across all accounts), the Reports family is usually a better fit than walking the resource tree — the server-side aggregation is cheaper than fetching every row.

<CodeGroup>
  ```bash cURL theme={null}
  # 1. Get the organization (if you only have its id from a list response)
  curl -H "Authorization: Bearer mk_your_key_here" \
    "https://api.montereyfinancial.app/organizations/01K..."

  # 2. List all accounts in that organization
  curl -H "Authorization: Bearer mk_your_key_here" \
    "https://api.montereyfinancial.app/accounts?organization_id=01K...&limit=500"

  # 3. List transactions for one specific account
  curl -H "Authorization: Bearer mk_your_key_here" \
    "https://api.montereyfinancial.app/accounts/01J.../transactions?limit=500"
  ```

  ```python Python theme={null}
  import os
  import httpx

  API_KEY = os.environ["MFS_API_KEY"]
  BASE = "https://api.montereyfinancial.app"
  HEADERS = {"Authorization": f"Bearer {API_KEY}"}

  def drill_org_to_transactions(organization_id: str) -> dict:
      org = httpx.get(
          f"{BASE}/organizations/{organization_id}", headers=HEADERS, timeout=30.0,
      ).json()

      accounts_resp = httpx.get(
          f"{BASE}/accounts",
          params={"organization_id": organization_id, "limit": 500},
          headers=HEADERS,
          timeout=30.0,
      ).json()
      accounts = accounts_resp["items"]

      transactions_by_account: dict[str, list[dict]] = {}
      for acct in accounts:
          tx_resp = httpx.get(
              f"{BASE}/accounts/{acct['id']}/transactions",
              params={"limit": 500},
              headers=HEADERS,
              timeout=30.0,
          ).json()
          transactions_by_account[acct["id"]] = tx_resp["items"]

      return {"organization": org, "accounts": accounts, "transactions": transactions_by_account}

  result = drill_org_to_transactions("01K...")
  ```

  ```typescript TypeScript theme={null}
  type Organization = { id: string; display_name: string; legal_name: string; is_active: boolean };
  type AccountRow = {
    id: string;
    organization_id: string;
    primary_borrower_party_id: string;
    external_contract_number: string | null;
    current_balance_cents: number;
    status_code: string;
  };
  type TransactionRow = {
    transaction_id: string;
    account_id: string;
    organization_id: string;
    type: string;
    amount_cents: number;
    effective_at: string;
  };

  const API_KEY = process.env.MFS_API_KEY!;
  const BASE = "https://api.montereyfinancial.app";
  const H = { Authorization: `Bearer ${API_KEY}` };

  async function drillOrgToTransactions(organizationId: string) {
    const orgRes = await fetch(`${BASE}/organizations/${organizationId}`, { headers: H });
    const organization = (await orgRes.json()) as Organization;

    const acctsRes = await fetch(
      `${BASE}/accounts?organization_id=${organizationId}&limit=500`,
      { headers: H },
    );
    const accounts = ((await acctsRes.json()) as { items: AccountRow[] }).items;

    const transactionsByAccount: Record<string, TransactionRow[]> = {};
    for (const acct of accounts) {
      const txRes = await fetch(`${BASE}/accounts/${acct.id}/transactions?limit=500`, { headers: H });
      transactionsByAccount[acct.id] = ((await txRes.json()) as { items: TransactionRow[] }).items;
    }

    return { organization, accounts, transactions: transactionsByAccount };
  }

  const result = await drillOrgToTransactions("01K...");
  ```
</CodeGroup>

## Backfill historical data at scale

Walk a multi-year date range in monthly chunks. The pagination pattern is identical to the daily sync — just scaled up. Three operational notes:

* **Chunk by month**, not year. Keeps individual jobs short enough to retry without losing too much state.
* **Save `next_cursor` between chunks.** A multi-hour backfill that crashes shouldn't restart from scratch — store the cursor in your job state and resume.
* **Respect the limit ceiling.** The maximum `limit` is 1000; passing higher returns 422 (see [Conventions](/api-reference/conventions)).

<CodeGroup>
  ```bash cURL theme={null}
  # Pull one month at a time. Loop in shell or in your job runner.
  for month in 2024-01 2024-02 2024-03; do
    from="${month}-01"
    to=$(date -d "${from} +1 month -1 day" +%F)
    cursor=""
    while :; do
      url="https://api.montereyfinancial.app/transactions?from=${from}&to=${to}&limit=500${cursor:+&cursor=$cursor}"
      resp=$(curl -s -H "Authorization: Bearer mk_your_key_here" "$url")
      echo "$resp" | jq -c '.items[]' >> backfill.jsonl
      cursor=$(echo "$resp" | jq -r '.page.next_cursor // empty')
      [ -z "$cursor" ] && break
    done
  done
  ```

  ```python Python theme={null}
  import os
  from datetime import date
  from dateutil.relativedelta import relativedelta  # pip install python-dateutil

  import httpx

  API_KEY = os.environ["MFS_API_KEY"]
  BASE = "https://api.montereyfinancial.app"
  HEADERS = {"Authorization": f"Bearer {API_KEY}"}

  def backfill_transactions(start: date, end: date) -> None:
      month_start = start.replace(day=1)
      while month_start <= end:
          month_end = (month_start + relativedelta(months=1)) - relativedelta(days=1)
          cursor = None
          while True:
              params = {
                  "from": month_start.isoformat(),
                  "to": min(month_end, end).isoformat(),
                  "limit": 500,
              }
              if cursor:
                  params["cursor"] = cursor
              resp = httpx.get(
                  f"{BASE}/transactions", params=params, headers=HEADERS, timeout=60.0,
              )
              resp.raise_for_status()
              page = resp.json()
              for row in page["items"]:
                  save_to_warehouse(row)  # dedupe on transaction_id
              cursor = page["page"]["next_cursor"]
              if not cursor:
                  break
          month_start += relativedelta(months=1)

  backfill_transactions(date(2022, 1, 1), date.today())
  ```

  ```typescript TypeScript theme={null}
  type TransactionRow = {
    transaction_id: string;
    account_id: string;
    organization_id: string;
    type: string;
    amount_cents: number;
    effective_at: string;
  };
  type Page = { items: TransactionRow[]; page: { next_cursor: string | null } };

  const API_KEY = process.env.MFS_API_KEY!;
  const BASE = "https://api.montereyfinancial.app";
  const H = { Authorization: `Bearer ${API_KEY}` };

  function addMonths(d: Date, n: number): Date {
    const out = new Date(d);
    out.setUTCMonth(out.getUTCMonth() + n);
    return out;
  }

  async function backfillTransactions(start: Date, end: Date): Promise<void> {
    let monthStart = new Date(Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), 1));
    while (monthStart <= end) {
      const monthEnd = new Date(addMonths(monthStart, 1).getTime() - 86_400_000);
      const windowEnd = monthEnd < end ? monthEnd : end;
      let cursor: string | null = null;
      do {
        const url = new URL(`${BASE}/transactions`);
        url.searchParams.set("from", monthStart.toISOString().slice(0, 10));
        url.searchParams.set("to", windowEnd.toISOString().slice(0, 10));
        url.searchParams.set("limit", "500");
        if (cursor) url.searchParams.set("cursor", cursor);
        const res = await fetch(url, { headers: H });
        if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
        const page = (await res.json()) as Page;
        for (const row of page.items) saveToWarehouse(row); // dedupe on transaction_id
        cursor = page.page.next_cursor;
      } while (cursor);
      monthStart = addMonths(monthStart, 1);
    }
  }

  await backfillTransactions(new Date("2022-01-01"), new Date());
  ```
</CodeGroup>

## Set up autopay on an account

The first write-surface recipe: attach a recurring payment schedule to an account. Two calls — find an eligible stored payment method, then create the schedule. `GET /accounts/{id}/payment-methods` returns exactly the methods that can fund autopay for that account: they belong to a borrower on the account, and only active methods are included (deactivated ones appear only in the top-level `/payment-methods` list). No filtering needed on your side.

The example pins a monthly charge to the 1st using `anchor_day`. For cadences like "every two weeks," use `frequency_unit: "week"` with `frequency_interval: 2` and no anchor.

<CodeGroup>
  ```bash cURL theme={null}
  # 1. List payment methods eligible for this account
  curl -H "Authorization: Bearer mk_your_key_here" \
    "https://api.montereyfinancial.app/accounts/01J.../payment-methods"
  # Every method returned is active and eligible — pick one.

  # 2. Create the schedule: 25,000 cents on the 1st of every month
  curl -X POST \
    -H "Authorization: Bearer mk_your_key_here" \
    -H "Content-Type: application/json" \
    -d '{
      "account_id": "01J...",
      "payment_method_id": "01J...",
      "amount_cents": 25000,
      "frequency_unit": "month",
      "frequency_interval": 1,
      "start_date": "2026-07-01",
      "anchor_day": 1
    }' \
    "https://api.montereyfinancial.app/autopay"
  # → 201 with the created schedule, including next_run_date.

  # Later: pause, resume, or cancel
  curl -X PATCH -H "Authorization: Bearer mk_your_key_here" \
    -H "Content-Type: application/json" \
    -d '{"status": "paused"}' \
    "https://api.montereyfinancial.app/autopay/01J..."
  curl -X DELETE -H "Authorization: Bearer mk_your_key_here" \
    "https://api.montereyfinancial.app/autopay/01J..."
  ```

  ```python Python theme={null}
  import os
  import httpx

  API_KEY = os.environ["MFS_API_KEY"]
  BASE = "https://api.montereyfinancial.app"
  HEADERS = {"Authorization": f"Bearer {API_KEY}"}

  def setup_autopay(account_id: str, amount_cents: int, start_date: str) -> dict:
      # 1. Find a payment method eligible for this account (only active ones are returned)
      pm_resp = httpx.get(
          f"{BASE}/accounts/{account_id}/payment-methods",
          headers=HEADERS,
          timeout=30.0,
      )
      pm_resp.raise_for_status()
      methods = pm_resp.json()["items"]
      if not methods:
          raise RuntimeError("No active payment method on file for this account")

      # 2. Create the schedule: monthly, pinned to the 1st
      create_resp = httpx.post(
          f"{BASE}/autopay",
          json={
              "account_id": account_id,
              "payment_method_id": methods[0]["id"],
              "amount_cents": amount_cents,
              "frequency_unit": "month",
              "frequency_interval": 1,
              "start_date": start_date,
              "anchor_day": 1,
          },
          headers=HEADERS,
          timeout=30.0,
      )
      create_resp.raise_for_status()
      return create_resp.json()  # includes next_run_date and status

  schedule = setup_autopay("01J...", amount_cents=25000, start_date="2026-07-01")

  # Later: pause / resume / cancel
  httpx.patch(f"{BASE}/autopay/{schedule['id']}", json={"status": "paused"}, headers=HEADERS, timeout=30.0)
  httpx.patch(f"{BASE}/autopay/{schedule['id']}", json={"status": "active"}, headers=HEADERS, timeout=30.0)
  httpx.delete(f"{BASE}/autopay/{schedule['id']}", headers=HEADERS, timeout=30.0)  # cannot be undone
  ```

  ```typescript TypeScript theme={null}
  type PaymentMethodRow = {
    id: string;
    party_id: string;
    instrument_type: "card" | "bank_account" | "wallet";
    nickname: string | null;
    last_four: string;
    is_active: boolean;
  };
  type AutopayRow = {
    id: string;
    account_id: string;
    payment_method_id: string;
    amount_cents: number;
    frequency_unit: "day" | "week" | "month" | "year";
    frequency_interval: number;
    start_date: string;
    next_run_date: string;
    status: "active" | "paused" | "cancelled" | "expired";
  };

  const API_KEY = process.env.MFS_API_KEY!;
  const BASE = "https://api.montereyfinancial.app";
  const H = { Authorization: `Bearer ${API_KEY}` };

  async function setupAutopay(accountId: string, amountCents: number, startDate: string): Promise<AutopayRow> {
    // 1. Find a payment method eligible for this account (only active ones are returned)
    const pmRes = await fetch(`${BASE}/accounts/${accountId}/payment-methods`, { headers: H });
    if (!pmRes.ok) throw new Error(`${pmRes.status} ${await pmRes.text()}`);
    const methods = ((await pmRes.json()) as { items: PaymentMethodRow[] }).items;
    const method = methods[0];
    if (!method) throw new Error("No active payment method on file for this account");

    // 2. Create the schedule: monthly, pinned to the 1st
    const createRes = await fetch(`${BASE}/autopay`, {
      method: "POST",
      headers: { ...H, "Content-Type": "application/json" },
      body: JSON.stringify({
        account_id: accountId,
        payment_method_id: method.id,
        amount_cents: amountCents,
        frequency_unit: "month",
        frequency_interval: 1,
        start_date: startDate,
        anchor_day: 1,
      }),
    });
    if (!createRes.ok) throw new Error(`${createRes.status} ${await createRes.text()}`);
    return (await createRes.json()) as AutopayRow;
  }

  const schedule = await setupAutopay("01J...", 25000, "2026-07-01");

  // Later: pause / resume / cancel
  await fetch(`${BASE}/autopay/${schedule.id}`, {
    method: "PATCH",
    headers: { ...H, "Content-Type": "application/json" },
    body: JSON.stringify({ status: "paused" }),
  });
  await fetch(`${BASE}/autopay/${schedule.id}`, { method: "DELETE", headers: H }); // cannot be undone
  ```
</CodeGroup>

Operational notes:

* **Updating a schedule** can change the payment method, amount, end date, or pause state. Cadence and `start_date` are fixed — cancel and recreate to change them.
* **Removing a payment method** that an active or paused schedule still uses returns `409` — cancel the schedule first.
* **One-time payments** (`POST /payments`) share this shape — account, payment method, amount — but aren't live yet; every request returns `501` until submission ships.

## Choosing reports vs resource endpoints

The Reporting API surface has two answer shapes — pre-shaped Reports and composable resource endpoints. They cover the same underlying data; the right choice depends on the shape of your question.

| Your question                                           | Use                                                                     |
| ------------------------------------------------------- | ----------------------------------------------------------------------- |
| "Give me all NSF returns in March."                     | `GET /reports/nsfr`                                                     |
| "What payments came in yesterday?"                      | `GET /reports/crr` or `GET /transactions`                               |
| "What's account `01J…` worth right now?"                | `GET /accounts/{id}`                                                    |
| "Who is the primary borrower on account `01J…`?"        | `GET /accounts/{id}` (includes `primary_borrower_party_id`)             |
| "Show every transaction for borrower `01K…`."           | `GET /persons/{id}/accounts` → fan out to `/accounts/{id}/transactions` |
| "All write-offs this quarter, grouped by organization." | `GET /reports/wor` (already returns `organization_id` per row)          |
| "Recent activity on this one account."                  | `GET /accounts/{id}/transactions`                                       |
| "Reconcile all your contracts against MFS this month."  | `GET /reports/crr` (filter locally by `external_contract_number`)       |

**Heuristics:**

* If the question is a row-shaped business answer ("payments", "returns", "balances"), there's probably a report for it.
* If the question is about a specific entity ("this account", "this person"), use the resource endpoints.
* If the question is "everything about X", drill from the resource endpoint.
* When in doubt, reports are less code — they pre-filter on the server, so you write less pagination code on your side.
