Appraisal Host - Appraisal Management Software for AMCs and Lenders

Developer documentation

OpenAPI 3.1 spec

Guide

Appraisal Host APIAuthenticationSandboxOrder lifecycleWebhooksReference notesChangelogSupport

API reference

Every endpoint

Reference notes

The conventions that apply everywhere, in one place. The machine readable contract is at /developers/spec/openapi.json (OpenAPI 3.1): generate a client from it, import it into your tooling, or read it alongside these pages.

Base URLs

EnvironmentBase URL
Productionhttps://api.appraisalhost.com/v1
Sandboxhttps://sandbox.api.appraisalhost.com/v1

HTTPS only, TLS 1.2 or better. The version is the first path segment.

Identifiers

Identifiers are opaque prefixed strings of up to 64 characters. Store them as strings, compare them exactly, and do not parse them.

PrefixResource
ord_An order
doc_A document
msg_A message, or a reply on a revision request
rr_A revision request
evt_An event
cli_An API client, which is your client id
len_A lender account, from GET /account
cmp_An appraisal management company, from GET /account

order_number is different: it is the appraisal management company's own order number, the one a person quotes on the phone. It is unique within that company, not across the platform, so key your records on id.

Event ids are lexicographically sortable: compare them as strings, and they come out in the order the events were raised. That is what makes the event cursor reliable. Do not decode one, and do not assume any other id sorts or carries meaning.

Dates, times and money

  • Timestamps are ISO-8601 with an explicit UTC offset: 2026-09-17T14:32:05Z. We always send UTC. Send UTC.
  • A date without a time is YYYY-MM-DD, and means a calendar date in the property's own local time. Due dates and appointment dates are of this kind.
  • Money is a decimal string with two places, plus a separate three letter currency code: "appraisal_fee": "525.00", "currency": "USD". We never use a floating point number for money, and neither should your parser. Every money field matches ^-?\d+\.\d{2}$.
  • Phone numbers are text. Include the country code, as in +1-217-555-0142 or +15085550142, and read back what you sent.
  • On a contact, type is phone or email and role says whose it is. On an email address use home, work or other.
  • Booleans are true and false, never "Y", "1" or "yes".
  • A field that applies to a resource is sent even when it has no value, as null. Code defensively all the same: treat a missing key as null rather than as an error, and ignore fields you do not know. New fields may appear at any time.

Pagination

Every list endpoint except the event feed uses page numbers:

{
  "data": [],
  "page": 1,
  "per_page": 25,
  "total": 138,
  "total_pages": 6
}
  • page starts at 1. per_page is 1 to 100, and defaults to 25.
  • A page past the end returns an empty data array, not a 404.
  • The event feed uses a cursor instead: pass next_cursor back as after until it comes back null. See Webhooks.

The order results come back in

Every list is ordered on a stable key:

EndpointOrdered by
GET /orderscreated_at descending, with id as the tie break
GET /orders/{order_id}/documentscreated_at descending, with id as the tie break
GET /orders/{order_id}/revision-requestscreated_at descending, with id as the tie break
GET /orders/{order_id}/messagescreated_at ascending
GET /eventscreated_at ascending, which is also event id order
GET /order-typescode ascending

Page numbers walk a collection that can change while you are reading it, so an order touched between your first page and your last can move. When you page with updated_since to reconcile, walk every page quickly, then run the same window again and compare the two runs: when they agree, nothing moved underneath you. Never assume a row cannot move between pages.

Errors

Every error, on every endpoint, has the same body:

{
  "code": "validation_failed",
  "message": "The request could not be processed. See details.",
  "details": [
    { "field": "property.postal_code", "code": "required", "value": null, "message": "A postal code is required." },
    { "field": "loan.purpose", "code": "invalid_value", "value": "cash_out", "message": "Must be one of: construction_only, construction_to_permanent, mortgage_modification, other, purchase, refinance." }
  ],
  "correlation_id": "req_01J9Z40M8P1R4T7W0Y3B6D9F"
}
  • code is stable. Branch on it.
  • message is plain text for a person, and may be reworded at any time. Never match on it.
  • details names each field at fault, with a dotted path such as borrowers.0.last_name. details[].code is stable, and details[].value carries the machine readable value at fault when there is one. Both are safe to branch on.
  • current_status is a top level field on every 409 that refuses an action because of where the order has got to. It holds the order's current status code, so you can branch on it without walking details.
  • correlation_id identifies the request in our logs. Log it, and quote it when you contact us.

Status codes

