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

Authentication

The API uses OAuth 2.0 client credentials. You exchange a client id and a client secret for a short lived bearer token, then send that token on every call.

Environments

EnvironmentToken endpoint
ProductionPOST https://api.appraisalhost.com/v1/oauth/token
SandboxPOST https://sandbox.api.appraisalhost.com/v1/oauth/token

Credentials are per environment. A sandbox credential never works in production, and the reverse.

How credentials are issued

The appraisal management company issues credentials to one of its lender accounts from its own screens, and can revoke them at any time. A credential therefore identifies both the lender and the company, which is why one address serves everyone: you do not tell us where the order belongs, the credential does.

  • The client id starts with cli_ and is safe to log.
  • The client secret is shown once, at creation, and is never displayed again. Store it in a secret manager. If it is lost, the company issues a new one and revokes the old.
  • A credential carries the lender scope in version 1. It can do everything on this reference for that lender account, and nothing outside it.
  • Ask the appraisal management company for production credentials. Ask our integrations team for sandbox credentials.

If you serve several lenders, you hold one credential per lender account and keep them separate. Never share one credential across lenders: it would place their orders on the wrong account.

Getting a token

POST /oauth/token is an OAuth 2.0 client credentials token endpoint. It accepts the standard form encoding and, as an alternative, JSON. A client generated from our contract and a client you write yourself both work, unchanged.

Form encoded, with the credential in the body:

curl -s -X POST https://api.appraisalhost.com/v1/oauth/token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials' \
  -d 'client_id=cli_example_2f8c41' \
  -d 'client_secret=example_secret_do_not_use_9f4b1c77ae'

Form encoded, with the credential in an HTTP Basic header, where the client id is the user name and the client secret is the password:

curl -s -X POST https://api.appraisalhost.com/v1/oauth/token \
  -u 'cli_example_2f8c41:example_secret_do_not_use_9f4b1c77ae' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials'

Or as JSON:

curl -s -X POST https://api.appraisalhost.com/v1/oauth/token \
  -H 'Content-Type: application/json' \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "cli_example_2f8c41",
    "client_secret": "example_secret_do_not_use_9f4b1c77ae"
  }'

The answer is the same either way:

{
  "access_token": "aht_at_7Qk2rX9wTm4ZbN1sV6yH0pL8",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "lender"
}

Errors at this endpoint, and only at this endpoint, follow RFC 6749 rather than the error body used everywhere else, so that a standard OAuth 2.0 client can classify them:

{
  "error": "invalid_client",
  "error_description": "The client id or client secret is not valid.",
  "correlation_id": "req_01J9Z2A7K3M5P8R1T4W6Y9B2D"
}

error is one of invalid_request, invalid_client, invalid_grant, unauthorized_client, unsupported_grant_type or invalid_scope. Every other endpoint answers with the one error body described on the Reference notes page.

The examples on these pages use obviously fake credentials. They will not authenticate anywhere.

Send the token on every other call:

curl -s 'https://api.appraisalhost.com/v1/orders?per_page=5' \
  -H 'Authorization: Bearer aht_at_7Qk2rX9wTm4ZbN1sV6yH0pL8'

What a credential is for

GET /account answers the question a configuration file cannot: which lender account, and which appraisal management company, is this credential actually for.

curl -s https://api.appraisalhost.com/v1/account \
  -H 'Authorization: Bearer aht_at_7Qk2rX9wTm4ZbN1sV6yH0pL8'
{
  "client_id": "cli_example_2f8c41",
  "environment": "production",
  "lender": { "id": "len_4WQ2H8ZKPB31", "name": "Example Mortgage Group" },
  "company": { "id": "cmp_7YT5M2XRND94", "name": "Example Appraisal Management" },
  "scopes": ["lender"],
  "rate_limit": { "requests_per_minute": 600 }
}

Check it at start-up

