{"agent_docs":{"contacts_search_contract":"response carries engine on every /search call; sidecar enrichment is attached by default (opt out with enriched=false): per-hit enrichment {twitter_handle, twitter_followers, recent_post_texts, exa_summary, news_titles, revenue_estimate_usd, revenue_evidence, enrichment_confidence, segment_tags, enriched_at} plus top-level enrichment_hit_count; single contacts resolve via GET /enrichment/<contact_id>; enrich=true separately adds per-hit associated[] with matched_on + evidence, hub_matches summaries for >100-match attribute groups, and enrichment_partial when budget-capped (depth default 1, max 3; enrich_attrs overrides follow-up keys phone,email,domain,persons)","email_accounts_and_otp":"docs/AGENT-EMAIL.md in the operator's repository (authenticator API + worked examples)","fallback_behavior":"on Typesense failure the query is retried on the SQLite/FTS5 ladder and engine=fts5-fallback is returned; enrichment does not run on that path","on_401":"your token is missing or wrong; do not retry without a valid token"},"description":"Single service exposing two products: (1) natural-language search over a 52M-row US business-contacts database, and (2) the epetron.com mail platform — account provisioning, a public webmail client, and a hosted authenticator (server-computed TOTP codes for arbitrary services).","endpoints":{"GET /enrichment/<contact_id>":"one sidecar enrichment doc by contact id; 404 {\"error\":\"not enriched\"} when absent (auth)","GET /health":"no auth; check drive/db availability before querying","GET /info":"full usage doc (auth)","GET /mail/inboxes":"list mail accounts (auth)","GET /search?q=<nl query>&limit=1..200&offset=n":"search (auth)","GET /webmail":"public webmail client (no auth to view; per-account sessions inside)","POST /mail/inboxes":"create a mail account (auth)","POST /mail/otp/entries":"add an authenticator entry for an account (auth)","POST /search {\"queries\":[...],\"limit\":20}":"up to 20 queries, run concurrently (auth)"},"expected_wait_times":{"advice":["set generous HTTP timeouts (>= 180 s) for first-of-a-kind queries","repeat queries are fast; do not abort and retry rapidly — that re-reads cold cache from the start","batch related queries via POST /search so they run concurrently server-side"],"cold_query_warning":"first query touching a new term/region can take 10-120 s: the 52M-row database lives on an external USB drive and cold-cache reads are slow","storage_down":"if /health reports storage_unavailable, wait for the drive to be remounted; the service recovers on its own","warm_query":"< 1 s typical"},"how_to_authenticate":{"example":"curl -H \"Authorization: Bearer $CONTACTS_BEARER_TOKEN\" http://<host>/search?q=...","header":"Authorization: Bearer <token>","token_source":"CONTACTS_BEARER_TOKEN in the operator's ~/.zshrc"},"products":{"contacts_search":{"endpoints":["GET /search?q=<nl query>&limit=1..200&offset=n","GET /search?q=<nl query>&enrich=true&depth=1&enrich_attrs=phone,email,domain,persons","POST /search {\"queries\":[...]} (up to 20, concurrent)","GET /info (full usage doc)"],"enrichment":{"depth":"association hops; default 1 (original results only), max 3; each hop follows the same shared-attribute keys from the previous hop's results","enrich":"add enrich=true to run stage 2: concurrent follow-up Typesense searches per result over shared phone_digits / email_norm / domain / person names; synchronous in the same response (latency-tolerant, generous budget)","enrich_attrs":"comma list overriding the default follow-up keys: phone,email,domain,persons"},"example_result_fields":["business_name","email","phone","address","categories","score"],"name":"contacts / magiclab","public_advanced_search":{"authentication":"none; do not send credentials","counts":"GET /directory/stats: total_records, businesses, individuals, counted_at. Registry counts overlap; they are not partitions of the record total.","endpoint":"GET /advanced/search?q=<query>&per_page=50","entity_views":"Automatic resolves unambiguous company/person intent. Records uses native pagination. Companies/people project a fixed first 250 ranked candidate pool, with at most three constrained affiliation searches of 80 candidates. Original source values remain; normalized keys and display values are separate. Record-level contact methods do not establish individual ownership. Array previews carry total/truncated fields; originals remain in source_records. Mixed-field people queries with full explicit person-name evidence prioritize person intent.","errors":"400 invalid query; 429 rate limited; 503 unavailable. Honor Retry-After before retrying.","facet_by":["city","state","category_primary"],"filter_by":"field:=literal clauses joined by &&; fields: city, state, category_primary, sic_code, phone_digits, persons","parameters":"q (1-200 UTF-8 bytes), per_page (1-50, default 50), page (1-100); optional query_by, filter_by, facet_by; view=auto|records|companies|people (default auto)","query_by":["business_name","business_name_norm","name_aliases","categories","persons","sic_description","phone_digits"],"response":"found, out_of, page, hits[].document; entity views add records_found, entity_scope and source_records. document.source_record_ref and company member references resolve full originals in source_records. Inspect entity_scope.complete, truncated, and affiliation_expansion before treating membership or counts as exhaustive.","timeout":"Advanced requests default to 180 seconds; configured range 1–600. Keep browser/agent request timeouts long enough and cancel explicitly when replacing a query."},"public_contact_images":"POST /directory/images with JSON {name,city,state} after selecting a contact. No credentials. Returns bounded Serper image matches and source URLs; image matches do not verify identity. Browser results are cached in sessionStorage. Honor Retry-After on 429/503.","public_context_search":"GET /advanced/context with the original advanced-search parameters plus context_query (name or category, 1-160 bytes) and context_kind (category, people, names). Original explicit and inferred hard filters are retained. The GUI runs at most two follow-ups automatically; related results are separate from the primary page.","public_gui":"GET / in a browser; no account required. GET /?format=json or Accept: application/json returns these instructions. Non-browser user agents receive instructions automatically.","response_fields":{"associated[]":"per-hit identity associations (only when enrich=true and engine=typesense); each entry: {id, business_name, city, state, matched_on, evidence} — matched_on is the shared attribute (phone|email|domain|persons), evidence shows the matched value","engine":"which engine served the query: \"typesense\" (primary) or \"fts5-fallback\" (SQLite/FTS5 ladder after a Typesense failure; enrichment is skipped on this path)","enrichment":"per-hit sidecar enrichment object, attached by default (pass enriched=false to opt out; Typesense engine only): {twitter_handle, twitter_followers, recent_post_texts, exa_summary, news_titles, revenue_estimate_usd, revenue_evidence, enrichment_confidence, segment_tags, enriched_at}; absent when the contact is not enriched","enrichment_hit_count":"number of hits on the page that got an enrichment object attached","enrichment_partial":"present and true only when enrichment was cut short by its latency budget (target <=120 s, hard timeout 180 s); results are still returned","hub_matches":"hub records (>100 matches on one shared attribute, e.g. shared info@ domains) are summarized, not inlined: {matched_on, total, sample: top 10}"},"what":"Natural-language search over 52M US business contacts (Typesense primary with bm25-style ranking, batch-capable, FTS5 fallback)"},"mail_platform":{"account_admin":{"DELETE /mail/inboxes/<local>":"remove account (auth)","GET /mail/inboxes":"list accounts (auth)","GET /mail/inboxes/<local>":"inspect one account (auth)","POST /mail/bearer-sync":"sync the bearer token as a fallback password for every account (auth)","POST /mail/inboxes {\"local\":\"name\",\"password\":\"optional\"}":"create address <name>@epetron.com (auth)","PUT /mail/inboxes/<local>/password":"set password (auth)"},"authenticator_admin":{"DELETE /mail/otp/entries/<address>/<id>":"remove one entry (auth)","GET /mail/otp/entries/<address>":"entries with live 6-digit codes (auth)","GET /mail/otp/status/<address>":"entry count (auth)","POST /mail/otp/entries {\"address\",\"issuer\",\"label\",\"secret\"|\"otpauth_uri\"}":"store a TOTP secret for an account; returns {\"id\"} (auth)","POST /mail/otp/reset/<address>":"wipe all authenticator entries for the address (auth)","login_contract":"webmail login is address+password only; OTP is never part of login"},"webmail":"https://contacts.epetron.com/webmail — humans log in here; agents see the 'For agents' panel on that page for account provisioning flows","what":"Email accounts on epetron.com with a public webmail client and a hosted authenticator (TOTP entries for arbitrary services)"}},"query_tips":["plain natural language works: 'dentist in Franklin, TN', '\"plastic surgery\"', 'hvac in Dallas'","put the city after 'in' to scope results; results are bm25-ranked","use POST /search with queries[] instead of many parallel GETs"],"service":"contacts-search-rs"}