updated: "2026-07-20T23:20:00Z"
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
- Request customer API access from Ratespedia.
- Store your API key only on your server.
- Run the configuration test endpoints first.
- Confirm your client handles success, validation errors, authentication errors, quota errors, and service errors.
- Switch to the production endpoints.
- Use pagination for production search results.
- 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.
| Environment | Method | Path | Purpose |
|---|---|---|---|
| Test | GET | /api/mailed-records/test/search | Validate headers, filters, pagination handling, and error handling. |
| Production | GET | /api/mailed-records/search | Search 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.
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.
| Scenario | Response |
|---|---|
ok | Normal success response. |
empty | Search success with no items. |
invalid_api_key | 401 invalid_api_key. |
rate_limited | 429 rate_limited. |
daily_quota_exceeded | 429 daily_quota_exceeded. |
server_error | 500 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
| Parameter | Type | Required | Notes |
|---|---|---|---|
query | string | No | General search text for names, addresses, or broad narrowing. Maximum 120 characters. |
reference | string | No | Reference lookup. Maximum 120 characters. |
address | string | No | Property-address lookup. Maximum 160 characters. |
state | string | No | Two-letter property state abbreviation. |
city | string | No | Property city. Maximum 80 characters. |
zip | string | No | Five-digit property ZIP code. |
loanType | string | No | Loan type filter. Maximum 40 characters. |
minMarketValue | number | No | Minimum property market value. |
maxMarketValue | number | No | Maximum property market value. |
minLoanAmount | number | No | Minimum first mortgage amount. |
maxLoanAmount | number | No | Maximum first mortgage amount. |
veteranInHousehold | boolean | No | true or false. |
limit | integer | No | Page size. Defaults to 25; maximum is normally 50. |
cursor | string | No | Opaque 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, passpageInfo.nextCursoras the next request'scursor. - 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:
GET /api/mailed-records/portal/searchaccepts a record number only and returns only an opaque record ID. It does not return customer, property, reference, PURL, contact, consent, or loan data.- A successful preview displays only
1 record found. The result requires a valid US phone before enabling Bonzo / CRM export. - 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.
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
| Status | Meaning |
|---|---|
200 | Request succeeded. |
400 | Request is malformed or failed validation. |
401 | API key or portal authentication is missing or invalid. |
405 | The attempted method is not available because the customer API is read-only. |
429 | Rate limit or quota was exceeded. |
500 | Service configuration or temporary service failure. |
Error Codes
| Code | Status | What to do |
|---|---|---|
missing_api_key | 401 | Send Authorization: Bearer <token> or x-api-key: <token>. |
invalid_api_key | 401 | Confirm the key with Ratespedia and retry with the correct environment. |
ambiguous_authentication | 400 | Send only one supported auth header. |
invalid_authorization_header | 401 | Use Authorization: Bearer <token>. |
invalid_json | 400 | Send valid JSON with Content-Type: application/json. |
invalid_body | 400 | Send a JSON object. |
invalid_parameter | 400 | Shorten text filters to the documented limits. |
invalid_zip | 400 | Use a five-digit ZIP code. |
invalid_number | 400 | Use non-negative numeric filter values. |
invalid_boolean | 400 | Use true or false. |
invalid_integer | 400 | Use a valid page-size integer. |
invalid_cursor | 400 | Restart pagination from the first page. |
invalid_range | 400 | Make sure minimum filters do not exceed maximum filters. |
read_only_api | 405 | Remove the write request and use GET /api/mailed-records/search. |
rate_limited | 429 | Retry after at least one minute. |
daily_quota_exceeded | 429 | Wait for the next quota window or contact Ratespedia. |
weekly_quota_exceeded | 429 | Wait for the next quota window or contact Ratespedia. |
monthly_quota_exceeded | 429 | Wait for the next quota window or contact Ratespedia. |
search_failed | 500 | Retry later. Contact Ratespedia if it persists. |
unexpected_error | 500 | Retry 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 type | Default |
|---|---|
| Search rate limit | 60 requests per minute |
| Default search page size | 25 |
| Maximum search page size | 50 |
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
- Confirm you have the correct production base URL.
- Store the API key server-side only.
- Run
GET /api/mailed-records/test/searchwith your client. - Force test scenarios for
401,400,429, and500handling. - Switch your client to production search.
- Confirm production pagination handling with
pageInfo.nextCursor. - Confirm your integration does not send write methods or contact-update payloads.
- Keep request and response logs that redact API keys and personal data.
Troubleshooting
| Symptom | Checks |
|---|---|
401 on test endpoints | Confirm the auth header exists and uses a supported format. |
401 on production endpoints | Confirm the API key is active and belongs to the production environment. |
400 on search | Check parameter names, ZIP format, numeric filters, booleans, and cursor freshness. |
405 read_only_api | Remove the write request and use the production GET search endpoint. |
| Empty production search results | Broaden filters or confirm the expected records are available to your account. |
429 responses | Reduce request rate or contact Ratespedia about quota. |
Support
For access, quota changes, production test records, or integration support, contact Ratespedia through /partners/contact.