Appearance
Recipe: Client, lender, or lead database
Nightly (or hourly) sync from Salesforce, HubSpot, a LOS, or your own client table into Appraiser Flow. The office keeps working in Clients and CRM. Your system remains the source of the external id.
1. Key
Settings → Public API → Create API Key, environment Test.
| You are loading | Scopes |
|---|---|
| Companies only | organizations:read, organizations:write |
| Companies and leads | those, plus leads:read, leads:write |
| Status after the office opens an order | orders:read |
Store AF_API and AF_API_KEY on the worker, not in the CRM’s client-side automation.
2. Map columns
| Your column | Appraiser Flow field | Notes |
|---|---|---|
| Account or company id | external_reference | Required for safe updates. Keep it stable. |
| System name | external_source | Example: salesforce, hubspot, encompass |
| Company name | name | Exact name is the last-resort match if the id is new |
| Lender vs AMC vs other | client_type | lender, amc, private_party, or other |
| Main phone | phone | 10-digit US. Stored as (615) 555-0100 |
| Main email | email | |
| Website | website | Required for every type except lender |
| Street, city, state, postal | address_line1, city, state, postal_code | Required for every type except lender. state is two letters |
| Your account number | account_number | Used as a match when external_reference is new |
| Person id | contacts[].external_reference | |
| Person name, email, phone | contacts[].display_name, email, phone | |
| Role | contacts[].contact_type | order_assigner, account_manager, billing, or other |
| Deal id | lead external_reference | |
| Deal name | lead title | |
| Pipeline stage | lead stage | lead, qualified, proposal, won, lost |
| Expected fee in cents | lead amount_cents | Engagement fee, not a property value |
Prefix ids if two systems could collide: salesforce:001xx, hubspot:8841.
3. Upsert companies, then leads
Send companies first so a lead can use organization_external_reference. A lead may instead include a nested organization, which needs organizations:write and creates the company in the same call.
Batch up to 100 companies per request. One bad business row returns action: "error" and the other rows still save. An invalid phone or unknown client_type fails the whole request before any row is written, so validate those before you POST.
python
import os
import uuid
import requests
API = os.environ["AF_API"].rstrip("/") + "/public/v1"
HEADERS = {
"Authorization": f"Bearer {os.environ['AF_API_KEY']}",
"Content-Type": "application/json",
}
def upsert_companies(rows: list[dict]) -> None:
response = requests.post(
f"{API}/organizations/batch",
headers={**HEADERS, "Idempotency-Key": uuid.uuid4().hex},
json={"items": rows},
timeout=60,
)
response.raise_for_status()
for item in response.json()["results"]:
if item["action"] == "error":
print(item["external_reference"], item["error_code"], item["message"])
def upsert_lead(deal: dict) -> None:
response = requests.post(
f"{API}/leads",
headers={**HEADERS, "Idempotency-Key": uuid.uuid4().hex},
json=deal,
timeout=30,
)
response.raise_for_status()Use a new idempotency key for each batch attempt with a new body. Keep external_reference stable across nights. Reusing one idempotency key with a changed body returns 409.
4. Check the office
Open Clients and the company you sent. External Reference should match the source id. Open CRM for the lead’s stage.
Poll an existing order when the lender portal needs status:
http
GET /public/v1/orders?loan_number=LN-4421
Authorization: Bearer af_test_…That call needs orders:read. It returns status, due date, engagement fee, and address. It does not return internal notes or an appraisal value.
5. Promote
Create a Live key with the same scopes, point the worker at https://api.appraiserflow.ai, deploy, then revoke the test key if staging should stop.