Mailed Records API

API Documentation

Production endpoints, configuration tests, response contracts, quotas, and error handling for integrations that search approved mailed-record fields.

Base URL
https://www.ratespedia.com
Authentication
Authorization: Bearer <token>
Safe setup
/api/mailed-records/test/*
GETProduction search
/api/mailed-records/search

Paginated search over approved mailed-record fields.

GETConfiguration search test
/api/mailed-records/test/search

Validates headers and filters without production data.

Configuration tests never touch production data.

Use the `/test` endpoints to validate headers, query parameters, JSON bodies, and error handling before switching to the production routes.

Partner home

updated: "2026-10-08T23:08:23Z"

Customer-facing reference for Ratespedia's read-only Mailed Records API. Use this guide to configure authentication, test your client safely, search approved mailed-record results, and handle every expected response.

Overview

The API gives approved customers a controlled, retrieval-only way to search mailed-record results. It supports two customer access paths:

  • Direct API access for server-side integrations.
  • The Mailed Records Portal for browser-based teams that should not handle API keys.

Canonical routes:

API docs: /partners/mailed-records-api
Portal:   /partners/mailed-records-portal

Base URL

https://www.ratespedia.com

Customer Setup Flow

  1. Request customer API access from Ratespedia.
  2. Store your API key only on your server.
  3. Run the configuration test endpoints first.
  4. Confirm your client handles success, validation errors, authentication errors, quota errors, and service errors.
  5. Switch to the production endpoints.
  6. Use pagination for production search results.
  7. Treat returned records as read-only. API keys cannot create, update, or delete database records.

Environments And Endpoints

Configuration tests return deterministic responses and do not read or update production records.

EnvironmentMethodPathPurpose
TestGET/api/mailed-records/test/searchValidate headers, filters, pagination handling, and error handling.
ProductionGET/api/mailed-records/searchSearch approved mailed-record results.

Read-only Contract

Customer API keys permit retrieval only. PATCH /api/mailed-records/contact and PATCH /api/mailed-records/test/contact return 405 read_only_api before authentication lookup, request-body parsing, quota accounting, or database code can run. The response includes Allow: GET and directs integrations to the production search route.

{
  "success": false,
  "error": "Mailed Records API keys support retrieval only. Use GET /api/mailed-records/search.",
  "code": "read_only_api"
}

Authenticated portal and CRM workflows are separate from customer API keys.

Historical reference behavior

Every current or historical mailhouse reference resolves to the canonical mailed record. A legacy reference therefore opens the same current record as its newest reference instead of surfacing an older physical duplicate. Portal exports load the most recent reference from the record's indexed mail-event history; they do not scan the complete cross-record reference view.

Authentication

Direct API requests require one API key. Send it with either supported header.

Recommended:

Authorization: Bearer <token>

Also supported:

x-api-key: <token>

Do not send both headers in the same request. Do not place API keys in browser code, URLs, screenshots, or client-side logs.

Configuration Test Endpoints

Use the search test endpoint during initial setup, credential rotation, and client changes. It validates authentication headers and query filters without touching production records.

Test responses include:

{
  "test": {
    "mode": "configuration_test",
    "header": "Authorization",
    "tokenPreview": "test...1234",
    "credentialValidated": false,
    "productionDataTouched": false
  }
}

credentialValidated: false means the test endpoint accepted the header format but did not verify that the key is active for production. Production endpoints validate the actual key.

Test Search

GET /api/mailed-records/test/search

Example:

curl -H "Authorization: Bearer $RATESPEDIA_API_KEY" \
  "https://www.ratespedia.com/api/mailed-records/test/search?state=TX&query=Main%20Street&limit=25"

Successful response:

{
  "success": true,
  "test": {
    "mode": "configuration_test",
    "header": "Authorization",
    "tokenPreview": "test...1234",
    "credentialValidated": false,
    "productionDataTouched": false
  },
  "items": [
    {
      "mailedRecordId": "11111111-1111-4111-8111-111111111111",
      "reference": "TEST-REF-0001",
      "mailhouseRef": "TEST-REF-0001",
      "owner": {
        "firstName": "Jordan",
        "lastName": "Sample",
        "address": "100 Owner Example Way",
        "city": "Austin",
        "state": "TX",
        "zip": "78701",
        "email": "jordan.sample@example.com",
        "phone": "+1 512-555-0199",
        "optInLanguage": "Configuration test consent language.",
        "trustedFormCertUrl": "https://cert.trustedform.com/example-test-certificate"
      },
      "property": {
        "address": "200 Property Example Ave",
        "city": "Austin",
        "state": "TX",
        "zip": "78701",
        "zip4": null,
        "type": "SFR",
        "marketValue": 425000,
        "purchasePrice": 315000,
        "lengthOfOwnership": 6
      },
      "loan": {
        "amount": 280000,
        "rate": 3.625,
        "lender": "Configuration Test Lender",
        "originationDate": "2021-02-01",
        "type": "CONVENTIONAL",
        "loanToValue": 65.9,
        "veteranInHousehold": false
      }
    }
  ],
  "pageInfo": {
    "limit": 25,
    "returned": 1,
    "hasNextPage": true,
    "nextCursor": "opaque-test-token"
  },
  "appliedFilters": {
    "query": "Main Street",
    "state": "TX",
    "limit": 25,
    "hasCursor": false
  }
}

Forced Test Scenarios

Use scenario to confirm your error handling before production traffic.

ScenarioResponse
okNormal success response.
emptySearch success with no items.
invalid_api_key401 invalid_api_key.
rate_limited429 rate_limited.
daily_quota_exceeded429 daily_quota_exceeded.
server_error500 search_failed.

Example:

curl -H "Authorization: Bearer $RATESPEDIA_API_KEY" \
  "https://www.ratespedia.com/api/mailed-records/test/search?scenario=rate_limited"

Production Search

GET /api/mailed-records/search

Purpose: search approved mailed-record results with filters and cursor pagination.

Query Parameters

ParameterTypeRequiredNotes
querystringNoGeneral search text for names, addresses, or broad narrowing. Maximum 120 characters.
referencestringNoReference lookup. Maximum 120 characters.
addressstringNoProperty-address lookup. Maximum 160 characters.
statestringNoTwo-letter property state abbreviation.
citystringNoProperty city. Maximum 80 characters.
zipstringNoFive-digit property ZIP code.
loanTypestringNoLoan type filter. Maximum 40 characters.
minMarketValuenumberNoMinimum property market value.
maxMarketValuenumberNoMaximum property market value.
minLoanAmountnumberNoMinimum first mortgage amount.
maxLoanAmountnumberNoMaximum first mortgage amount.
veteranInHouseholdbooleanNotrue or false.
limitintegerNoPage size. Defaults to 25; maximum is normally 50.
cursorstringNoOpaque cursor from pageInfo.nextCursor.

Production Search Example

curl -H "Authorization: Bearer $RATESPEDIA_API_KEY" \
  "https://www.ratespedia.com/api/mailed-records/search?state=TX&query=Main%20Street&limit=25"

Production Search Response

{
  "success": true,
  "items": [
    {
      "mailedRecordId": "uuid",
      "reference": "ABC123",
      "mailhouseRef": "ABC123",
      "owner": {
        "firstName": "Jane",
        "lastName": "Doe",
        "address": "123 Owner St",
        "city": "Dallas",
        "state": "TX",
        "zip": "75001",
        "email": "jane@example.com",
        "phone": "+1 555-555-0100",
        "optInLanguage": "Customer consent language.",
        "trustedFormCertUrl": "https://cert.trustedform.com/example"
      },
      "property": {
        "address": "456 Property Ave",
        "city": "Dallas",
        "state": "TX",
        "zip": "75001",
        "zip4": null,
        "type": "SFR",
        "marketValue": 450000,
        "purchasePrice": 320000,
        "lengthOfOwnership": 7
      },
      "loan": {
        "amount": 280000,
        "rate": 3.625,
        "lender": "Example Bank",
        "originationDate": "2021-02-01",
        "type": "CONVENTIONAL",
        "loanToValue": 62.2,
        "veteranInHousehold": false
      }
    }
  ],
  "pageInfo": {
    "limit": 25,
    "returned": 25,
    "hasNextPage": true,
    "nextCursor": "opaque-token"
  },
  "appliedFilters": {
    "query": "Main Street",
    "state": "TX",
    "limit": 25,
    "hasCursor": false
  }
}

Pagination

Search uses cursor pagination.

  • Read pageInfo.hasNextPage.
  • If it is true, pass pageInfo.nextCursor as the next request's cursor.
  • Treat cursors as opaque. Do not parse, edit, or store them as business identifiers.
  • Keep the same filters when requesting the next page.

Portal Access

Customers who need browser access can use:

/partners/mailed-records-portal

Use the portal when your team needs browser access without handling API keys. Ratespedia will provide the customer login or setup instructions separately.

BRONCO owns personal portal-user administration. For normal setup and recovery, BRONCO sends a one-time setup link to /partners/mailed-records-portal/setup-password. For support resets, BRONCO can assign a temporary password by Supabase Auth admin API and mark the user with ratespedia_force_password_change / ratespedia_password_reset_required in Auth app_metadata. A user with either flag can authenticate with the temporary password, but the portal redirects them to the setup-password screen and API session/search/export routes reject normal portal access with 403 password_change_required until the permanent password is saved. The setup-password API explicitly overwrites those flags to false and clears the reset metadata after the successful password update. Do not rely on omitting or deleting metadata keys in the update payload; Supabase Auth metadata updates can preserve omitted keys, which would keep the user in the setup-password loop. The portal may use client-side metadata to route a fresh temporary-password sign-in to setup-password, but existing browser sessions must ask the server before redirecting so stale session metadata cannot force a loop after the server-side flags have been cleared. The setup-password page also checks the server before rendering the form for an active browser session; if the server says the reset requirement is already cleared, the page routes the user back to the portal instead of asking for another password.

The portal also supports self-service password resets from the login screen. The Forgot Password link collects the registered portal email, sends a 6-digit verification code to active portal members, and allows a new password only after that code is verified. Reset codes are stored server-side as HMAC hashes in mailed_records_portal_password_reset_codes, expire after 15 minutes, and are consumed after success or too many failed attempts. Self-service request, delivery, confirmation failure, and success events are written to the mailed records API audit log with resetType: self_service.

Portal lookup is a two-stage disclosure flow for every organization:

  1. GET /api/mailed-records/portal/search accepts a record number only and returns only an opaque record ID. It does not return customer, property, reference, PURL, contact, consent, or loan data.
  2. A successful preview displays only 1 record found. The result requires a valid US phone before enabling Bonzo / CRM export.
  3. Full customer and loan details are returned only after the downstream CRM confirms delivery. Invalid phone, rejected delivery, timeout, or a changed preview returns no full record.
  4. When the record's artwork code maps to an uploaded BRONCO mailer asset, the authorized response includes short-lived signed URLs for its WebP preview pages. The portal renders those pages below the revealed record details. PDF originals remain private, and records without an uploaded preview render no artwork section.

The CRM delivery payload sends the property street address, city, state, and ZIP as distinct fields. The street-address field must not contain the city, state, or ZIP. When an individual property-address component is unavailable, delivery falls back to the corresponding owner mailing-address component. ZIP values are normalized to exactly five digits at the CRM boundary. A ZIP+4 value contributes its first five digits; a standalone four-digit extension is ignored and the alternate property or owner ZIP is checked instead.

Portal searches and exports resolve the current canonical mailed_records row. An older mail reference identifies that row through mail history; it does not restore the field values from the earlier drop. The legacy export route reloads the row immediately before delivery and accepts only the newly entered phone from the browser. Name, email, consent, certificate, address, reference, PURL, and loan fields come from that server-side reload.

For controlled organizations, the preview lookup is restricted to exact approved mailed_records.customer scope rows. Export uses POST /api/mailed-records/portal/search with the previewed mailedRecordId, record number, phone, and a browser-generated UUID requestId. The server re-runs the scoped lookup and refuses delivery if it no longer matches the previewed record. A confirmed operation writes immutable delivery evidence and one billing event before returning the full record.

Reusing the same request ID with identical input returns the stored terminal result and does not post or charge twice. Reusing it with different member, previewed record, phone, or record number is rejected. A Bonzo timeout is delivery_unknown: no full record is revealed, no charge is recorded, and the request must not be automatically retried.

For a controlled organization, the separate portal PATCH .../portal/contact and POST .../portal/bonzo-export routes still return 409 controlled_search_required. The preview GET remains available but is scope-restricted and disclosure-limited. Flags-off organizations use the same limited preview and retain the separate phone-gated export route during rollout.

Portal contact and CRM delivery operations remain organization-authenticated and are not available to API-key callers.

Agency And Member Activity Attribution

Portal audit rows preserve organization ID, organization-member ID, auth-user ID, user email, and event-time organization/member labels. Controlled operation rows preserve the same labels for confirmed exports. BRONCO reads the service-role-only portal_member_activity_daily_v view to report activity by Eastern date, agency, and banker.

  • Search count: action = 'search' with HTTP 200, including valid no-match previews. Validation, authorization, rate-limit, and server failures do not count.
  • Legacy export count: action = 'export_bonzo' with HTTP 200 and confirmed downstream success.
  • Controlled export count: billing_events.event_type = 'charge' tied to a delivered operation. Failed, unknown, replayed, deduplicated, and reversed operations do not increase the metric.

The aggregate view contains staff attribution and counts only. It does not expose customer names, addresses, phones, references, record IDs, export payloads, or webhook URLs.

Error Format

All API errors use this JSON shape:

{
  "success": false,
  "error": "Human-readable message.",
  "code": "machine_readable_code"
}

Some errors may include a details object when safe to expose.

Status Codes

StatusMeaning
200Request succeeded.
400Request is malformed or failed validation.
401API key or portal authentication is missing or invalid.
405The attempted method is not available because the customer API is read-only.
429Rate limit or quota was exceeded.
500Service configuration or temporary service failure.

Error Codes

CodeStatusWhat to do
missing_api_key401Send Authorization: Bearer <token> or x-api-key: <token>.
invalid_api_key401Confirm the key with Ratespedia and retry with the correct environment.
ambiguous_authentication400Send only one supported auth header.
invalid_authorization_header401Use Authorization: Bearer <token>.
invalid_json400Send valid JSON with Content-Type: application/json.
invalid_body400Send a JSON object.
invalid_parameter400Shorten text filters to the documented limits.
invalid_zip400Use a five-digit ZIP code.
invalid_number400Use non-negative numeric filter values.
invalid_boolean400Use true or false.
invalid_integer400Use a valid page-size integer.
invalid_cursor400Restart pagination from the first page.
invalid_range400Make sure minimum filters do not exceed maximum filters.
read_only_api405Remove the write request and use GET /api/mailed-records/search.
rate_limited429Retry after at least one minute.
daily_quota_exceeded429Wait for the next quota window or contact Ratespedia.
weekly_quota_exceeded429Wait for the next quota window or contact Ratespedia.
monthly_quota_exceeded429Wait for the next quota window or contact Ratespedia.
search_failed500Retry later. Contact Ratespedia if it persists.
unexpected_error500Retry later. Contact Ratespedia if it persists.

Rate Limits And Quotas

Ratespedia enforces short-term rate limits and longer quota windows. Exact customer limits are assigned during onboarding.

Default behavior commonly used by the API:

Limit typeDefault
Search rate limit60 requests per minute
Default search page size25
Maximum search page size50

If you receive 429, slow your request rate and retry later. If you consistently hit quota limits, contact Ratespedia to review your plan.

Customer Go-Live Checklist

  1. Confirm you have the correct production base URL.
  2. Store the API key server-side only.
  3. Run GET /api/mailed-records/test/search with your client.
  4. Force test scenarios for 401, 400, 429, and 500 handling.
  5. Switch your client to production search.
  6. Confirm production pagination handling with pageInfo.nextCursor.
  7. Confirm your integration does not send write methods or contact-update payloads.
  8. Keep request and response logs that redact API keys and personal data.

Troubleshooting

SymptomChecks
401 on test endpointsConfirm the auth header exists and uses a supported format.
401 on production endpointsConfirm the API key is active and belongs to the production environment.
400 on searchCheck parameter names, ZIP format, numeric filters, booleans, and cursor freshness.
405 read_only_apiRemove the write request and use the production GET search endpoint.
Empty production search resultsBroaden filters or confirm the expected records are available to your account.
429 responsesReduce request rate or contact Ratespedia about quota.

Support

For access, quota changes, production test records, or integration support, contact Ratespedia through /partners/contact.

Portal Contractor Agreement (2026-10-06)

Ratespedia #161 and centralized BRONCO #155 cover this feature. Personal portal users must accept version 2026-10-06 before accessing records and renew after exactly 720 elapsed hours. Shared organization credentials cannot satisfy per-user acceptance. Agreement bootstrap/session endpoints remain available before acceptance; data routes enforce agreement status in the common authentication helper. The browser hides data and refreshes status on focus/visibility and expiry.

The supplied text is immutable and SHA-256 verified. portal_agreement_versions preserves exact text; portal_agreement_acceptances records verified Auth ID/email, typed signer name, organization/member IDs, organization label, server timestamp, IP and user agent. RLS is enabled with service-role-only table/RPC access. Evidence is append-only and has no cascading user/member/company foreign keys. Concurrent requests and retries serialize by user/company and reuse an unexpired acceptance without extending its deadline. A new version requires fresh acceptance.

BRONCO User Management shows current status, last acceptance, next due date, and paginated history including exact accepted text and digest. Its history API requires trusted app_metadata BRONCO staff roles or Azure provider, never user-editable metadata. No CRM payload, API-key, public landing, or PURL change.

Deploy the additive migration 20261006041448_portal_user_agreement_acceptance.sql, then BRONCO, then Ratespedia. Roll back application releases first; keep signature evidence. SQL tests run against isolated PGlite; live SQL smoke uses a rolled-back transaction. Required verification includes focused tests, typecheck, production builds, changed-file lint, and BRONCO Direct Mail docs gate.

User Agreement presentation revision (2026-10-06)

Follow-up #161/#155 publishes immutable version 2026-10-06.2. Source: src/content/agreements/user-agreement.txt. AgreementDocument renders numbered sections, subsections and clauses without dropping contractual lines. Remove only the instructed footer; name the document User Agreement. Signing uses the explicit checkbox and authenticated personal account; the server derives the signature label from auth.memberLabel or verified email, ignoring names submitted by the browser. No typed name field is required. BRONCO current status follows the new version, while prior text and signatures remain intact. Migration 20261006044345 adds only the revised document; apply before both app deployments. The generator takes explicit source/version/new-migration paths and does not regenerate the original applied schema migration. Regression checks include exact line preservation, footer removal, spoofed signer names, account-label/email fallback, historical version preservation, SQL renewal rules and responsive browser consent/reload.

Compact User Agreement presentation (2026-10-06)

Follow-up #161 replaces the contents navigation and its scroll tracking with a centered, single-column agreement. The card is max-w-3xl and 60dvh tall with a 28rem usability minimum on short viewports; its header and consent footer remain outside the flexible inner scroll area. Softer headings, compact account metadata, and the Agree and continue action reduce visual intensity. Header and consent footer use slate-100 (slate-950/60 in dark mode) to give the reading area a subtle visual frame. Exact contractual text/version, read-to-end gating, hover guidance, thirty-day renewal, and acceptance evidence remain unchanged.

Agreement read-to-end requirement (2026-10-06)

Follow-up #161 keeps the acceptance action disabled until the inner agreement panel reaches its bottom and consent is checked. PortalAgreement also guards form submission, latches the reached-end state for the loaded document, and resets it together with consent whenever the document reloads. ResizeObserver handles text reflow and agreements that fit fully within the panel. An accessible status explains the requirement. This is a browser interaction rule; it does not alter immutable agreement text/version or server acceptance evidence.

The consent checkbox is also disabled until the agreement end is reached. Its focusable wrapper shows the exact tooltip Please review the entire user agreement on hover, keyboard focus, or touch, and Escape dismisses it. Tooltip state resets with document loading; the checkbox and acceptance action remain disabled while saving.

Mobile client gateway

The personal native client uses the fixed /api/mobile/portal/* allowlist documented in mobile-portal.md. It reuses the existing handler contracts and per-member CRM receiver; shared credentials cannot establish mobile identity. The sidecar audit contains no customer records. The status endpoint is read-only and owner-scoped. Full records and CSV remain gated by confirmed delivery.