Do this once per credential, every time your service starts, before it places anything:

  1. Fetch a token with the credential.
  2. Call GET /account.
  3. Check environment is the environment this deployment is for.
  4. Check lender.id and lender.name against your own configuration for that lender. If they do not match, refuse to place orders with that credential and raise an alert.
  5. Log client_id with lender.id, so that a credential mapped to the wrong lender is visible in your logs rather than in somebody's order.

A credential placed against the wrong lender puts that lender's borrower on another lender's account. This check is what stops it.

Token lifetime and refresh

  • A token is valid for 60 minutes. expires_in is the authority, in seconds from the response.
  • Tokens are opaque. Do not parse one, and do not depend on its length or alphabet.
  • Cache the token and reuse it until shortly before it expires. Asking for a token before every call wastes your rate limit.
  • Requesting a new token does not invalidate the old one, so a rolling refresh is safe: fetch a new token at about 55 minutes and swap it in.
  • There is no refresh token. Client credentials are the refresh.

Handle expiry defensively. If a call returns 401 with code token_expired, fetch a new token and retry the call once. If the retry also returns 401 with code invalid_client or client_revoked, stop and alert an operator: the credential has been revoked or replaced.

{
  "code": "token_expired",
  "message": "The access token has expired.",
  "correlation_id": "req_01J9Z41P0R3T6W9Y2B5D8F1H"
}

Rotation and revocation

  • Rotation. The appraisal management company creates a second credential, you deploy it, then the company revokes the first. Nothing is interrupted, because both work during the overlap.
  • Revocation. A revoked client id fails immediately with 401 and code client_revoked. Tokens already issued to it stop working at the same moment.
  • Compromise. If a secret may have leaked, ask the company to revoke it right away and issue a new one. Tell us at integrations@appraisalhost.com so we can check the access log for the credential.

Rate limits

The limit is 600 requests per minute per client, counted across every endpoint. Every response carries your current position:

HeaderMeaning
X-RateLimit-LimitRequests allowed in the current window.
X-RateLimit-RemainingRequests left in the current window.
X-RateLimit-ResetUnix time in seconds when the window resets.

Over the limit, we answer 429 with a Retry-After header in seconds:

{
  "code": "rate_limited",
  "message": "Too many requests. Retry after 30 seconds.",
  "correlation_id": "req_01J9Z48F4H7K0M3P6R9T2W5Y"
}

Wait for Retry-After, then retry. Back off exponentially if you are still limited. Webhooks exist so that you do not have to poll: a webhook subscription plus an occasional reconciliation read is well inside the limit, while polling every order every minute is not.

Idempotency

POST /orders accepts an Idempotency-Key header, and you should always send one. Use a value that is unique to the order in your own system, for example los-88421-create-1.

  • A key is scoped to your client credential and to the endpoint that accepts it. The same key sent by a different credential is a different key.
  • We remember the key and its result for 24 hours.
  • A repeat with the same key and the same body returns the original order, with the same id and the same 201. Nothing is created twice.
  • A repeat with the same key and a different body is refused with 409 and code idempotency_key_reused.
  • After 24 hours the key is forgotten, so do not rely on it as a permanent duplicate guard. Store the returned id against your loan file instead.
  • POST /orders is the call that takes a key. The other writes carry none, so a request of theirs that timed out is settled by reading rather than by sending it again: read GET /orders/{order_id}/messages, GET /orders/{order_id}/documents or GET /orders/{order_id}/revision-requests and look for what you sent.
curl -s -X POST https://api.appraisalhost.com/v1/orders \
  -H 'Authorization: Bearer aht_at_7Qk2rX9wTm4ZbN1sV6yH0pL8' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: los-88421-create-1' \
  -d @order.json

Transport and hygiene

  • HTTPS only, TLS 1.2 or better. Plain HTTP is refused.
  • Never put a client id or secret in a URL, a query string, or a browser. A browser cannot keep a secret, so the API is called from your server.
  • We do not set cookies and do not keep a session. Every call carries its own token.
  • Log the correlation_id from every error. It is the fastest way for our integrations team to find your request.
Appraisal Host APISandbox
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