StatusMeaningWhat to do
400The request could not be read: malformed JSON, a missing required field, a value of the wrong type.Fix the request. Do not retry unchanged.
401No token, an invalid token, an expired token, or a revoked client.Refresh the token and retry once. Stop and alert on invalid_client or client_revoked.
403The credential is valid but not allowed to do this: the action is switched off for this client, or the lender account is not one the company invoices (account_not_billable).Do not retry. Ask the appraisal management company.
404No such resource, or one this credential is not entitled to see.Do not retry. We answer 404 rather than 403 for another account's resource so that ids cannot be probed.
409A conflict with current state: an action the status does not allow, an amendment to a closed order, a reused idempotency key.Read the order and decide. Do not retry blindly.
413The upload is larger than 25 MB.Send a smaller file.
415Unsupported Content-Type, or a file type we do not accept.Fix the request.
422Well formed, but the values cannot be used: a product this lender may not order, a loan type that does not apply, a due date in the past, a missing acknowledgement.Fix the values. details says which.
429Over the rate limit.Wait for Retry-After, then retry with backoff.
500A failure on our side.Retry a read. Retry a create with the same Idempotency-Key. For any other write, read the order first: it may have been applied. Quote the correlation_id if it continues.
503The API is not serving requests for a moment, for example during planned maintenance.Wait for Retry-After, then retry. Nothing you sent was applied.

Common error codes

validation_failed, semantic_validation_failed, invalid_client, client_revoked, token_expired, not_found, action_not_allowed_in_status, action_not_permitted_for_client, account_not_billable, recipient_not_allowed, idempotency_key_reused, payload_too_large, unsupported_media_type, rate_limited, webhook_not_configured, service_unavailable, internal_error.

account_not_billable is the one to read before you build: API ordering requires a lender account the appraisal management company invoices, and a create on an account that pays per order by card is refused with 403 and that code.

Rate limits

600 requests per minute per client, across all endpoints. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset, and a 429 carries Retry-After in seconds. See Authentication.

Idempotency and retries

  • POST /orders accepts Idempotency-Key, remembered for 24 hours. Always send one. The key is scoped to your credential and to that endpoint, and a replay returns the original order with the original 201.
  • Every GET is safe to retry. PUT /webhook is idempotent by nature.
  • POST actions (hold, resume, cancel) are safe to retry: an action the order has already taken returns 409 rather than doing it twice.
  • A message, an upload and a revision request take no key. If one of those times out, read the thread, the document list or the revision request list before you send it again, or you will create a second one.
  • Retry 429, 500 and 503 with exponential backoff and jitter. Do not retry 400, 403, 404, 409, 413, 415 or 422 without changing the request.

Versioning and deprecation

  • The major version is in the path. Today that is v1.
  • Within v1 we make additive changes only: new endpoints, new optional request fields, new response fields, new enum values, new event types. Your integration must tolerate all five. In particular, treat an unknown status code, document kind or event type as something to store and ignore, not as an error.
  • We will never, within v1, remove a field, rename one, change a field's type, change the meaning of an existing enum value, or make an optional request field required.
  • A breaking change means a new major version at a new path, and 12 months of notice before v1 is retired. We publish the notice on the Changelog and write to the technical contact on every active credential.
  • api_version on every webhook payload tells you which version of the contract that payload was built to.
  • The contract file carries the same version in info.version. Diff it between releases to see exactly what changed.

Field naming and enums

Field names are snake_case. Enum values are lower case with underscores. The enums you will meet most:

  • Loan purpose: construction_only, construction_to_permanent, mortgage_modification, other, purchase, refinance. Send other with loan.purpose_other.
  • Property type: church, commercial_non_residential, condominium, condominium_over_four_stories, cooperative, farm, home_and_business_combined, manufactured_mobile_home, mixed_use_residential, multifamily_more_than_four_units, other, single_family, townhouse, two_to_four_unit_property, vacant_land.
  • Occupancy: investment_property, primary_residence, second_home, vacant.
  • Contact type and role: phone or email, and home, cell, work or other.
  • Note type: order or appointment.
  • Additional contact purpose: report or status.
  • Report format: uad_2_6 or uad_3_6.
  • Appraiser certification on a product: none, fha_required, fha_optional or usda.
  • Document purpose on an upload: engagement_letter, sales_contract, prior_report or other.
  • Order status codes and document kinds: see Order lifecycle.

Order type codes, loan type codes and add-on codes are the appraisal management company's own, and come from GET /order-types. They are stable for that company. Do not write them into your source, and do not assume one company's codes appear at another.

Data we do not accept

  • Card numbers or any payment instrument. Orders are billed to the lender account the credential belongs to.
  • Credentials in a URL or a query string.
  • Real borrower data in the sandbox.

Downloading the contract

  • JSON: https://www.appraisalhost.com/developers/spec/openapi.json
  • The version of the contract is info.version, and the current value is 1.0.0.

Generate your client from the contract rather than hand writing models. The contract is the interface we hold ourselves to: where behavior and contract disagree, the contract is the defect report, and we fix one or the other and record it in the Changelog.

WebhooksChangelog
Appraisal Host - Appraisal Management Software for AMCs and Lenders

Platform

  • Features
  • Integrations
  • Pricing

Solutions

  • For AMCs
  • For Lenders
  • For Appraisal Companies
  • For Non-QM Lenders
  • For Banks
  • For Credit Unions

Resources

  • Tools
  • Developers
  • Blog
  • FAQ
  • Support
  • Compliance & Regulations

Company

  • About
  • Contact

Appraisal Host

1 Washington Mall #1105

Boston, MA 02108

© 2026 Appraisal Host

|Privacy Policy|Terms of Service|SMS Consent

appraisalhost.com is a service of Appraisal Host LLC.

Powered byOptiWork.ai