Foyer API pages

Foyer API · Recipe

Auto-approve callers from my CRM

Your clients should not have to explain themselves to a screener. This script reads your CRM's contact export and approves each number, so those callers ring straight through.

You need: Concierge or Chief of Staff, a key with contacts:read and contacts:write, and a CSV export from your CRM with a name and a phone number column. Most CRMs can export contacts as CSV; you can also read from your CRM's own API instead of the file.

How it works

  1. Check that your plan allows changes: GET /v1/me returns access.writes_allowed.
  2. Read the numbers Foyer already treats as contacts: GET /v1/contacts, page by page.
  3. For each new number in the export, call POST /v1/contacts/approve with the number and name. It is the same as choosing Through on a contact in the app.

Approving a number also clears a block on it. Review the export first so you do not let through someone you blocked on purpose.

The script

foyer_approve_from_crm.py
import csv
import os
import sys
import time

import requests

API = "https://api.hifoyer.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['FOYER_API_KEY']}"}
CSV_PATH = sys.argv[1] if len(sys.argv) > 1 else "crm-export.csv"


def call(method, path, **kwargs):
    while True:
        resp = requests.request(method, f"{API}{path}", headers=HEADERS, timeout=30, **kwargs)
        if resp.status_code == 429:
            time.sleep(int(resp.headers.get("Retry-After", "5")))
            continue
        resp.raise_for_status()
        return resp.json()


if not call("GET", "/me")["data"]["access"]["writes_allowed"]:
    sys.exit("Approving callers needs Concierge or Chief of Staff.")

known, cursor = set(), None
while True:
    page = call("GET", "/contacts", params={"limit": 100, **({"cursor": cursor} if cursor else {})})
    known.update(c["number"] for c in page["data"])
    cursor = page["next_cursor"]
    if not cursor:
        break

with open(CSV_PATH, newline="") as f:
    for row in csv.DictReader(f):
        number, name = row.get("Phone", "").strip(), row.get("Name", "").strip()
        if not number or number in known:
            continue
        call("POST", "/contacts/approve", json={"number": number, "name": name} if name else {"number": number})
        known.add(number)
        print("Approved", name or number)
        time.sleep(1.1)  # stay under 60 requests a minute

Change "Phone" and "Name" to your export's column names. US numbers may leave out +1. Approving a number that is already approved is safe, but a name you send replaces the name Foyer has saved for that number.

Run it once, or nightly to pick up new clients. It only adds approvals; it never removes one.

All are in the endpoint reference.