{"openapi":"3.1.0","info":{"title":"Geysera Signal API","version":"v1","description":"Read your identified accounts programmatically. Authenticate with an API key from Exports & API: `Authorization: Bearer sk_sig_...`. Results respect your plan's resolution cap, exactly as the dashboard does.\n\n**Scopes.** Every endpoint here needs the `read` scope, chosen when the key is created and fixed for its lifetime; a key without it gets 403 `insufficient_scope`. A separate `copilot` scope reaches the natural-language endpoint, which is not part of this REST contract — `GET /capabilities` on the same host lists every action a key can call, with the scope each one needs.\n\nUnknown query parameters are ignored rather than rejected, so a misspelled `limit` returns the default page size instead of an error. Pagination is `page` and `page_size` (max 200)."},"servers":[{"url":"https://app.signal.geysera.com/signal-api/v1"}],"paths":{"/accounts":{"get":{"tags":["signal","public-api"],"summary":"List identified accounts (API-key auth, cap-respecting)","operationId":"list_accounts_public_signal_api_v1_accounts_get","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Page Size"}},{"name":"classification","in":"query","required":false,"schema":{"anyOf":[{"enum":["lead","customer","competitor","excluded"],"type":"string"},{"type":"null"}],"title":"Classification"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicAccountList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/visitors":{"get":{"tags":["signal","public-api"],"summary":"List identified visitors (API-key auth, cap- and tier-respecting)","description":"Visitors belonging to accounts inside the plan's resolution cap.\n\nTwo rules are load-bearing and mirror the dashboard exactly:\n\n* The accessible set is the earliest ``cap`` accounts by ``first_seen_at``. Skipping\n  this would let a free tenant read their entire book through the API while the UI\n  blurs it — the paywall has to hold on every surface or it holds on none.\n* Free-tier contact fields are withheld. `is_locked` says so explicitly rather than\n  leaving an integrator to guess why the values are null.","operationId":"list_visitors_public_signal_api_v1_visitors_get","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Page Size"}},{"name":"min_intent","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":100,"minimum":0},{"type":"null"}],"description":"0-100 display scale, as shown in the app.","title":"Min Intent"},"description":"0-100 display scale, as shown in the app."},{"name":"classification","in":"query","required":false,"schema":{"anyOf":[{"enum":["lead","customer","competitor","excluded"],"type":"string"},{"type":"null"}],"title":"Classification"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicVisitorList"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/attribution":{"get":{"tags":["signal","public-api"],"summary":"Attribution rollup for a window (API-key auth)","description":"The nightly attribution rollup, served as-is.\n\nDeliberately reads the cached rollup only and never falls back to a live scan, unlike\nthe dashboard endpoint. A live recompute scans up to 200k visitor rows; behind an API\nkey that is a trivially repeatable way to exhaust the database, and an integrator\npolling on a cron would do it without meaning to. ``computed_at`` tells the caller how\nfresh the answer is so staleness is visible rather than hidden.","operationId":"get_attribution_public_signal_api_v1_attribution_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"default":30,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Attribution Public Signal Api V1 Attribution Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/recommendations":{"get":{"tags":["signal","public-api"],"summary":"Open recommended actions (API-key auth)","description":"Catalog-grounded recommendations that are currently actionable.\n\nQueries ``signal_journey_recommendation`` directly rather than importing the\ndashboard's recommendations service. That service lives on an unmerged branch\n(PR #98), and importing it here would have made this endpoint raise ImportError at\nruntime while every unit test passed — a cross-branch dependency is not a dependency,\nit is a deferred outage.\n\n\"Open\" means the same thing it means in the app: status 'new', or 'snoozed' with the\nsnooze elapsed. Status mutation is deliberately NOT exposed — acting on a\nrecommendation carries an optimistic-locking contract that needs its own design\nrather than being bolted onto a read-only v1.","operationId":"list_recommendations_public_signal_api_v1_recommendations_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Recommendations Public Signal Api V1 Recommendations Get"}}}}}}}},"components":{"schemas":{"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"PublicAccount":{"properties":{"company_domain":{"type":"string","title":"Company Domain"},"company_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Name"},"intent_score":{"type":"integer","title":"Intent Score","description":"0-100, same scale as the dashboard."},"visitor_count":{"type":"integer","title":"Visitor Count"},"visit_count":{"type":"integer","title":"Visit Count"},"classification":{"type":"string","title":"Classification"},"first_seen_at":{"type":"string","format":"date-time","title":"First Seen At"},"last_seen_at":{"type":"string","format":"date-time","title":"Last Seen At"}},"type":"object","required":["company_domain","intent_score","visitor_count","visit_count","classification","first_seen_at","last_seen_at"],"title":"PublicAccount"},"PublicAccountList":{"properties":{"accounts":{"items":{"$ref":"#/components/schemas/PublicAccount"},"type":"array","title":"Accounts"},"total":{"type":"integer","title":"Total"},"page":{"type":"integer","title":"Page"},"page_size":{"type":"integer","title":"Page Size"}},"type":"object","required":["accounts","total","page","page_size"],"title":"PublicAccountList"},"PublicVisitor":{"properties":{"company_domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Domain"},"company_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Name"},"intent_score":{"type":"integer","title":"Intent Score","description":"0-100, same scale as the dashboard."},"classification":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Classification"},"resolved_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved Email","description":"Null on the free tier — upgrade to reveal."},"resolved_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved Name"},"resolved_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved Title"},"employer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Employer"},"first_visit_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"First Visit At"},"last_visit_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Visit At"},"visit_count":{"type":"integer","title":"Visit Count","default":0},"is_locked":{"type":"boolean","title":"Is Locked","description":"True when contact fields were withheld by tier.","default":false}},"type":"object","required":["intent_score"],"title":"PublicVisitor"},"PublicVisitorList":{"properties":{"visitors":{"items":{"$ref":"#/components/schemas/PublicVisitor"},"type":"array","title":"Visitors"},"total":{"type":"integer","title":"Total"},"page":{"type":"integer","title":"Page"},"page_size":{"type":"integer","title":"Page Size"}},"type":"object","required":["visitors","total","page","page_size"],"title":"PublicVisitorList"}}}}