{"openapi":"3.1.0","info":{"title":"Foyer API","version":"1.0.0","summary":"Read and manage a Foyer member's screened calls, voicemails, contacts and settings.","description":"Foyer is an AI phone call screener. A member forwards their phone line to\nFoyer; Foyer answers callers it does not know, asks who they are and why\nthey are calling, and then rings the member, holds the call for review,\ntakes a voicemail, or turns the caller away. Saved contacts ring straight\nthrough.\n\nThis API lets a member connect their own AI assistant or scripts to their\nown Foyer account. Everything it returns belongs to the member who made the\nkey; there is no way to reach another member's data.\n\n## Authentication\nSend a personal API key in the `Authorization` header:\n`Authorization: Bearer fyr_…`. The member makes keys in the Foyer app or\ndashboard under Settings › Connected apps. A key is shown once, expires\nafter 90 days (or sooner if chosen), and can be revoked at any time. Keys\nare never accepted in the URL.\n\n## Permissions (scopes)\nEach key carries the scopes the member ticked when making it:\n\n| Scope | What it allows |\n|---|---|\n| `calls:read` | See calls, summaries, voicemails list, the review queue, statistics and search. |\n| `transcripts:read` | Read the words: the screening conversation, voicemail text and the recorded call's transcript. Also lets search look inside them. |\n| `audio:read` | Get short-lived links to recordings (voicemail, screening, the whole call). Off unless the member grants it. |\n| `contacts:read` | See contacts and how Foyer treats each caller. |\n| `settings:read` | See settings, numbers and forwarding lines, notes and what the member's host (the AI screener) knows. |\n| `calls:write` | Mark calls read or unread, archive, refile and report spam. |\n| `contacts:write` | Add contacts and change how Foyer treats a caller. |\n| `settings:write` | Change any setting the member can change in the app. |\n| `notes:write` | Add or remove notes for the host and manage the facts it knows. |\n\nA request without the scope it needs gets `403 insufficient_scope`.\n\n## Plans\nEvery Foyer plan can read. Changing anything (any `*:write` scope) needs\nConcierge or Chief of Staff; the free period and complimentary access count\nas the top tier. On Doorman, write requests get\n`403 plan_does_not_include_writes` and nothing is changed; a key made with\nwrite scopes keeps them and they work again after an upgrade. Use\n`GET /v1/me` to see what the current key may do.\n\n## Limits\nPer member: 60 requests a minute and 10,000 a day, and at most 20 searches\na minute. Over a limit the API answers `429 rate_limited` with a\n`Retry-After` header (seconds).\n\n## Pagination\nLists are newest first. A page has `data` and `next_cursor`; pass\n`next_cursor` back unchanged as `cursor` for the next page. `next_cursor`\nis `null` on the last page. `limit` sets the page size.\n\n## Errors\nEvery error has the same shape:\n`{\"error\": {\"code\": \"not_found\", \"message\": \"…\", \"status\": 404}}`, with an\noptional `details` object. `message` is written for people; `code` is\nstable and meant for programs.\n\n## Dates, numbers and ids\nDates are ISO 8601 in UTC. Phone numbers are E.164 (`+16175550100`). Call\nids are opaque strings; use them exactly as returned.\n\n## What the API cannot do\nFor safety these stay in the Foyer app only: deleting the account or\nremoving Foyer, purchases and billing (Apple in-app purchase), phone-number\nverification codes (adding a screened number, confirming an email),\nexporting data by email, admin tools, answering or controlling a call\nwhile it is live, sharing public links, and the phone interview with the\nhost. Reading a call through the API never marks it read; use\n`POST /v1/calls/{id}/mark-read` for that.\n","contact":{"name":"Foyer","url":"https://hifoyer.com"}},"servers":[{"url":"https://api.hifoyer.com","description":"Production"}],"security":[{"apiKey":[]}],"tags":[{"name":"Discovery","description":"The key in use, its permissions and this specification."},{"name":"Calls","description":"Screened calls, voicemails, the review queue, search and statistics."},{"name":"Call actions","description":"Mark, archive, refile and report calls (calls:write)."},{"name":"Audio","description":"Short-lived links to recordings (audio:read)."},{"name":"Contacts","description":"Contacts, approved and blocked callers, remembered callers and personal greetings."},{"name":"Account and settings","description":"The member's name, plan, numbers and every setting the app can change."},{"name":"Host knowledge","description":"Notes for the host (the AI screener) and the facts it knows about the member."},{"name":"Keys","description":"Key management for the member's own signed-in Foyer app or dashboard (not usable with an API key)."}],"x-foyer-write-responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}},"paths":{"/v1/openapi.json":{"get":{"operationId":"get_openapi_spec","tags":["Discovery"],"summary":"This specification as JSON","description":"The OpenAPI 3.1 document for this API. No authentication.","security":[],"x-foyer-auth":"public","responses":{"200":{"description":"The OpenAPI document.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/v1/me":{"get":{"operationId":"get_me","tags":["Discovery"],"summary":"What this key can do","description":"The key in use, which scopes it has, whether the member's plan allows\nchanges, and the rate limits. Call this first to learn what you may do.\nNeeds no particular scope.\n","x-foyer-auth":"key","responses":{"200":{"description":"The key and its permissions.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Me"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/calls":{"get":{"operationId":"list_calls","tags":["Calls"],"summary":"List screened calls","description":"The member's calls, newest first, with who called, what Foyer did with\nthe call (`outcome`) and a one-line summary. Filter by outcome, date,\nread state, voicemail or caller. Archived calls are left out unless\n`archived` says otherwise. Listing never marks calls read.\n","x-foyer-auth":"key","x-foyer-scope":"calls:read","parameters":[{"$ref":"#/components/parameters/Outcome"},{"$ref":"#/components/parameters/Since"},{"$ref":"#/components/parameters/Until"},{"$ref":"#/components/parameters/Unread"},{"$ref":"#/components/parameters/HasVoicemail"},{"$ref":"#/components/parameters/Archived"},{"$ref":"#/components/parameters/Caller"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"$ref":"#/components/responses/CallPage"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/voicemails":{"get":{"operationId":"list_voicemails","tags":["Calls"],"summary":"List calls that left a voicemail","description":"Calls where the caller left a voicemail, newest first. Same shape and\nfilters as `GET /v1/calls`. Use `voicemail.reviewed` to find new ones;\nread the message text with `GET /v1/calls/{id}` (needs\n`transcripts:read`) or listen with `GET /v1/calls/{id}/audio?part=voicemail`.\n","x-foyer-auth":"key","x-foyer-scope":"calls:read","parameters":[{"$ref":"#/components/parameters/Since"},{"$ref":"#/components/parameters/Until"},{"$ref":"#/components/parameters/Unread"},{"$ref":"#/components/parameters/Archived"},{"$ref":"#/components/parameters/Caller"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"$ref":"#/components/responses/CallPage"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/review":{"get":{"operationId":"list_review_queue","tags":["Calls"],"summary":"Calls waiting for the member's review","description":"Calls Foyer was unsure about and held for the member (`outcome` =\n`review`) that the member has not looked at yet, newest first. To\nclear one, mark it read; to decide what happens next time this caller\nrings, refile it (`POST /v1/calls/{id}/refile`).\n","x-foyer-auth":"key","x-foyer-scope":"calls:read","parameters":[{"$ref":"#/components/parameters/Since"},{"$ref":"#/components/parameters/Until"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"$ref":"#/components/responses/CallPage"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/calls/{id}":{"get":{"operationId":"get_call","tags":["Calls"],"summary":"One call in full","description":"Everything about one call: the caller, the outcome and summary, which\nforwarding line it came in on, what went wrong if anything\n(`incidents`), the voicemail, recordings and which audio parts exist.\nWith `transcripts:read` it also has the words: the screening\nconversation between Foyer and the caller, the voicemail text and the\ntranscript of the recorded connected call. Without that scope those\nfields are left out and `transcripts_omitted` is true. Reading a call\nhere does NOT mark it read.\n","x-foyer-auth":"key","x-foyer-scope":"calls:read","parameters":[{"$ref":"#/components/parameters/CallId"}],"responses":{"200":{"description":"The call.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CallDetail"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/search":{"get":{"operationId":"search_calls","tags":["Calls"],"summary":"Search calls by words","description":"Finds calls whose caller name, caller ID, company, number or summary\ncontain `q` (case-insensitive, the whole phrase). With\n`transcripts:read` it also looks inside voicemail text, the screening\nconversation and the recorded call's transcript, and each hit carries a\nshort `excerpt` around the match.\n\nCalls are stored encrypted, so search examines up to 400 calls per\nrequest, newest first. If `next_cursor` is not null, more history\nremains: pass it back as `cursor` to keep searching further back.\n`search.reached_oldest_call` says when there is nothing older. Combine\nwith `since`/`until` or `outcome` to narrow the scan. Counts against the\nsearch limit of 20 a minute.\n","x-foyer-auth":"key","x-foyer-scope":"calls:read","parameters":[{"name":"q","in":"query","required":true,"description":"The words to find, 2-200 characters. Matched as one phrase, ignoring case. Digits also match part of a caller's number (3 or more digits).","schema":{"type":"string","minLength":2,"maxLength":200},"example":"plumber"},{"$ref":"#/components/parameters/Outcome"},{"$ref":"#/components/parameters/Since"},{"$ref":"#/components/parameters/Until"},{"$ref":"#/components/parameters/Archived"},{"name":"limit","in":"query","description":"Most matches to return, 1-50 (default 25).","schema":{"type":"integer","minimum":1,"maximum":50,"default":25}},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"Matching calls, newest first.","content":{"application/json":{"schema":{"type":"object","required":["data","next_cursor","search"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SearchHit"}},"next_cursor":{"type":["string","null"],"description":"Pass back as `cursor` to keep searching older calls; null when the oldest call was reached."},"search":{"type":"object","properties":{"searched_fields":{"type":"array","items":{"$ref":"#/components/schemas/SearchField"},"description":"Which parts of each call were searched (transcript fields need transcripts:read)."},"calls_examined":{"type":"integer","description":"How many calls this request looked at."},"reached_oldest_call":{"type":"boolean","description":"True when there is no older call left to search."},"method":{"type":"string","enum":["scan","index"],"description":"How the search ran."}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/stats":{"get":{"operationId":"get_call_stats","tags":["Calls"],"summary":"Call counts today and overall","description":"How many calls Foyer handled today (put through, held for review, sent to voicemail, blocked) and since the member joined.","x-foyer-auth":"key","x-foyer-scope":"calls:read","x-foyer-action":"stats","parameters":[{"name":"exclude_self_tests","in":"query","description":"true leaves out test calls the member made to their own line.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"The counts.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"today":{"type":"object","properties":{"through":{"type":"integer","description":"Calls put through to the member."},"review":{"type":"integer","description":"Calls held for review."},"voicemail":{"type":"integer","description":"Calls sent to voicemail."},"blocked":{"type":"integer","description":"Spam or blocked callers turned away."},"total":{"type":"integer"}}},"lifetime":{"type":"object","properties":{"screened":{"type":"integer"},"blocked":{"type":"integer"},"since":{"type":["string","null"],"format":"date-time"}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/calls/{id}/audio":{"get":{"operationId":"get_call_audio_link","tags":["Audio"],"summary":"A five-minute link to a recording","description":"Returns a URL that plays one recording of this call for the next five\nminutes, without further authentication. Fetch it promptly; it answers\nwith a redirect to the audio file (or the audio itself). Ask again for\na new link when it expires. Needs `audio:read`, which members grant\nseparately. `GET /v1/calls/{id}` lists the parts that exist in\n`audio_parts`.\n","x-foyer-auth":"key","x-foyer-scope":"audio:read","parameters":[{"$ref":"#/components/parameters/CallId"},{"name":"part","in":"query","required":true,"description":"Which recording. `voicemail`: the message the caller left.\n`screening`: Foyer's conversation with the caller. `call`: the\nwhole call (screening plus the connected conversation when it was\nrecorded). `caller` / `owner`: one side of the connected\nconversation.\n","schema":{"$ref":"#/components/schemas/AudioPart"}}],"responses":{"200":{"description":"The link.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","required":["part","url","expires_at"],"properties":{"part":{"$ref":"#/components/schemas/AudioPart"},"url":{"type":"string","format":"uri","description":"Plays the audio until expires_at. Treat it as a secret."},"expires_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/audio":{"get":{"operationId":"play_audio_link","tags":["Audio"],"summary":"Play a recording from a link","description":"The URL returned by `GET /v1/calls/{id}/audio`, used as given. The\nlink is its own credential for five minutes; no Authorization header\nis needed. It stops working when it expires, when the key that made it\nis revoked or loses `audio:read`, or when the account closes.\n","security":[],"x-foyer-auth":"link","parameters":[{"name":"link","in":"query","required":true,"description":"The opaque signed token from the link.","schema":{"type":"string"}}],"responses":{"200":{"description":"The audio (supports HTTP Range).","content":{"audio/*":{"schema":{"type":"string","format":"binary"}}}},"302":{"description":"Redirect to the audio file at a short-lived storage URL."},"404":{"$ref":"#/components/responses/NotFound"},"410":{"description":"The link expired or is no longer allowed (`link_expired`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/calls/{id}/mark-read":{"post":{"operationId":"mark_call_read","tags":["Call actions"],"summary":"Mark a call read","description":"Clears the unread dot on every device, exactly like opening the call in the app. Idempotent; returns when it was first read.","x-foyer-auth":"key","x-foyer-scope":"calls:write","x-foyer-action":"mark_call_reviewed","parameters":[{"$ref":"#/components/parameters/CallId"}],"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/calls/{id}/mark-unread":{"post":{"operationId":"mark_call_unread","tags":["Call actions"],"summary":"Mark a call unread","description":"Shows the call as unread again on every device (and its voicemail as new).","x-foyer-auth":"key","x-foyer-scope":"calls:write","x-foyer-action":"mark_call_unread","parameters":[{"$ref":"#/components/parameters/CallId"}],"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/calls/{id}/archive":{"post":{"operationId":"archive_call","tags":["Call actions"],"summary":"Archive a call","description":"Moves the call out of the member's lists into Archived on every device. Nothing is deleted; unarchive brings it back.","x-foyer-auth":"key","x-foyer-scope":"calls:write","x-foyer-action":"archive_call","parameters":[{"$ref":"#/components/parameters/CallId"}],"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/calls/{id}/unarchive":{"post":{"operationId":"unarchive_call","tags":["Call actions"],"summary":"Unarchive a call","description":"Returns an archived call to the member's lists.","x-foyer-auth":"key","x-foyer-scope":"calls:write","x-foyer-action":"unarchive_call","parameters":[{"$ref":"#/components/parameters/CallId"}],"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/calls/{id}/voicemail/mark-reviewed":{"post":{"operationId":"mark_voicemail_reviewed","tags":["Call actions"],"summary":"Mark a voicemail heard","description":"Marks this call's voicemail as reviewed, so it no longer counts as a new message.","x-foyer-auth":"key","x-foyer-scope":"calls:write","x-foyer-action":"voicemail_reviewed","parameters":[{"$ref":"#/components/parameters/CallId"}],"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/calls/{id}/refile":{"post":{"operationId":"refile_call","tags":["Call actions"],"summary":"Tell Foyer how this caller should be handled","description":"Re-files the call and sets the caller's treatment for next time, the\nsame as the choices on a call in the app:\n`through` approves the caller (rings straight through from now on);\n`screened` treats them as a stranger (screened every time);\n`review` holds their calls for the member's review;\n`voicemail` files the call as voicemail;\n`block` blocks the caller (turned away from now on). Foyer learns from\nthese choices.\n","x-foyer-auth":"key","x-foyer-scope":"calls:write","x-foyer-action":"refile","parameters":[{"$ref":"#/components/parameters/CallId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["decision"],"properties":{"decision":{"type":"string","enum":["through","screened","review","voicemail","block"]}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/calls/{id}/report-spam":{"post":{"operationId":"report_spam","tags":["Call actions"],"summary":"Report the caller as spam","description":"Files a spam report for this call's caller, which helps protect every Foyer member. Returns how many households have reported the number. Idempotent.","x-foyer-auth":"key","x-foyer-scope":"calls:write","x-foyer-action":"report_spam","parameters":[{"$ref":"#/components/parameters/CallId"}],"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/contacts":{"get":{"operationId":"list_contacts","tags":["Contacts"],"summary":"Contacts and how Foyer treats them","description":"Everyone who rings straight through (saved contacts and approved\ncallers), sorted by name, with their treatment: `through`, or\n`appear_unknown` while the member has asked Foyer to screen them like a\nstranger for a while. `personalized` means they have a personal\ngreeting or answering setup. The first page also lists `blocked`\nnumbers. Every other caller is a stranger and is screened.\n","x-foyer-auth":"key","x-foyer-scope":"contacts:read","x-foyer-action":"whitelist","parameters":[{"name":"limit","in":"query","description":"Contacts per page, 1-500 (default 100).","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"One page of contacts.","content":{"application/json":{"schema":{"type":"object","required":["data","next_cursor","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"blocked":{"type":"array","description":"Blocked numbers (first page only).","items":{"type":"object","properties":{"number":{"type":"string"},"treatment":{"type":"string","const":"block"}}}},"next_cursor":{"type":["string","null"]},"total":{"type":"integer","description":"Contacts across all pages."}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"operationId":"add_contact","tags":["Contacts"],"summary":"Add a contact","description":"Saves a contact; they ring straight through from now on. It is also added to the member's iPhone contacts at the next sync.","x-foyer-auth":"key","x-foyer-scope":"contacts:write","x-foyer-action":"contact_add","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["number"],"properties":{"number":{"$ref":"#/components/schemas/PhoneNumber"},"name":{"type":"string","description":"Full name as it should appear."},"first_name":{"type":"string","maxLength":200},"last_name":{"type":"string","maxLength":200},"business_name":{"type":"string","maxLength":200}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/contacts/archive":{"post":{"operationId":"archive_contact","tags":["Contacts"],"summary":"Remove a contact (archive)","description":"Takes the number out of contacts and clears any approval or block, so the caller is screened like a stranger. Reversible with restore.","x-foyer-auth":"key","x-foyer-scope":"contacts:write","x-foyer-action":"contact_remove","requestBody":{"$ref":"#/components/requestBodies/NumberOnly"},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/contacts/restore":{"post":{"operationId":"restore_contact","tags":["Contacts"],"summary":"Restore an archived contact","description":"Brings an archived contact back under its saved name; they ring straight through again.","x-foyer-auth":"key","x-foyer-scope":"contacts:write","x-foyer-action":"contact_restore","requestBody":{"$ref":"#/components/requestBodies/NumberOnly"},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/contacts/approve":{"post":{"operationId":"approve_caller","tags":["Contacts"],"summary":"Let a caller ring straight through","description":"Approves a number (the \"Through\" choice on a contact). Clears a block on it.","x-foyer-auth":"key","x-foyer-scope":"contacts:write","x-foyer-action":"whitelist_add","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["number"],"properties":{"number":{"$ref":"#/components/schemas/PhoneNumber"},"name":{"type":"string","description":"Optional name to save with the approval."}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/contacts/unapprove":{"post":{"operationId":"unapprove_caller","tags":["Contacts"],"summary":"Screen a caller again","description":"Removes the approval, so Foyer screens this number like any stranger. To hold their calls for review or block them, refile one of their calls instead.","x-foyer-auth":"key","x-foyer-scope":"contacts:write","x-foyer-action":"whitelist_remove","requestBody":{"$ref":"#/components/requestBodies/NumberOnly"},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/contacts/appear-unknown":{"post":{"operationId":"set_appear_unknown","tags":["Contacts"],"summary":"Screen a contact like a stranger for a while","description":"For the next `hours`, this contact is screened like an unknown caller instead of ringing straight through. `hours` 0 ends it now.","x-foyer-auth":"key","x-foyer-scope":"contacts:write","x-foyer-action":"set_override","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["number","hours"],"properties":{"number":{"$ref":"#/components/schemas/PhoneNumber"},"hours":{"type":"number","minimum":0,"maximum":720}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/contacts/preferences":{"patch":{"operationId":"update_contact_preferences","tags":["Contacts"],"summary":"Change one contact's preferences","description":"Per-contact options for a saved contact. `record_always` records every\ncall with this contact (a recording notice plays first; available on\naccounts in the preview). `answer_mode` `personal_ai` lets Foyer answer\nthis contact with a personal greeting and script instead of ringing\nstraight through (`direct`). Send only the fields to change. Some of\nthese depend on the member's plan.\n","x-foyer-auth":"key","x-foyer-scope":"contacts:write","x-foyer-action":"set_contact_config","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["number"],"properties":{"number":{"$ref":"#/components/schemas/PhoneNumber"},"record_always":{"type":"boolean"},"answer_mode":{"type":"string","enum":["direct","personal_ai"]},"voice_id":{"type":"string","description":"Voice for the personal greeting (from GET /v1/voices); empty clears."},"greeting":{"type":"string","maxLength":1000,"description":"Custom greeting for this contact; empty clears."},"script":{"type":"string","maxLength":8000,"description":"What Foyer should say or ask; empty clears."},"first_name":{"type":"string","maxLength":200},"last_name":{"type":"string","maxLength":200},"business_name":{"type":"string","maxLength":200}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/remembered-callers":{"get":{"operationId":"list_remembered_callers","tags":["Contacts"],"summary":"Callers Foyer remembers","description":"People who are not contacts but have called before and gave a name; Foyer greets them by it next time. Up to 200, most recent first.","x-foyer-auth":"key","x-foyer-scope":"contacts:read","x-foyer-action":"remembered_callers","responses":{"200":{"description":"The remembered callers.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether returning-caller recognition is on."},"callers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":["string","null"]},"name_source":{"type":["string","null"],"description":"owner when the member set the name","otherwise how Foyer learned it.":null},"number":{"type":["string","null"]},"last_contact":{"type":["object","null"],"description":"When they last called and why."},"updated_at":{"type":["string","null"],"format":"date-time"}}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/remembered-callers/{id}":{"patch":{"operationId":"rename_remembered_caller","tags":["Contacts"],"summary":"Correct a remembered caller's name","description":"The name Foyer greets this caller by. 1-3 words of letters, up to 40 characters.","x-foyer-auth":"key","x-foyer-scope":"contacts:write","x-foyer-action":"remembered_caller_update","parameters":[{"$ref":"#/components/parameters/UuidId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":40}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"delete":{"operationId":"forget_remembered_caller","tags":["Contacts"],"summary":"Forget a remembered caller","description":"`forget=name` forgets only the name; `forget=all` (default) forgets the caller entirely.","x-foyer-auth":"key","x-foyer-scope":"contacts:write","x-foyer-action":"remembered_caller_delete","parameters":[{"$ref":"#/components/parameters/UuidId"},{"name":"forget","in":"query","description":"`name` forgets only the name (the last call stays on record); `all` forgets the caller entirely.","schema":{"type":"string","enum":["name","all"],"default":"all"}}],"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/answering-profiles":{"get":{"operationId":"list_answering_profiles","tags":["Contacts"],"summary":"Saved personal greetings","description":"Reusable greetings and scripts the member saved for answering particular contacts.","x-foyer-auth":"key","x-foyer-scope":"contacts:read","x-foyer-action":"answering_profiles","responses":{"200":{"description":"The saved greetings.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"voice_id":{"type":["string","null"]},"greeting":{"type":"string"},"script":{"type":"string"},"updated_at":{"type":["string","null"],"format":"date-time"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"operationId":"save_answering_profile","tags":["Contacts"],"summary":"Save a personal greeting","description":"Saves a reusable greeting and/or script. At least one of greeting, script or voice_id is required.","x-foyer-auth":"key","x-foyer-scope":"contacts:write","x-foyer-action":"save_answering_profile","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":60},"greeting":{"type":"string","maxLength":1000},"script":{"type":"string","maxLength":8000},"voice_id":{"type":"string"}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/answering-profiles/{id}":{"delete":{"operationId":"delete_answering_profile","tags":["Contacts"],"summary":"Remove a saved personal greeting","x-foyer-auth":"key","x-foyer-scope":"contacts:write","x-foyer-action":"delete_answering_profile","parameters":[{"$ref":"#/components/parameters/UuidId"}],"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/account":{"get":{"operationId":"get_account","tags":["Account and settings"],"summary":"The member's name, plan and Foyer number","description":"Names (and the name callers hear), pronouns, the Foyer number calls are forwarded to, and the plan with its API access.","x-foyer-auth":"key","x-foyer-scope":"settings:read","x-foyer-action":"owner_profile","responses":{"200":{"description":"The account.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Account"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/account/profile":{"patch":{"operationId":"update_account_name","tags":["Account and settings"],"summary":"Change the member's name","description":"First, last and preferred name. The preferred name (or first and last) is what Foyer tells callers it answers for. At least one name is required; `pronouns` is optional.","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_owner_profile","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"first_name":{"type":"string","maxLength":100},"last_name":{"type":"string","maxLength":100},"preferred_name":{"type":"string","maxLength":120},"pronouns":{"$ref":"#/components/schemas/Pronouns"}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/account/pronouns":{"put":{"operationId":"set_pronouns","tags":["Account and settings"],"summary":"How Foyer refers to the member","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_owner_pronouns","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["pronouns"],"properties":{"pronouns":{"$ref":"#/components/schemas/Pronouns"}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/settings":{"get":{"operationId":"get_settings","tags":["Account and settings"],"summary":"All settings","description":"Screening level and host, the greeting callers hear, recording, ringing, spam handling and notifications, grouped as in the app.","x-foyer-auth":"key","x-foyer-scope":"settings:read","x-foyer-action":"settings","responses":{"200":{"description":"The settings.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Settings"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/settings/openings":{"get":{"operationId":"list_openings","tags":["Account and settings"],"summary":"Greeting choices","description":"The preset opening lines callers can hear (with how each sounds for this member) and the current choice. Use a preset `key` with PUT /v1/settings/opening.","x-foyer-auth":"key","x-foyer-scope":"settings:read","x-foyer-action":"openings","responses":{"200":{"$ref":"#/components/responses/PassThrough"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/settings/screening-hosts":{"get":{"operationId":"list_screening_hosts","tags":["Account and settings"],"summary":"AI models that can screen calls","description":"The screening hosts (AI models) available on this account, which one is in use, and whether an administrator locked the choice.","x-foyer-auth":"key","x-foyer-scope":"settings:read","x-foyer-action":"models","responses":{"200":{"$ref":"#/components/responses/PassThrough"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/voices":{"get":{"operationId":"list_voices","tags":["Account and settings"],"summary":"Voices Foyer can speak in","description":"The voices available for answering calls and the current one. Use an `id` with PUT /v1/settings/voice.","x-foyer-auth":"key","x-foyer-scope":"settings:read","x-foyer-action":"voices","responses":{"200":{"description":"The voices.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"current":{"type":["object","null"]},"voices":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"gender":{"type":"string"},"accent":{"type":"string"}}}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/settings/screening-level":{"put":{"operationId":"set_screening_level","tags":["Account and settings"],"summary":"How strict screening is","description":"Send `level` on the 1-5 scale (1-2 lenient, 3 balanced, 4-5 strict) or\n`level_3` on the three-level scale (1 Open, 2 Balanced, 3 Strict;\nstored as 1, 3 and 5). `GET /v1/settings` says which scale the member\nsees (`screening.level_scale`).\n","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_sensitivity","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"level":{"type":"integer","minimum":1,"maximum":5},"level_3":{"type":"integer","minimum":1,"maximum":3}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/settings/screening-host":{"put":{"operationId":"set_screening_host","tags":["Account and settings"],"summary":"Which AI model screens calls","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_model","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["host"],"properties":{"host":{"type":"string","enum":["qwen","gemini","sonnet"],"description":"A key from GET /v1/settings/screening-hosts."}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/settings/opening":{"put":{"operationId":"set_opening","tags":["Account and settings"],"summary":"The first thing callers hear","description":"A preset (`classic`, `warm`, `concise`, `butler`) or `custom` with your own `text` (1-8000 characters). Foyer adds the recording notice when one is required.","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_opening","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["key"],"properties":{"key":{"type":"string","enum":["classic","warm","concise","butler","custom"]},"text":{"type":"string","maxLength":8000}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/settings/identity-style":{"put":{"operationId":"set_identity_style","tags":["Account and settings"],"summary":"How much Foyer says about who it answers for","description":"`friendly` uses the member's name, `balanced` is more reserved, `anonymous` never says who it answers for.","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_identity_style","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["style"],"properties":{"style":{"type":"string","enum":["friendly","balanced","anonymous"]}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/settings/recording":{"put":{"operationId":"set_recording","tags":["Account and settings"],"summary":"Which calls are recorded","description":"The call outcomes whose calls are recorded. Send the full list; an empty list records none. `known` (saved contacts) is only available on lines that support it.","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_recording","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["record_outcomes"],"properties":{"record_outcomes":{"type":"array","items":{"type":"string","enum":["known","connect","review","voicemail","honeypot"]}}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/settings/known-callers":{"patch":{"operationId":"set_known_caller_options","tags":["Account and settings"],"summary":"Options for calls from saved contacts","description":"`transcription_enabled` records and transcribes calls with saved contacts (where available). `greeting_enabled` plays a short greeting before putting a contact through. Send one or both.","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_known_call_preferences","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"transcription_enabled":{"type":"boolean"},"greeting_enabled":{"type":"boolean"}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/settings/notifications":{"patch":{"operationId":"update_notifications","tags":["Account and settings"],"summary":"Email and push notifications","description":"A partial update merged into the member's notification choices, for\nexample `{\"categories\": {\"voicemail\": {\"push\": false}}}`. Categories:\n`voicemail`, `call.known`, `call.connect`, `call.review`,\n`call.voicemail_tier`, `call.spam`, `call.live`, `recordings`,\n`daily_digest`, `product_updates`; each has `email` and/or `push`\n(see `notifications.channels_available` in GET /v1/settings).\n`channels` are master switches; `options` include\n`voicemail_include_transcript`, `voicemail_include_audio_link` and\n`live_note_include_words`. Unknown keys are refused.\n","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_notify_prefs","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"channels":{"type":"object","properties":{"email":{"type":"boolean"},"push":{"type":"boolean"}}},"categories":{"type":"object","additionalProperties":{"type":"object","properties":{"email":{"type":"boolean"},"push":{"type":"boolean"}}}},"options":{"type":"object","additionalProperties":{"type":"boolean"}}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/settings/live-call-alerts":{"put":{"operationId":"set_live_call_alerts","tags":["Account and settings"],"summary":"Alerts while a call is being screened","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_live_call_alerts","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["mode"],"properties":{"mode":{"type":"string","enum":["off","live_activity","notification"]}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/settings/app-badge":{"put":{"operationId":"set_app_badge","tags":["Account and settings"],"summary":"What the app icon badge counts","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_badge_settings","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["mode"],"properties":{"mode":{"type":"string","enum":["voicemail","missed","all","off"]}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/settings/announcements":{"put":{"operationId":"set_announcement_push","tags":["Account and settings"],"summary":"Push notifications for Foyer announcements","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_announcement_preferences","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["push_enabled"],"properties":{"push_enabled":{"type":"boolean"}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/settings/pause":{"put":{"operationId":"pause_screening","tags":["Account and settings"],"summary":"Pause screening","description":"While paused, every call rings straight through without screening (for example while expecting a verification call). `indefinite` stays paused until resumed; Foyer reminds the member daily.","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_pause","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["preset"],"properties":{"preset":{"type":"string","enum":["10m","1h","8h","24h","indefinite"]}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}},"delete":{"operationId":"resume_screening","tags":["Account and settings"],"summary":"Resume screening now","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_pause","responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/settings/safe-word":{"put":{"operationId":"set_safe_word","tags":["Account and settings"],"summary":"The safe word","description":"A word or short phrase (up to 60 characters) that lets any caller who says it straight through without screening. Pick something only real contacts would know. An empty string clears it.","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_safe_word","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["word"],"properties":{"word":{"type":"string","maxLength":60}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/settings/voice":{"put":{"operationId":"set_voice","tags":["Account and settings"],"summary":"The voice Foyer answers in","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_voice","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["voice_id"],"properties":{"voice_id":{"type":"string","description":"An id from GET /v1/voices."},"speed":{"type":"number","minimum":0.6,"maximum":1.4,"description":"Optional speaking speed (1 is normal)."}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/settings/voice-speed":{"put":{"operationId":"set_voice_speed","tags":["Account and settings"],"summary":"How fast Foyer speaks","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_voice_speed","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["speed"],"properties":{"speed":{"type":"number","minimum":0.6,"maximum":1.4}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/settings/ringing":{"put":{"operationId":"set_ringing","tags":["Account and settings"],"summary":"Where approved calls ring","description":"`both` (the Foyer app and a SIP phone), `app`, `groundwire` (SIP phone only) or `none`.","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_ring_mode","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["mode"],"properties":{"mode":{"type":"string","enum":["both","app","groundwire","none"]}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/settings/browser-ringing":{"put":{"operationId":"set_browser_ringing","tags":["Account and settings"],"summary":"Ring the web dashboard too","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_web_ring","requestBody":{"$ref":"#/components/requestBodies/Enabled"},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/settings/caller-name-lookup":{"put":{"operationId":"set_caller_name_lookup","tags":["Account and settings"],"summary":"Look up caller ID names","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_cnam","requestBody":{"$ref":"#/components/requestBodies/Enabled"},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/settings/spam-auto-report":{"put":{"operationId":"set_spam_auto_report","tags":["Account and settings"],"summary":"Report spam calls automatically","description":"On some accounts this is always on and the stored choice is ignored; the response says the value in effect.","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_auto_report","requestBody":{"$ref":"#/components/requestBodies/Enabled"},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/settings/do-not-call":{"put":{"operationId":"set_do_not_call","tags":["Account and settings"],"summary":"Record that the number is on the Do Not Call registry","description":"The member's own statement that their number is registered at donotcall.gov. Foyer cannot check the registry.","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_dnc","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["registered"],"properties":{"registered":{"type":"boolean"}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/numbers":{"get":{"operationId":"list_numbers","tags":["Account and settings"],"summary":"Foyer number and forwarding lines","description":"The Foyer number calls are forwarded to; the member's phone lines that\nforward to it, each with whether forwarding is working\n(`forwarding_status`: `verified`, `not_forwarding` or `unchecked`) and\nhow to turn it on or off with that carrier; and any extra numbers that\nring when a call is put through.\n","x-foyer-auth":"key","x-foyer-scope":"settings:read","x-foyer-action":"settings","responses":{"200":{"description":"The numbers.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Numbers"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/numbers/forwarding-lines":{"post":{"operationId":"add_forwarding_line","tags":["Account and settings"],"summary":"Add a phone line that forwards to Foyer","description":"Records a line the member forwards to their Foyer number. Turning forwarding on happens with the carrier; GET /v1/numbers shows the code to dial.","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"add_source","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["number"],"properties":{"number":{"$ref":"#/components/schemas/PhoneNumber"},"label":{"type":"string","maxLength":60,"description":"For example \"Work cell\"."},"type":{"type":"string","enum":["cell","googlevoice","landline","other"]},"carrier":{"type":"string"},"forward_code":{"type":"string","maxLength":40}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/numbers/forwarding-lines/label":{"patch":{"operationId":"rename_forwarding_line","tags":["Account and settings"],"summary":"Rename a forwarding line","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"rename_source","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["number","label"],"properties":{"number":{"$ref":"#/components/schemas/PhoneNumber"},"label":{"type":"string","minLength":1,"maxLength":60}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/numbers/forwarding-lines/remove":{"post":{"operationId":"remove_forwarding_line","tags":["Account and settings"],"summary":"Remove a forwarding line from Foyer","description":"Removes the line from the member's account. It does not turn off forwarding at the carrier; calls already forwarded still reach Foyer.","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"remove_source","requestBody":{"$ref":"#/components/requestBodies/NumberOnly"},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/numbers/carrier":{"put":{"operationId":"set_line_carrier","tags":["Account and settings"],"summary":"Set a line's carrier","description":"The carrier of the member's primary line (or `number` when given), so Foyer can show the right forwarding codes.","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_carrier","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["carrier"],"properties":{"carrier":{"type":"string","maxLength":40},"forward_code":{"type":"string","maxLength":40},"number":{"$ref":"#/components/schemas/PhoneNumber"}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/numbers/screened-carrier":{"put":{"operationId":"set_screened_number_carrier","tags":["Account and settings"],"summary":"Set the carrier of an extra screened number","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"screened_number_set_carrier","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["number","carrier"],"properties":{"number":{"$ref":"#/components/schemas/PhoneNumber"},"carrier":{"type":"string","enum":["verizon","att","tmobile","uscellular","googlefi","googlevoice","visible","mint","cricket","boost","metro","xfinity","spectrum","other"]},"forward_code":{"type":"string","maxLength":40}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/numbers/ring-destination":{"put":{"operationId":"set_ring_destination","tags":["Account and settings"],"summary":"Ring an extra verified number","description":"Turns ringing on or off for a separately verified number that is not a forwarding line.","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_source_ring","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["number","enabled"],"properties":{"number":{"$ref":"#/components/schemas/PhoneNumber"},"enabled":{"type":"boolean"}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/numbers/ring-order":{"put":{"operationId":"set_ring_order","tags":["Account and settings"],"summary":"The order extra numbers ring in","description":"Numbers first to last. Only verified, enabled ring destinations are ordered; others are skipped. Depends on the plan.","x-foyer-auth":"key","x-foyer-scope":"settings:write","x-foyer-action":"set_ring_order","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["numbers"],"properties":{"numbers":{"type":"array","items":{"$ref":"#/components/schemas/PhoneNumber"}}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/notes":{"get":{"operationId":"list_notes","tags":["Host knowledge"],"summary":"Notes for the host and what it understands","description":"The member's notes to their host (the AI screener), for example \"My plumber Dave will call this week, put him through\", and `directives`, the host's working summary of all of them.","x-foyer-auth":"key","x-foyer-scope":"settings:read","x-foyer-action":"notes","responses":{"200":{"description":"The notes.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"notes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"text":{"type":"string"},"summary":{"type":"string","description":"The host's one-line reading of the note."},"created_at":{"type":["string","null"],"format":"date-time"},"kind":{"type":"string"},"source":{"type":"string"}}}},"directives":{"type":"string","description":"What the host currently follows","compiled from the notes.":null},"directives_updated_at":{"type":["string","null"],"format":"date-time"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"operationId":"add_note","tags":["Host knowledge"],"summary":"Tell the host something","description":"Adds a note (3-2000 characters) the host takes into account on future calls. The response includes the host's reply and its updated directives.","x-foyer-auth":"key","x-foyer-scope":"notes:write","x-foyer-action":"add_note","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","minLength":3,"maxLength":2000}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/notes/{id}":{"delete":{"operationId":"remove_note","tags":["Host knowledge"],"summary":"Remove a note","description":"Archives the note and recompiles the host's directives without it.","x-foyer-auth":"key","x-foyer-scope":"notes:write","x-foyer-action":"remove_note","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/notes/directives":{"put":{"operationId":"set_directives","tags":["Host knowledge"],"summary":"Edit the host's directives directly","description":"Replaces the compiled directives with this text as written. The next added or removed note recompiles over it.","x-foyer-auth":"key","x-foyer-scope":"notes:write","x-foyer-action":"set_directives","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["directives"],"properties":{"directives":{"type":"string"}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/profile":{"get":{"operationId":"get_host_profile","tags":["Host knowledge"],"summary":"Facts the host knows about the member","description":"The facts the host uses when screening (family, pets, providers, vendors the member expects, where they live and work), including suggestions it learned from calls that wait for the member's decision, and whether call learning is on.","x-foyer-auth":"key","x-foyer-scope":"settings:read","x-foyer-action":"profile","responses":{"200":{"$ref":"#/components/responses/PassThrough"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/v1/profile/short-form":{"patch":{"operationId":"update_host_facts","tags":["Host knowledge"],"summary":"Edit the member's key facts","description":"Keys present are replaced; missing keys are left alone; empty values\nclear. List keys (arrays of short strings): `spouse`, `wife`, `mother`,\n`father`, `children`, `pets`, `expectedVendors`, `activities`. Text\nkeys: `location`, `industry`, `work`.\n","x-foyer-auth":"key","x-foyer-scope":"notes:write","x-foyer-action":"profile_short_form_update","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["facts"],"properties":{"facts":{"type":"object","additionalProperties":true}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/profile/facts/{id}/decision":{"post":{"operationId":"decide_host_fact","tags":["Host knowledge"],"summary":"Accept, edit or drop a fact","description":"`accept` a suggested fact, `edit` it (send `value`), `archive` it, `reject` a suggestion, or `delete` a fact.","x-foyer-auth":"key","x-foyer-scope":"notes:write","x-foyer-action":"profile_fact_update","parameters":[{"$ref":"#/components/parameters/UuidId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["decision"],"properties":{"decision":{"type":"string","enum":["accept","edit","archive","reject","delete"]},"value":{"type":"string"},"target_key":{"type":"string"}}}}}},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/profile/learning":{"put":{"operationId":"set_call_learning","tags":["Host knowledge"],"summary":"Let the host learn from calls","description":"When on (and available on the member's line), the host suggests facts from calls. Turning it off stops using learned facts right away.","x-foyer-auth":"key","x-foyer-scope":"notes:write","x-foyer-action":"set_profile_learning","requestBody":{"$ref":"#/components/requestBodies/Enabled"},"responses":{"200":{"$ref":"#/components/responses/WriteResult"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/v1/keys":{"get":{"operationId":"list_api_keys","tags":["Keys"],"summary":"The member's API keys","description":"Every key the member made, newest first, with its status. Never shows the secret. Needs the member's signed-in app session.","security":[{"appSession":[]}],"x-foyer-auth":"session","responses":{"200":{"description":"The keys.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}},"available":{"type":"boolean","description":"Whether this account may make keys now."},"writes_allowed":{"type":"boolean","description":"Whether the plan includes changes now, so a new key may have write scopes (Concierge, Chief of Staff, the free period)."},"plan":{"type":"string","description":"The plan's name","e.g. Doorman.":null}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"operationId":"create_api_key","tags":["Keys"],"summary":"Make an API key","description":"Makes a key with the chosen scopes. The `secret` is in this response\nonly; Foyer stores just a fingerprint of it. Write scopes need a plan\nthat includes changes (403 `plan_does_not_include_writes` otherwise).\nAt most 10 active keys.\n","security":[{"appSession":[]}],"x-foyer-auth":"session","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","scopes"],"properties":{"name":{"type":"string","minLength":1,"maxLength":60,"description":"What the key is for, e.g. \"Claude\"."},"scopes":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/Scope"}},"expires_in_days":{"type":"integer","minimum":1,"maximum":90,"default":90}}}}}},"responses":{"201":{"description":"The new key, with its secret (shown once).","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"allOf":[{"$ref":"#/components/schemas/ApiKey"},{"type":"object","properties":{"secret":{"type":"string","description":"The key (fyr_…). Shown once."}}}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/v1/keys/{id}/revoke":{"post":{"operationId":"revoke_api_key","tags":["Keys"],"summary":"Revoke an API key","description":"The key stops working immediately, including audio links it made. Idempotent.","security":[{"appSession":[]}],"x-foyer-auth":"session","parameters":[{"$ref":"#/components/parameters/UuidId"}],"responses":{"200":{"description":"The revoked key.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ApiKey"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/keys/activity":{"get":{"operationId":"get_api_activity","tags":["Keys"],"summary":"What each key did","description":"For each key, requests in the last `days` and what they read or changed in plain words (\"read 14 calls, 3 voicemails, searched twice\"), plus the 50 most recent requests. The log records endpoints, counts and status only, never content.","security":[{"appSession":[]}],"x-foyer-auth":"session","parameters":[{"name":"days","in":"query","description":"How many days back to summarize, 1-90 (default 30).","schema":{"type":"integer","minimum":1,"maximum":90,"default":30}}],"responses":{"200":{"description":"The activity.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"days":{"type":"integer"},"keys":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/ApiKey"},{"type":"object","properties":{"requests":{"type":"integer"},"errors":{"type":"integer"},"last_request_at":{"type":["string","null"],"format":"date-time"},"records_read":{"type":"object","additionalProperties":{"type":"integer"}},"searches":{"type":"integer"},"changes":{"type":"integer"},"summary":{"type":"string"}}}]}},"recent":{"type":"array","items":{"type":"object","properties":{"key_id":{"type":"string"},"at":{"type":"string","format":"date-time"},"method":{"type":"string"},"endpoint":{"type":"string"},"status":{"type":"integer"},"resource":{"type":["string","null"]},"records":{"type":"integer"},"error_code":{"type":["string","null"]}}}}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"fyr_ API key","description":"A personal API key made in Settings › Connected apps: Authorization: Bearer fyr_…"},"appSession":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"The member's signed-in Foyer app or dashboard session. Used only for managing keys."}},"parameters":{"CallId":{"name":"id","in":"path","required":true,"description":"The call's id, exactly as returned in a call's `id`.","schema":{"type":"string"}},"UuidId":{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"Limit":{"name":"limit","in":"query","description":"Items per page, 1-100 (default 25).","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}},"Cursor":{"name":"cursor","in":"query","description":"The `next_cursor` from the previous page, unchanged. Omit for the first page.","schema":{"type":"string"}},"Outcome":{"name":"outcome","in":"query","description":"Only calls with these outcomes (comma-separated). See the Outcome schema for what each means.","schema":{"type":"string"},"example":"review,voicemail"},"Since":{"name":"since","in":"query","description":"Only calls that started at or after this time (ISO 8601).","schema":{"type":"string","format":"date-time"}},"Until":{"name":"until","in":"query","description":"Only calls that started at or before this time (ISO 8601).","schema":{"type":"string","format":"date-time"}},"Unread":{"name":"unread","in":"query","description":"true for calls the member has not opened yet, false for ones they have.","schema":{"type":"boolean"}},"HasVoicemail":{"name":"has_voicemail","in":"query","description":"true for calls that left a voicemail.","schema":{"type":"boolean"}},"Archived":{"name":"archived","in":"query","description":"`exclude` (default) leaves archived calls out, `only` lists just them, `include` lists both.","schema":{"type":"string","enum":["exclude","only","include"],"default":"exclude"}},"Caller":{"name":"caller","in":"query","description":"Only calls from this full phone number.","schema":{"type":"string"}}},"requestBodies":{"NumberOnly":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["number"],"properties":{"number":{"$ref":"#/components/schemas/PhoneNumber"}}}}}},"Enabled":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean"}}}}}}},"responses":{"CallPage":{"description":"One page of calls, newest first.","content":{"application/json":{"schema":{"type":"object","required":["data","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Call"}},"next_cursor":{"type":["string","null"],"description":"Pass back as `cursor` for the next page; null on the last page."}}}}}},"PassThrough":{"description":"The information as the Foyer app shows it.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","additionalProperties":true}}}}}},"WriteResult":{"description":"Done. `data` is what Foyer saved, in the same shape the Foyer app\nreceives (field names may be camelCase). Re-read the resource for the\nAPI's own shape.\n","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","additionalProperties":true}}}}}},"BadRequest":{"description":"The request is missing something or has a value Foyer does not accept (`invalid_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"No valid key, or the key was revoked or expired (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"Not allowed. `insufficient_scope`: the key lacks the scope named in\n`details.required_scope`. `plan_does_not_include_writes`: the member's\nplan reads only, nothing was changed. `api_not_enabled`: the API is not\nopen on this account yet. `forbidden`: the app itself refuses this\nchange (for example a feature not available on this line).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"No such call, contact or item on this account (`not_found`, `media_not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Conflict":{"description":"The change conflicts with the account's current state (`conflict`, `key_limit`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PlanRequired":{"description":"This feature is not included in the member's plan (`plan_required`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many requests (`rate_limited`). Wait the number of seconds in Retry-After.","headers":{"Retry-After":{"description":"Seconds until the limit resets.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ServerError":{"description":"Something failed on Foyer's side (`server_error`, `unavailable`). Safe to retry later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","status"],"properties":{"code":{"type":"string","description":"Stable code: unauthorized, api_not_enabled, insufficient_scope,\nplan_does_not_include_writes, plan_required, forbidden,\ninvalid_request, not_found, media_not_found, conflict,\nkey_limit, link_expired, rate_limited, unavailable,\nserver_error.\n"},"message":{"type":"string","description":"A sentence for people."},"status":{"type":"integer","description":"The HTTP status."},"details":{"type":"object","additionalProperties":true}}}}},"Scope":{"type":"string","enum":["calls:read","transcripts:read","audio:read","contacts:read","settings:read","calls:write","contacts:write","settings:write","notes:write"]},"PhoneNumber":{"type":"string","description":"A phone number, ideally E.164 (+16175550100). US numbers may omit +1.","example":"+16175550100"},"Pronouns":{"type":["string","null"],"enum":["he","she","they","private",null],"description":"How Foyer refers to the member; null means it just uses their name."},"Outcome":{"type":"string","enum":["known","connect","review","voicemail","gate","honeypot","fallback"],"description":"What Foyer did with the call.\n`known`: a saved contact, put straight through.\n`connect`: screened, then put through to the member.\n`review`: Foyer was unsure and held the call for the member.\n`voicemail` / `gate`: sent to voicemail (the caller may or may not have left one).\n`honeypot`: screened as spam and turned away.\n`fallback`: a blocked caller, turned away.\n"},"AudioPart":{"type":"string","enum":["voicemail","screening","call","caller","owner"]},"SearchField":{"type":"string","enum":["name","caller_id_name","company","number","summary","voicemail","screening_conversation","connected_call"]},"Call":{"type":"object","description":"A screened call as it appears in lists.","required":["id","started_at","caller","outcome","summary","read","archived","voicemail"],"properties":{"id":{"type":"string","description":"The call's id."},"started_at":{"type":["string","null"],"format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time"},"duration_sec":{"type":"integer"},"caller":{"type":"object","properties":{"number":{"type":["string","null"],"description":"The caller's number (E.164)","null when withheld.":null},"name":{"type":["string","null"],"description":"The name Foyer knows them by (contact","what they said":null,"or caller ID).":null},"caller_id_name":{"type":["string","null"],"description":"The carrier's caller ID name."},"company":{"type":["string","null"],"description":"The business they said they call from."},"identity_source":{"type":"string","description":"Where `name` came from (contact","stated":null,"cnam":null,"none …).":null},"city":{"type":["string","null"]},"state":{"type":["string","null"]}}},"outcome":{"$ref":"#/components/schemas/Outcome"},"outcome_label":{"type":"string","description":"The outcome in plain words."},"refiled_as":{"type":["string","null"],"enum":["through","screened","review","voicemail","block",null],"description":"The member's own decision on this call, if they refiled it."},"summary":{"type":"string","description":"One or two sentences on who called and why."},"spam_score":{"type":["number","null"],"description":"0-1 likelihood the caller is spam","when known.":null},"self_test":{"type":"boolean","description":"The member called their own line to test it."},"read":{"type":"boolean","description":"The member has opened this call."},"archived":{"type":"boolean"},"voicemail":{"type":"object","properties":{"state":{"type":"string","enum":["transcribed","audio_only","captured","not_left","none"],"description":"`transcribed` message with text; `audio_only` message without text yet; `captured` being processed; `not_left` sent to voicemail but no message; `none`."},"has_audio":{"type":"boolean"},"reviewed":{"type":"boolean","description":"The member has heard it."}}},"has_screening_recording":{"type":"boolean"},"has_call_recording":{"type":"boolean"},"has_connected_transcript":{"type":"boolean","description":"The connected conversation was recorded and transcribed."},"record_always":{"type":"boolean","description":"Recorded because the caller is a contact set to always record."},"hangup":{"type":"object","description":"How and when the call ended","when recorded.":null}}},"CallDetail":{"allOf":[{"$ref":"#/components/schemas/Call"},{"type":"object","properties":{"read_at":{"type":["string","null"],"format":"date-time"},"to":{"type":["string","null"],"description":"The Foyer number the call reached."},"forwarded_from":{"type":["string","null"],"description":"The member's line that forwarded the call","when known.":null},"line":{"type":["object","null"],"description":"The member's forwarding line the caller dialled, when identified.","properties":{"name":{"type":["string","null"]},"number":{"type":["string","null"]}}},"incidents":{"type":"array","description":"Anything that went wrong or was degraded on this call, in plain words, most serious first.","items":{"type":"object"}},"voicemail":{"type":"object","properties":{"duration_sec":{"type":["number","null"]},"reviewed_at":{"type":["string","null"],"format":"date-time"},"transcript":{"type":"string","description":"The message text (transcripts:read)."}}},"recordings":{"type":"object","properties":{"screening":{"type":["object","null"]},"call":{"type":["object","null"]}}},"audio_parts":{"type":"array","items":{"$ref":"#/components/schemas/AudioPart"},"description":"The recordings GET /v1/calls/{id}/audio can play for this call."},"connected_call":{"type":["object","null"],"description":"The conversation after Foyer put the caller through, when it was recorded.","properties":{"started_at":{"type":["string","null"],"format":"date-time"},"transcript_status":{"type":["string","null"]},"transcript":{"type":"array","description":"Both sides in order (transcripts:read).","items":{"type":"object","properties":{"speaker":{"type":"string","enum":["caller","you"]},"text":{"type":"string"},"start_sec":{"type":["number","null"]},"at":{"type":["string","null"],"format":"date-time"}}}},"sides":{"type":"array","description":"Each side's text on its own, when the two could not be put in order (transcripts:read).","items":{"type":"object","properties":{"speaker":{"type":"string","enum":["caller","you"]},"text":{"type":"string"}}}}}},"screening_conversation":{"type":"array","description":"What Foyer and the caller said while screening, in order (transcripts:read).","items":{"type":"object","properties":{"speaker":{"type":"string","enum":["caller","foyer"]},"text":{"type":"string"},"at":{"type":["string","null"],"format":"date-time"}}}},"transcripts_omitted":{"type":"boolean","description":"True when the key lacks transcripts:read, so the words were left out."}}}]},"SearchHit":{"type":"object","properties":{"call":{"$ref":"#/components/schemas/Call"},"matched_in":{"type":"array","items":{"$ref":"#/components/schemas/SearchField"}},"excerpt":{"type":["string","null"],"description":"A short passage around the first match (not for number matches)."}}},"Contact":{"type":"object","properties":{"number":{"type":"string"},"name":{"type":["string","null"]},"treatment":{"type":"string","enum":["through","appear_unknown"],"description":"`through` rings straight through; `appear_unknown` is screened like a stranger until appear_unknown_until."},"appear_unknown_until":{"type":["string","null"],"format":"date-time"},"personalized":{"type":"boolean","description":"Has a personal greeting or answering setup."}}},"Account":{"type":"object","properties":{"member_id":{"type":"string","format":"uuid"},"first_name":{"type":["string","null"]},"last_name":{"type":["string","null"]},"preferred_name":{"type":["string","null"]},"display_name":{"type":["string","null"]},"name_callers_hear":{"type":["string","null"],"description":"The name Foyer says it answers for."},"pronouns":{"$ref":"#/components/schemas/Pronouns"},"foyer_number":{"type":["string","null"],"description":"The Foyer number the member's lines forward to."},"plan":{"type":"object","properties":{"name":{"type":"string","description":"Doorman","Concierge":null,"Chief of Staff":null,"or how access is granted.":null},"key":{"type":"string"},"api_access":{"type":"string","enum":["read","write"]},"basis":{"type":"string"}}}}},"Me":{"type":"object","properties":{"member_id":{"type":"string","format":"uuid"},"key":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/Scope"}},"expires_at":{"type":"string","format":"date-time"}}},"access":{"type":"object","properties":{"level":{"type":"string","enum":["read","write"],"description":"What the member's plan allows through the API."},"plan":{"type":"string"},"plan_key":{"type":"string"},"basis":{"type":"string","description":"Why (plan, free_period, complimentary, grace, trial, no_active_plan, billing_unknown)."},"writes_allowed":{"type":"boolean"}}},"scopes":{"type":"array","items":{"type":"object","properties":{"scope":{"$ref":"#/components/schemas/Scope"},"granted":{"type":"boolean"},"description":{"type":"string"}}}},"rate_limits":{"type":"object","properties":{"per_minute":{"type":"integer"},"per_day":{"type":"integer"},"searches_per_minute":{"type":"integer"}}}}},"Settings":{"type":"object","properties":{"screening":{"type":"object","properties":{"level":{"type":["integer","null"],"description":"Stored strictness 1-5."},"level_scale":{"type":"integer","enum":[3,5],"description":"Which scale the member's app shows."},"level_3":{"type":["integer","null"],"description":"The same choice on the three-level scale."},"host":{"type":["string","null"],"description":"The AI model screening calls."},"safe_word":{"type":["string","null"]},"pause":{"type":["object","null"],"description":"Whether screening is paused and until when."}}},"greeting":{"type":"object","properties":{"opening_key":{"type":"string"},"opening_text":{"type":"string"},"opener_preview":{"type":["object","null"],"description":"What callers hear","as composed now.":null},"identity_style":{"type":["string","null"]},"owner_pronouns":{"$ref":"#/components/schemas/Pronouns"},"voice_id":{"type":["string","null"]},"voice_speed":{"type":"number"}}},"recording":{"type":"object","properties":{"record_outcomes":{"type":"array","items":{"type":"string"}},"known_caller_transcription":{"type":"boolean"},"known_caller_greeting":{"type":"boolean"},"known_caller_transcription_available":{"type":"boolean"}}},"ringing":{"type":"object","properties":{"mode":{"type":"string","enum":["both","app","groundwire","none"]},"browser":{"type":"boolean"},"iphone_ready":{"type":"boolean","description":"An iPhone can ring right now."}}},"spam":{"type":"object","properties":{"auto_report":{"type":"boolean"},"do_not_call_registered_at":{"type":["string","null"],"format":"date-time"},"caller_name_lookup":{"type":"boolean"}}},"notifications":{"type":"object","properties":{"preferences":{"type":"object","description":"The full notification choices (channels","categories":null,"options":null,"live_calls).":null},"channels_available":{"type":"object","description":"Which channels each category supports."},"live_call_alerts":{"type":"string","enum":["off","live_activity","notification"]},"app_badge":{"type":["string","null"],"enum":["voicemail","missed","all","off",null]},"announcements_push":{"type":["boolean","null"]}}}}},"Numbers":{"type":"object","properties":{"foyer_number":{"type":["string","null"]},"forwarding_lines":{"type":"array","items":{"type":"object","properties":{"number":{"type":["string","null"]},"label":{"type":"string"},"type":{"type":"string"},"carrier":{"type":"string"},"verified":{"type":"boolean"},"forwarding_status":{"type":"string","enum":["verified","not_forwarding","unchecked","disconnected"]},"forwarding_status_at":{"type":["string","null"],"format":"date-time"},"last_checked_at":{"type":["string","null"],"format":"date-time"},"forwarding_verified_at":{"type":["string","null"],"format":"date-time"},"turn_on":{"type":["object","null"],"description":"How to switch forwarding on with this carrier."},"turn_off":{"type":["object","null"],"description":"How to switch it off."}}}},"ring_destinations":{"type":"array","items":{"type":"object","properties":{"number":{"type":["string","null"]},"label":{"type":"string"},"verified":{"type":"boolean"},"ring_enabled":{"type":"boolean"},"priority":{"type":"integer"}}}}}},"ApiKey":{"type":"object","description":"A personal API key (`kind` = `key`) or an app the member connected by\nsigning in, such as Claude or ChatGPT through the Foyer MCP connector\n(`kind` = `app`). Both are listed, logged and revoked the same way.\n","properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["key","app"],"description":"`key`: a personal API key. `app`: an app connected by signing in (MCP)."},"name":{"type":"string","description":"The key's name","or the connected app's name.":null},"key_hint":{"type":["string","null"],"description":"The first characters of the key","to tell keys apart. Null for a connected app.":null},"app":{"type":"object","description":"Connected apps only (kind `app`).","properties":{"name":{"type":"string","description":"The name the app registered with."},"host":{"type":["string","null"],"description":"Where the app sends the member back after sign-in","e.g. claude.ai.":null}}},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/Scope"}},"status":{"type":"string","enum":["active","revoked","expired"]},"created_at":{"type":"string","format":"date-time"},"last_used_at":{"type":["string","null"],"format":"date-time"},"expires_at":{"type":"string","format":"date-time"},"revoked_at":{"type":["string","null"],"format":"date-time"}}}}}}