{"openapi":"3.1.0","info":{"title":"Heedeo API","version":"2026-11-08","summary":"Tell a server who is behind a signup, a trial or a coupon, and whether to stop it.","description":"The HTTP API, for a server where @heedeo/sdk does not run, and for tools. The guide, https://app.heedeo.com/docs/integration.md, says when and how to call each operation; this says what each takes and answers. Generated from the API's code and held to it by its tests.\n\nThe browser half is not here: the script tag loads https://app.heedeo.com/i.js, and identify(), which calls /v1/identify with a bundle only the script makes, answers the token you send as token. Neither is a call to write by hand, and nor is /v1/cli/*, the device codes npx @heedeo/sdk init and the Connect page share.\n\nHeedeo enforces nothing. An error, a timeout or no answer is never a reason to refuse a person: carry on as if Heedeo were not there. Each error's code is a key into x-heedeo-errors, with what to do.\n\nAn answer may gain fields under the same version: read the names you need and pass the rest through. The version a call reads is its heedeo-version header, else the day its workspace was made."},"servers":[{"url":"https://app.heedeo.com"}],"tags":[{"name":"Evaluations","description":"Record an event at an action, and read its answer."},{"name":"People","description":"Labels, outcomes, a person's standing and deletion."},{"name":"Setup","description":"Whether the integration is sending."}],"paths":{"/v1/evaluations":{"post":{"operationId":"evaluate","summary":"Record an event and answer its evaluation","tags":["Evaluations"],"parameters":[{"$ref":"#/components/parameters/Version"},{"$ref":"#/components/parameters/Sdk"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EvaluationRequest"}}}},"responses":{"200":{"description":"The evaluation. Read flagged to decide.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Evaluation"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"400":{"description":"invalid_request: The call is malformed. A token Heedeo cannot read is not an error: the event is scored as no token. What to do: Read `message` and fix the call.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"401":{"description":"missing_key: No key was sent. What to do: Set the secret key where the server runs. Until then nobody is checked.\n\ninvalid_key: The key is not one of yours. What to do: Set the secret key where the server runs. Until then nobody is checked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"403":{"description":"publishable_key: A publishable key was sent where the secret key belongs. What to do: Send the secret key (`sk_`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"409":{"description":"invalid_request: The `request_id` already answered another call: another event (another action, or another token), or on `/v1/lists` and `/v1/outcomes` another write (another target, action or outcome). A `request_id` is one attempt at one write, and its stored answer is never given to a different one. A report sent again under its `request_id` after you deleted the person it was on is refused too: the report went with them and is not written again. A report on an account another of your visitors holds too, or on a browser a split left with another visitor, is not one of these: it stays with that visitor, and its repeat answers as before, naming them. What to do: Make one `request_id` for each action, and for each label write and report you mean; send the same one on its retries.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"413":{"description":"too_large: The body is over 64 KB (`/v1/evaluations`, `/v1/lists`, `/v1/outcomes` and identify's request). What to do: Send a smaller body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"500":{"description":"internal: Heedeo failed while answering. What to do: Carry on as if Heedeo were not there (section 5: https://app.heedeo.com/docs/integration/5.md). That is never a refusal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"503":{"description":"busy: Another event on the same token arrived at the same moment. What to do: Send the call again with the same `request_id` (the package's helper does).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}}},"description":"What the package's evaluate() sends. Send it at the action, once per attempt. Read flagged and no_token: never refuse on flag, which does not know about Trust. Heedeo enforces nothing; a 5xx, a timeout or no answer means carry on, never refuse.","x-heedeo-package":"heedeo.evaluate()","x-max-body-bytes":65536,"security":[{"secret":[]}]}},"/v1/events":{"post":{"operationId":"logEvent","summary":"The same call under its earlier name","tags":["Evaluations"],"parameters":[{"$ref":"#/components/parameters/Version"},{"$ref":"#/components/parameters/Sdk"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EvaluationRequest"}}}},"responses":{"200":{"description":"The evaluation. Read flagged to decide.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Evaluation"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"400":{"description":"invalid_request: The call is malformed. A token Heedeo cannot read is not an error: the event is scored as no token. What to do: Read `message` and fix the call.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"401":{"description":"missing_key: No key was sent. What to do: Set the secret key where the server runs. Until then nobody is checked.\n\ninvalid_key: The key is not one of yours. What to do: Set the secret key where the server runs. Until then nobody is checked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"403":{"description":"publishable_key: A publishable key was sent where the secret key belongs. What to do: Send the secret key (`sk_`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"409":{"description":"invalid_request: The `request_id` already answered another call: another event (another action, or another token), or on `/v1/lists` and `/v1/outcomes` another write (another target, action or outcome). A `request_id` is one attempt at one write, and its stored answer is never given to a different one. A report sent again under its `request_id` after you deleted the person it was on is refused too: the report went with them and is not written again. A report on an account another of your visitors holds too, or on a browser a split left with another visitor, is not one of these: it stays with that visitor, and its repeat answers as before, naming them. What to do: Make one `request_id` for each action, and for each label write and report you mean; send the same one on its retries.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"413":{"description":"too_large: The body is over 64 KB (`/v1/evaluations`, `/v1/lists`, `/v1/outcomes` and identify's request). What to do: Send a smaller body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"500":{"description":"internal: Heedeo failed while answering. What to do: Carry on as if Heedeo were not there (section 5: https://app.heedeo.com/docs/integration/5.md). That is never a refusal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"503":{"description":"busy: Another event on the same token arrived at the same moment. What to do: Send the call again with the same `request_id` (the package's helper does).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}}},"description":"The same route as POST /v1/evaluations, answering the same. Every package before 0.1.0-pilot.18 sends it, and logEvent() still does. Send new calls to /v1/evaluations.","deprecated":true,"x-heedeo-package":"heedeo.logEvent()","x-max-body-bytes":65536,"security":[{"secret":[]}]}},"/v1/evaluations/{id}":{"get":{"operationId":"getEvaluation","summary":"Read an evaluation again by its event id","description":"The answer as it was given, with when (answered_at) and for what (action). It is history, not a decision now: flagged, listed and the reasons are what they were at the event, so a label put on since does not change them; send a new action as its own event. Kept 30 days, or as long as a flag holds its event, and deleted with its visitor. Records nothing.","tags":["Evaluations"],"parameters":[{"name":"id","in":"path","required":true,"description":"The event_id the answer carried, or a log line's.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/Version"},{"$ref":"#/components/parameters/Sdk"}],"responses":{"200":{"description":"The evaluation as it was given.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Answered"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"401":{"description":"missing_key: No key was sent. What to do: Set the secret key where the server runs. Until then nobody is checked.\n\ninvalid_key: The key is not one of yours. What to do: Set the secret key where the server runs. Until then nobody is checked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"403":{"description":"publishable_key: A publishable key was sent where the secret key belongs. What to do: Send the secret key (`sk_`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"404":{"description":"not_found: No such path, or no visitor or event of yours has that id (`GET` and `DELETE /v1/visitors/:id`, `POST /v1/outcomes`), or no evaluation of yours has it (`GET /v1/evaluations/:id`: an answer is kept 30 days, or as long as a flag holds its event, and deleted with its visitor). An id deleted before is not this: a second deletion answers 200. What to do: Check the path and the id you sent: an event id for an evaluation, a visitor id for a visitor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"500":{"description":"internal: Heedeo failed while answering. What to do: Carry on as if Heedeo were not there (section 5: https://app.heedeo.com/docs/integration/5.md). That is never a refusal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}}},"x-heedeo-package":"heedeo.evaluation(), from 0.2.0-beta.1","security":[{"secret":[]}]}},"/v1/lists":{"post":{"operationId":"label","summary":"Trust, block or clear a label on a visitor, an email or a network","tags":["People"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListRequest"}}}},"responses":{"200":{"description":"On add, the label as stored; on remove, whether there was one.","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","description":"What the label is on: visitor, email, network, or one added later."},"value":{"type":"string","description":"As stored: the person's current id, an email's hash, a network's prefix."},"action":{"type":"string","enum":["allow","block"]},"added_by":{"type":"string"},"added_at":{"type":"string","format":"date-time"}},"required":["kind","value","action"]},{"type":"object","properties":{"removed":{"type":"boolean"}},"required":["removed"]}]}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"},"heedeo-replayed":{"$ref":"#/components/headers/Replayed"}}},"400":{"description":"invalid_request: The call is malformed. A token Heedeo cannot read is not an error: the event is scored as no token. What to do: Read `message` and fix the call.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"401":{"description":"missing_key: No key was sent. What to do: Set the secret key where the server runs. Until then nobody is checked.\n\ninvalid_key: The key is not one of yours. What to do: Set the secret key where the server runs. Until then nobody is checked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"403":{"description":"publishable_key: A publishable key was sent where the secret key belongs. What to do: Send the secret key (`sk_`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"409":{"description":"invalid_request: The `request_id` already answered another call: another event (another action, or another token), or on `/v1/lists` and `/v1/outcomes` another write (another target, action or outcome). A `request_id` is one attempt at one write, and its stored answer is never given to a different one. A report sent again under its `request_id` after you deleted the person it was on is refused too: the report went with them and is not written again. A report on an account another of your visitors holds too, or on a browser a split left with another visitor, is not one of these: it stays with that visitor, and its repeat answers as before, naming them. What to do: Make one `request_id` for each action, and for each label write and report you mean; send the same one on its retries.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"413":{"description":"too_large: The body is over 64 KB (`/v1/evaluations`, `/v1/lists`, `/v1/outcomes` and identify's request). What to do: Send a smaller body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"500":{"description":"internal: Heedeo failed while answering. What to do: Carry on as if Heedeo were not there (section 5: https://app.heedeo.com/docs/integration/5.md). That is never a refusal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}}},"x-heedeo-package":"heedeo.trust(), block() and clearLabel(), from 0.2.0-beta.1; each sends a request_id from 0.2.0-beta.1","parameters":[{"$ref":"#/components/parameters/Sdk"}],"x-max-body-bytes":65536,"security":[{"secret":[]}]}},"/v1/outcomes":{"post":{"operationId":"reportOutcome","summary":"Report what you learned of an event, a visitor or an account","tags":["People"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OutcomeRequest"}}}},"responses":{"200":{"description":"The outcome as stored; event_id on an event's.","content":{"application/json":{"schema":{"type":"object","properties":{"event_id":{"type":"string","format":"uuid"},"kind":{"type":"string","description":"What the report is about: event, visitor, account, or one added later."},"visitor_id":{"type":["string","null"],"format":"uuid"},"merged_into":{"type":["string","null"],"format":"uuid"},"outcome":{"type":"string","enum":["abuse","legit"]},"label":{"type":["string","null"]},"source":{"type":"string"},"note":{"type":["string","null"]},"risk":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"}},"required":["kind","visitor_id","outcome","label","source","note","risk","created_at"]}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"},"heedeo-replayed":{"$ref":"#/components/headers/Replayed"}}},"400":{"description":"invalid_request: The call is malformed. A token Heedeo cannot read is not an error: the event is scored as no token. What to do: Read `message` and fix the call.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"401":{"description":"missing_key: No key was sent. What to do: Set the secret key where the server runs. Until then nobody is checked.\n\ninvalid_key: The key is not one of yours. What to do: Set the secret key where the server runs. Until then nobody is checked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"403":{"description":"publishable_key: A publishable key was sent where the secret key belongs. What to do: Send the secret key (`sk_`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"404":{"description":"not_found: No such path, or no visitor or event of yours has that id (`GET` and `DELETE /v1/visitors/:id`, `POST /v1/outcomes`), or no evaluation of yours has it (`GET /v1/evaluations/:id`: an answer is kept 30 days, or as long as a flag holds its event, and deleted with its visitor). An id deleted before is not this: a second deletion answers 200. What to do: Check the path and the id you sent: an event id for an evaluation, a visitor id for a visitor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"409":{"description":"invalid_request: The `request_id` already answered another call: another event (another action, or another token), or on `/v1/lists` and `/v1/outcomes` another write (another target, action or outcome). A `request_id` is one attempt at one write, and its stored answer is never given to a different one. A report sent again under its `request_id` after you deleted the person it was on is refused too: the report went with them and is not written again. A report on an account another of your visitors holds too, or on a browser a split left with another visitor, is not one of these: it stays with that visitor, and its repeat answers as before, naming them. What to do: Make one `request_id` for each action, and for each label write and report you mean; send the same one on its retries.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"413":{"description":"too_large: The body is over 64 KB (`/v1/evaluations`, `/v1/lists`, `/v1/outcomes` and identify's request). What to do: Send a smaller body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"500":{"description":"internal: Heedeo failed while answering. What to do: Carry on as if Heedeo were not there (section 5: https://app.heedeo.com/docs/integration/5.md). That is never a refusal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}}},"x-heedeo-package":"heedeo.outcome(), which sends a request_id from 0.2.0-beta.1","parameters":[{"$ref":"#/components/parameters/Sdk"}],"x-max-body-bytes":65536,"security":[{"secret":[]}]}},"/v1/visitors/{id}":{"get":{"operationId":"getVisitor","summary":"A person's standing now","tags":["People"],"parameters":[{"name":"id","in":"path","required":true,"description":"A visitor id from an answer; a retired id answers its visitor.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/Version"},{"$ref":"#/components/parameters/Sdk"}],"responses":{"200":{"description":"The person now, after Trust.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Standing"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"401":{"description":"missing_key: No key was sent. What to do: Set the secret key where the server runs. Until then nobody is checked.\n\ninvalid_key: The key is not one of yours. What to do: Set the secret key where the server runs. Until then nobody is checked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"403":{"description":"publishable_key: A publishable key was sent where the secret key belongs. What to do: Send the secret key (`sk_`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"404":{"description":"not_found: No such path, or no visitor or event of yours has that id (`GET` and `DELETE /v1/visitors/:id`, `POST /v1/outcomes`), or no evaluation of yours has it (`GET /v1/evaluations/:id`: an answer is kept 30 days, or as long as a flag holds its event, and deleted with its visitor). An id deleted before is not this: a second deletion answers 200. What to do: Check the path and the id you sent: an event id for an evaluation, a visitor id for a visitor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"500":{"description":"internal: Heedeo failed while answering. What to do: Carry on as if Heedeo were not there (section 5: https://app.heedeo.com/docs/integration/5.md). That is never a refusal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}}},"x-heedeo-package":"heedeo.visitor()","security":[{"secret":[]}]},"delete":{"operationId":"deleteVisitor","summary":"Delete everything Heedeo holds of a person","description":"For a deletion request you received. Safe to send again: an id deleted before answers that id with an empty deleted and the first deleted_at. An event id is a 400 that says to send its visitor_id. Your labels are kept.","tags":["People"],"parameters":[{"name":"id","in":"path","required":true,"description":"The visitor id; a retired id deletes the visitor it joined.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/Sdk"}],"responses":{"200":{"description":"The visitor's id, the rows each table lost, and when.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"deleted":{"type":"object","properties":{"flag_decisions":{"type":"number"},"flags":{"type":"number"},"actions":{"type":"number"},"outcomes":{"type":"number"},"outcomes_folded":{"type":"number"},"merge_outcomes":{"type":"number"},"event_requests":{"type":"number"},"write_requests":{"type":"number"},"events":{"type":"number"},"farm_group_members":{"type":"number"},"farm_group_pairs":{"type":"number"},"observations":{"type":"number"},"browser_links":{"type":"number"},"browser_edges":{"type":"number"},"browser_visitors":{"type":"number"},"visitor_alias":{"type":"number"},"browsers":{"type":"number"},"visitors":{"type":"number"}}},"deleted_at":{"type":"string","format":"date-time"}},"required":["id","deleted","deleted_at"]}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"400":{"description":"invalid_request: The call is malformed. A token Heedeo cannot read is not an error: the event is scored as no token. What to do: Read `message` and fix the call.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"401":{"description":"missing_key: No key was sent. What to do: Set the secret key where the server runs. Until then nobody is checked.\n\ninvalid_key: The key is not one of yours. What to do: Set the secret key where the server runs. Until then nobody is checked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"403":{"description":"publishable_key: A publishable key was sent where the secret key belongs. What to do: Send the secret key (`sk_`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"404":{"description":"not_found: No such path, or no visitor or event of yours has that id (`GET` and `DELETE /v1/visitors/:id`, `POST /v1/outcomes`), or no evaluation of yours has it (`GET /v1/evaluations/:id`: an answer is kept 30 days, or as long as a flag holds its event, and deleted with its visitor). An id deleted before is not this: a second deletion answers 200. What to do: Check the path and the id you sent: an event id for an evaluation, a visitor id for a visitor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"500":{"description":"internal: Heedeo failed while answering. What to do: Carry on as if Heedeo were not there (section 5: https://app.heedeo.com/docs/integration/5.md). That is never a refusal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}}},"x-heedeo-package":"heedeo.deleteVisitor()","security":[{"secret":[]}]}},"/v1/setup-status":{"get":{"operationId":"getSetupStatus","summary":"Whether identifications and events have arrived","description":"Home's live check, for whoever holds the secret key: the agent setting the app up checks its test event here.","tags":["Setup"],"parameters":[{"name":"event_id","in":"query","required":false,"description":"The event_id of the event under test: the answer then says whether Heedeo holds that event and whether it was identified, which a count cannot prove, since an older event keeps it above 0.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/Sdk"}],"responses":{"200":{"description":"How many of each are kept, and the first and the last.","content":{"application/json":{"schema":{"type":"object","properties":{"identifications":{"$ref":"#/components/schemas/Arrivals"},"events":{"allOf":[{"$ref":"#/components/schemas/Arrivals"},{"type":"object","properties":{"identified":{"type":"object","properties":{"count":{"type":"number","description":"Of the events kept now (30 days), those identified."},"last":{"type":["string","null"],"format":"date-time","description":"When the last identified event came, remembered 13 months: it can be set while count is 0."}},"required":["count","last"]}},"required":["identified"]}],"description":"Events kept, and of them those whose token named a visitor."},"event":{"type":["object","null"],"properties":{"id":{"type":"string","format":"uuid"},"identified":{"type":"boolean"},"visitor_id":{"type":["string","null"],"format":"uuid"}},"required":["id","identified","visitor_id"],"description":"With event_id only: that event, or null when this workspace holds none by that id."},"served":{"type":["string","null"],"format":"date-time"}},"required":["identifications","events"]}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"400":{"description":"invalid_request: The call is malformed. A token Heedeo cannot read is not an error: the event is scored as no token. What to do: Read `message` and fix the call.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"401":{"description":"missing_key: No key was sent. What to do: Set the secret key where the server runs. Until then nobody is checked.\n\ninvalid_key: The key is not one of yours. What to do: Set the secret key where the server runs. Until then nobody is checked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"403":{"description":"publishable_key: A publishable key was sent where the secret key belongs. What to do: Send the secret key (`sk_`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}},"500":{"description":"internal: Heedeo failed while answering. What to do: Carry on as if Heedeo were not there (section 5: https://app.heedeo.com/docs/integration/5.md). That is never a refusal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"heedeo-trace-id":{"$ref":"#/components/headers/TraceId"}}}},"security":[{"secret":[]}]}}},"components":{"securitySchemes":{"secret":{"type":"http","scheme":"bearer","description":"Your secret key: Authorization: Bearer sk_…. Keep it on your server, out of git and of any browser bundle."}},"headers":{"TraceId":{"description":"This request's id: an error's body repeats it as trace_id. Quote it to Heedeo support when a call fails, which always has its line in Heedeo's log; a call that worked may have none (the busiest keep one success in ten), so for an answer quote its event_id. The package gives it as error.traceId from 0.2.0-beta.1.","schema":{"type":"string"}},"Replayed":{"description":"true on a repeat under a request_id that answered before: the answer is the first attempt's, and nothing was written again.","schema":{"type":"string","enum":["true"]}}},"parameters":{"Sdk":{"name":"heedeo-sdk","in":"header","required":false,"description":"The version of @heedeo/sdk sending the call, which the package sends from 0.2.0-beta.1. A call of your own sends none, and is counted as one with no package.","schema":{"type":"string","maxLength":32,"examples":["0.2.0-beta.1"]}},"Version":{"name":"heedeo-version","in":"header","required":false,"description":"The API version the caller reads, a date (YYYY-MM-DD); 2026-11-08 is the latest. Send it: from 2026-11-08 a workspace on the decision refuses a near-miss of an action's name, and an answer's related[] holds only the link kinds the version reads (same_machine from 2026-10-31). Without it, or with a value that is not a date (which is ignored, not refused), the day the workspace was made, and names as they always were. The package sends its own from 0.1.0-pilot.15.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","examples":["2026-11-08"]}}},"schemas":{"EvaluationRequest":{"type":"object","properties":{"token":{"type":["string","null"],"maxLength":4096,"description":"What identify() answered in the browser, just before this call. Optional: an event with no token is still checked, on what needs none. A token Heedeo cannot read (forged, garbled, another environment's, not a string, over 4,096 characters) is no token, never an error, and so are an empty string and null.","x-on-invalid":"no_token","examples":["tk1.eyJ2aXNpdG9yX2lkIjoi…"]},"action":{"type":"string","pattern":"^[\\w.:-]{1,64}$","description":"The action's name: signup, login, trial_start, redeem:CODE or payout, or one of your own, which is stored and judged on nothing. From heedeo-version: 2026-11-08, a workspace on the decision refuses a near-miss of the five names (sign_up, register) with a 400 naming the spelling to send.","x-on-invalid":"refused","examples":["signup"]},"user_id":{"type":["string","null"],"maxLength":256,"description":"Your account's id, when the account exists. Hashed on arrival. An empty string is absent.","x-on-invalid":"refused","examples":["user_42"]},"email":{"type":["string","null"],"maxLength":320,"description":"The address as the person typed it. Hashed on arrival; never stored raw. A string that is not an address (no @, over 320 characters) is left out and the event scored without it, so the action is still checked; a value that is not a string is refused.","x-on-invalid":{"string":"dropped","other":"refused"},"examples":["ana@example.com"]},"acting_for":{"type":["string","null"],"maxLength":256,"description":"The account an agent acts for, when an agent sends the action. An empty string is absent.","x-on-invalid":"refused","examples":["user_7"]},"client_ip":{"type":["string","null"],"description":"The end user's IP address, IPv4 or IPv6. Leave it out when the browser called Heedeo itself (no route on your own domain): the token carries the address identify saw.","x-on-invalid":"refused","examples":["198.51.100.7"]},"headers":{"type":["object","null"],"properties":{"user-agent":{"type":"string"},"accept":{"type":"string"},"accept-language":{"type":"string"},"sec-fetch-site":{"type":"string"},"sec-fetch-mode":{"type":"string"},"sec-fetch-dest":{"type":"string"},"upgrade":{"type":"string"},"signature":{"type":"string"},"signature-input":{"type":"string"},"signature-agent":{"type":"string"},"content-digest":{"type":"string"},"sec-ch-ua":{"type":"string"},"sec-ch-ua-platform":{"type":"string"},"sec-ch-ua-mobile":{"type":"string"},"cf-ipcountry":{"type":"string"},"x-vercel-ip-country":{"type":"string"},"host":{"type":"string","description":"The host the end user's request came to."},":method":{"type":"string","description":"The end user's request method; an OPTIONS preflight is never read as an agent."},":path":{"type":"string","description":"The path the end user's request came to."},":forwarded":{"type":"string","description":"The header names your server looked for on the end user's request, comma-separated, such as user-agent,accept,accept-language. With it, a name it lists and the headers lack is reported in the answer's agent.missing_headers; without it, missing_headers is null (Heedeo cannot tell a header the request lacked from one you do not send)."}},"additionalProperties":{"type":"string"},"description":"The end user's request headers, name to value (any case), never cookie or authorization: what tells a browser from a program. Heedeo reads the names listed here; send them as the request had them, and :forwarded with the names you looked for, or the answer's agent cannot say which headers a browser would have sent and this request lacked.","x-on-invalid":"refused","examples":[{"user-agent":"Mozilla/5.0","accept-language":"en-GB",":forwarded":"user-agent,accept,accept-language"}]},"request_id":{"type":["string","null"],"minLength":1,"maxLength":128,"description":"One id per attempt at one action. Sent again, it answers the stored answer; an id that already answered another action or token is a 409.","x-on-invalid":"refused","examples":["9b2f4a7e-0c1d-4e5f-8a9b-2c3d4e5f6a7b"]},"evidence":{"anyOf":[{"$ref":"#/components/schemas/Evidence","x-on-invalid":"refused"},{"type":"null"}],"examples":[{"email":{"verified":true}}]},"once":{"type":["boolean","null"],"deprecated":true,"description":"Deprecated: the action's name says whether a person does it once. Read by a workspace on the legacy verdict only.","x-on-invalid":"refused","examples":[true]},"status":{"type":["string","null"],"enum":["attempted","succeeded","failed",null],"description":"How the action ended, as your server says: attempted when left out.","x-on-invalid":"refused","examples":["succeeded"]}},"required":["action"],"additionalProperties":false,"description":"One event: an action by a person. Every field but action is optional; an unknown field is refused."},"Evidence":{"type":"object","additionalProperties":false,"description":"What your platform knows about the person: every field optional, an unknown one refused, a null one the same as an absent one. phone.number and payment.card_fingerprint are hashed on arrival. Send phone.number or phone.hash, not both.","x-on-invalid":"refused","properties":{"email":{"type":["object","null"],"additionalProperties":false,"properties":{"verified":{"type":["boolean","null"],"description":"Must be true or false.","examples":[true]},"verified_at":{"type":["string","null"],"format":"date-time","maxLength":64,"description":"Must be an ISO 8601 timestamp. Send RFC 3339, as the example. Any other form JavaScript's Date.parse reads (2026-11-01, for one) is taken too, and stored as the instant it reads, in UTC.","examples":["2026-11-01T09:30:00Z"]}}},"phone":{"type":["object","null"],"additionalProperties":false,"properties":{"number":{"type":["string","null"],"description":"Must be an E.164 number such as +14155550123.","examples":["+1 (415) 555-0123"]},"hash":{"type":["string","null"],"pattern":"^[\\w+/=:.-]{1,256}$","description":"Must be a string of up to 256 letters, digits or + / = : . _ -.","examples":["Xt5EWLLDS7FJjR1c"]},"verified":{"type":["boolean","null"],"description":"Must be true or false.","examples":[true]},"line_type":{"type":["string","null"],"enum":["mobile","landline","fixed_voip","non_fixed_voip","toll_free","prepaid","unknown",null],"description":"Must be one of mobile, landline, fixed_voip, non_fixed_voip, toll_free, prepaid, unknown.","examples":["mobile"]},"country":{"type":["string","null"],"pattern":"^[A-Za-z]{2}$","description":"Must be a two-letter country code.","examples":["GB"]}},"not":{"type":"object","properties":{"number":{"type":"string"},"hash":{"type":"string"}},"required":["number","hash"]}},"oauth":{"type":["object","null"],"additionalProperties":false,"properties":{"provider":{"type":["string","null"],"enum":["google","apple","github","microsoft","other",null],"description":"Must be one of google, apple, github, microsoft, other.","examples":["google"]},"email_verified":{"type":["boolean","null"],"description":"Must be true or false.","examples":[true]},"hosted_domain":{"type":["string","null"],"maxLength":253,"pattern":"^[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)+$","description":"Must be a domain name.","examples":["example.com"]},"real_user_status":{"type":["integer","null"],"enum":[0,1,2,null],"description":"Must be one of 0, 1, 2.","examples":[0]},"private_relay":{"type":["boolean","null"],"description":"Must be true or false.","examples":[true]},"account_created_at":{"type":["string","null"],"format":"date-time","maxLength":64,"description":"Must be an ISO 8601 timestamp. Send RFC 3339, as the example. Any other form JavaScript's Date.parse reads (2026-11-01, for one) is taken too, and stored as the instant it reads, in UTC.","examples":["2026-11-01T09:30:00Z"]},"two_factor":{"type":["boolean","null"],"description":"Must be true or false.","examples":[true]}}},"account":{"type":["object","null"],"additionalProperties":false,"properties":{"created_at":{"type":["string","null"],"format":"date-time","maxLength":64,"description":"Must be an ISO 8601 timestamp. Send RFC 3339, as the example. Any other form JavaScript's Date.parse reads (2026-11-01, for one) is taken too, and stored as the instant it reads, in UTC.","examples":["2026-11-01T09:30:00Z"]},"plan":{"type":["string","null"],"enum":["free","trial","paid",null],"description":"Must be one of free, trial, paid.","examples":["free"]},"paid_since":{"type":["string","null"],"format":"date-time","maxLength":64,"description":"Must be an ISO 8601 timestamp. Send RFC 3339, as the example. Any other form JavaScript's Date.parse reads (2026-11-01, for one) is taken too, and stored as the instant it reads, in UTC.","examples":["2026-11-01T09:30:00Z"]}}},"payment":{"type":["object","null"],"additionalProperties":false,"properties":{"funding":{"type":["string","null"],"enum":["credit","debit","prepaid",null],"description":"Must be one of credit, debit, prepaid.","examples":["credit"]},"card_fingerprint":{"type":["string","null"],"pattern":"^[\\w+/=:.-]{1,256}$","description":"Must be a string of up to 256 letters, digits or + / = : . _ -.","examples":["Xt5EWLLDS7FJjR1c"]},"cvc_check":{"type":["string","null"],"enum":["pass","fail",null],"description":"Must be one of pass, fail.","examples":["pass"]},"wallet":{"type":["string","null"],"enum":["apple_pay","google_pay","link","none",null],"description":"Must be one of apple_pay, google_pay, link, none.","examples":["apple_pay"]},"three_ds":{"type":["string","null"],"enum":["authenticated","failed","not_attempted",null],"description":"Must be one of authenticated, failed, not_attempted.","examples":["authenticated"]},"country":{"type":["string","null"],"pattern":"^[A-Za-z]{2}$","description":"Must be a two-letter country code.","examples":["GB"]},"radar_risk_level":{"type":["string","null"],"enum":["normal","elevated","highest","not_assessed","unknown",null],"description":"Must be one of normal, elevated, highest, not_assessed, unknown.","examples":["normal"]},"free_trial_abuse_risk":{"type":["string","null"],"enum":["normal","elevated","highest","not_assessed","unknown",null],"description":"Must be one of normal, elevated, highest, not_assessed, unknown.","examples":["normal"]}}},"verification":{"type":["object","null"],"additionalProperties":false,"properties":{"kind":{"type":["string","null"],"enum":["otp","email_link","3ds","id_document","liveness","captcha",null],"description":"Must be one of otp, email_link, 3ds, id_document, liveness, captcha.","examples":["otp"]},"result":{"type":["string","null"],"enum":["passed","failed",null],"description":"Must be one of passed, failed.","examples":["passed"]},"at":{"type":["string","null"],"format":"date-time","maxLength":64,"description":"Must be an ISO 8601 timestamp. Send RFC 3339, as the example. Any other form JavaScript's Date.parse reads (2026-11-01, for one) is taken too, and stored as the instant it reads, in UTC.","examples":["2026-11-01T09:30:00Z"]}}}}},"ListRequest":{"type":"object","properties":{"op":{"type":"string","enum":["add","remove"],"x-on-invalid":"refused","examples":["add"]},"kind":{"type":"string","enum":["visitor","email","network"],"x-on-invalid":"refused","examples":["visitor"]},"value":{"type":"string","description":"By kind: a visitor id from an answer, an email address, or an IP address or its /24 (IPv4) or /48 (IPv6).","x-on-invalid":"refused","examples":["0b6f3c1e-8f2a-4d5b-9c7e-1a2b3c4d5e6f"]},"action":{"type":"string","description":"The label to give, on add: allow is Trust, block is Block. Required on add, one of allow or block; not read on remove.","x-on-invalid":"refused","examples":["allow"]},"request_id":{"type":["string","null"],"minLength":1,"maxLength":128,"description":"One id for the write you mean, the same on every attempt at it (a new UUID is best; never one made from the target). A repeat in the next 72 hours answers the first answer, with the header heedeo-replayed: true, and writes nothing; one that answered another write (another op, kind, value or action) is a 409. Without one, a write is applied each time it arrives.","x-on-invalid":"refused","examples":["3c9e1f7a-5b2d-4e8f-a1c3-9d7b6e5f4a3b"]}},"required":["op","kind","value"],"anyOf":[{"properties":{"op":{"const":"remove"}}},{"properties":{"action":{"type":"string","enum":["allow","block"]}},"required":["action"]}]},"OutcomeRequest":{"type":"object","properties":{"event_id":{"type":["string","null"],"format":"uuid","description":"An event_id from an answer.","x-on-invalid":"refused","examples":["7e6d5c4b-3a2f-4e1d-9c0b-a9f8e7d6c5b4"]},"visitor_id":{"type":["string","null"],"format":"uuid","description":"A visitor id from an answer.","x-on-invalid":"refused","examples":["0b6f3c1e-8f2a-4d5b-9c7e-1a2b3c4d5e6f"]},"user_id":{"type":["string","null"],"minLength":1,"maxLength":256,"description":"Your account's id; hashed on arrival.","x-on-invalid":"refused","examples":["user_42"]},"outcome":{"type":"string","enum":["abuse","legit"],"x-on-invalid":"refused","examples":["abuse"]},"label":{"type":["string","null"],"pattern":"^[\\w.:-]{1,64}$","description":"Your word for it, such as trial_abuse.","x-on-invalid":"refused","examples":["trial_abuse"]},"note":{"type":["string","null"],"description":"Kept as one line of up to 200 characters: a longer one is cut there, never refused, and a value that is not a string is left out.","x-kept-length":200,"x-on-invalid":"dropped","examples":["Second trial on a new card"]},"request_id":{"type":["string","null"],"minLength":1,"maxLength":128,"description":"One id for the report you mean, the same on every attempt at it. A repeat in the next 72 hours answers the first answer, with the header heedeo-replayed: true, and replaces nothing; one that answered another report (another subject, outcome, label or note) is a 409.","x-on-invalid":"refused","examples":["5d8a2c6e-1f4b-4a9d-b7e3-2c1f0a9b8d7e"]}},"required":["outcome"],"description":"Exactly one of event_id, visitor_id and user_id, with the outcome.","oneOf":[{"required":["event_id"],"properties":{"event_id":{"not":{"type":"null"}}}},{"required":["visitor_id"],"properties":{"visitor_id":{"not":{"type":"null"}}}},{"required":["user_id"],"properties":{"user_id":{"not":{"type":"null"}}}}]},"Evaluation":{"type":"object","properties":{"flagged":{"type":"boolean","description":"Stop this action: flag is true and you have not pressed Trust on the person. Heedeo's own reading, the same as the package's verdict.flagged. Read this, not flag."},"no_token":{"type":"boolean","description":"The event came with no usable token (a note no_token, token_expired or unknown_visitor), false for a person you trust: ask for your own verification step."},"flag":{"type":"boolean","description":"The event reached your flag line, or you blocked or reported the person. Does not know about Trust: read flagged."},"score":{"type":"number","description":"The reasons' weights added up."},"threshold":{"type":"number","description":"Deprecated: read thresholds.review.","deprecated":true},"thresholds":{"type":"object","properties":{"verify":{"type":"number"},"review":{"type":"number"},"block":{"type":["number","null"]},"action":{"type":["string","null"]}},"required":["verify","review"]},"suggested":{"type":"string","description":"The band the score falls in at your thresholds."},"decision":{"type":"object","properties":{"flag":{"type":"boolean","description":"Deprecated: read flagged.","deprecated":true},"band":{"type":"string","description":"Deprecated: read suggested.","deprecated":true},"score":{"type":"number","description":"Deprecated: read score.","deprecated":true},"risk":{"type":["string","null"],"description":"A risk: account_abuse, or one added later."},"by":{"type":["string","null"],"description":"What decided: risk, label, report, retry, or one added later."},"judged":{"type":"array","items":{"type":"string","description":"A risk: account_abuse, or one added later."}}},"required":["flag","band","score","risk","by","judged"]},"risks":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/RiskScore"}},"version":{"type":["string","null"],"format":"date-time","description":"When your weights last changed, null while the defaults are in use."},"reasons":{"type":"array","items":{"$ref":"#/components/schemas/Reason"},"description":"Each reason with the weight your table gave it; a note (no_token, token_expired, unknown_visitor) weighs 0."},"visitor":{"anyOf":[{"$ref":"#/components/schemas/Person"},{"type":"null"}],"description":"The person across their browsers; null with no usable token."},"related":{"type":"array","items":{"$ref":"#/components/schemas/Related"}},"account":{"type":["object","null"],"properties":{"visitors":{"type":"object","properties":{"24h":{"type":"integer"},"7d":{"type":"integer"},"all":{"type":"integer"}},"required":["24h","7d","all"]},"new_browser":{"type":"boolean"}},"required":["visitors","new_browser"]},"counts":{"type":"object","properties":{"24h":{"type":"integer"},"7d":{"type":"integer"}},"required":["24h","7d"]},"activity":{"type":"object","properties":{"last_hour":{"type":"integer"},"hourly_norm":{"type":"number"},"burst":{"type":"boolean"}},"required":["last_hour","hourly_norm","burst"]},"listed":{"type":["string","null"],"enum":["allow","block",null],"description":"Your label on the person when the event was scored: allow (Trust), block (Block) or null."},"email":{"type":["object","null"],"properties":{"disposable":{"type":"boolean"},"alias_of_existing":{"type":"boolean"},"pattern":{"type":"boolean"},"free_provider":{"type":"boolean"},"role_address":{"type":"boolean"},"young_domain":{"type":"boolean"},"domain_registered":{"type":["string","null"],"format":"date-time"},"domain_age_unknown":{"type":"boolean"},"breach":{"type":["object","null"],"properties":{"first":{"type":["string","null"],"format":"date"}},"required":["first"]}},"required":["disposable","alias_of_existing","pattern"]},"network":{"anyOf":[{"$ref":"#/components/schemas/Network"},{"type":"null"}]},"agent":{"type":["object","null"],"properties":{"operator":{"type":["string","null"]},"verified":{"type":"boolean"},"declared":{"type":["string","null"]},"operator_network":{"type":["string","null"]},"automation":{"type":["string","null"]},"http_client":{"type":["string","null"]},"missing_headers":{"type":["array","null"],"items":{"type":"string"}}},"required":["operator","verified","declared","operator_network","automation"]},"environment":{"anyOf":[{"$ref":"#/components/schemas/Environment"},{"type":"null"}]},"event_id":{"type":"string","format":"uuid","description":"This event's id: what GET /v1/evaluations/{id} and POST /v1/outcomes take."}},"required":["flagged","no_token","flag","score","threshold","reasons","visitor","counts","activity","listed","email","network","agent","event_id"],"description":"The evaluation: what Heedeo answered for one event."},"Person":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"confidence":{"type":"number"},"first_seen":{"type":"string","format":"date-time"},"last_seen":{"type":"string","format":"date-time"},"emails":{"type":"object","properties":{"24h":{"type":"integer"},"7d":{"type":"integer"},"all":{"type":"integer"}},"required":["24h","7d","all"]},"accounts":{"type":"object","properties":{"24h":{"type":"integer"},"7d":{"type":"integer"},"all":{"type":"integer"}},"required":["24h","7d","all"]},"addresses":{"type":"object","properties":{"1h":{"type":"integer"},"24h":{"type":"integer"},"7d":{"type":"integer"}},"required":["1h","24h","7d"]},"countries":{"type":"object","properties":{"1h":{"type":"integer"},"24h":{"type":"integer"},"7d":{"type":"integer"}},"required":["1h","24h","7d"]},"browsers":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"kind":{"type":"string"},"confidence":{"type":"number"},"merged_by":{"type":"string"},"evidence":{"type":"object","properties":{"kind":{"type":"string"},"score":{"type":"number"}}},"first_seen":{"type":"string","format":"date-time"},"last_seen":{"type":"string","format":"date-time"}},"required":["id","kind","confidence","merged_by","first_seen","last_seen"]}},"aliases":{"type":"array","items":{"type":"string","format":"uuid"}},"actions":{"type":"object","additionalProperties":{"type":"object","properties":{"count":{"type":"integer"},"first":{"type":["string","null"],"format":"date-time"},"last":{"type":["string","null"],"format":"date-time"}},"required":["count","first","last"]}},"merged_into":{"type":"string","format":"uuid","description":"On GET /v1/visitors/{id}: the id asked for was retired by a merge, and this is the visitor it joined."}},"required":["id","confidence","first_seen","last_seen","emails","accounts"]},"Network":{"type":"object","properties":{"link_weight":{"type":"number"},"datacenter":{"type":"boolean"},"vpn":{"type":["boolean","null"]},"vpn_confidence":{"type":["string","null"],"description":"How sure: high, medium, low, or one added later."},"vpn_service":{"type":["string","null"]},"residential_proxy":{"type":["boolean","null"]},"residential_proxy_confidence":{"type":["string","null"],"description":"How sure: high, medium, low, or one added later."},"proxy_services":{"type":["array","null"],"items":{"type":"string"}},"proxy":{"type":["boolean","null"]},"tor":{"type":"boolean"},"private_relay":{"type":"boolean"},"anonymous":{"type":["boolean","null"]},"network_type":{"type":["string","null"]},"asn":{"type":["string","null"]},"devices":{"type":["integer","null"]},"risks":{"type":["array","null"],"items":{"type":"string"}},"country":{"type":["string","null"]},"city":{"type":["string","null"]},"region":{"type":["string","null"]},"timezone_mismatch":{"type":["boolean","null"]}},"required":["datacenter","vpn","proxy","tor","private_relay","anonymous","network_type","asn"]},"Environment":{"type":"object","properties":{"tampered":{"type":["boolean","null"]},"tampered_confidence":{"type":["string","null"],"description":"How sure: high, medium, low, or one added later."},"tamper":{"type":"array","items":{"type":"string"}},"virtual_machine":{"type":"boolean"},"virtual_machine_confidence":{"type":["string","null"],"description":"How sure: high, medium, low, or one added later."},"vm":{"type":"array","items":{"type":"string"}},"private_mode":{"type":["boolean","null"]},"rarity":{"type":["integer","null"]},"anti_detect":{"type":["boolean","null"],"description":"True when the browser disguises what it is; null when nothing showed. Never false."},"automation_framework":{"type":["boolean","null"],"description":"True when an automation tool controlled the browser or its developer tools were open; null when nothing showed. Never false."},"interaction":{"type":["object","null"],"properties":{"pointer":{"type":"integer"},"key":{"type":"integer"},"touch":{"type":"integer"}},"required":["pointer","key","touch"]},"connection":{"type":["object","null"],"properties":{"tls":{"type":["string","null"]},"cipher":{"type":["string","null"]},"protocol":{"type":["string","null"]},"rtt_ms":{"type":["number","null"]}},"required":["tls","cipher","protocol","rtt_ms"]}},"required":["tampered","tamper","virtual_machine","vm","private_mode"]},"Related":{"type":"object","properties":{"visitor_id":{"type":"string","format":"uuid"},"link":{"type":"string","description":"How the browsers are related: shared_account, shared_email, shared_principal, shared_phone, shared_card, probable, same_machine, ip, or one added later."},"weight":{"type":"number"},"identities":{"type":"integer"}},"required":["visitor_id","link","weight","identities"]},"RiskScore":{"type":"object","properties":{"score":{"type":"number"},"judged":{"type":"boolean"},"reasons":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string","description":"The reason's code: listed_block, receipt_reused, disposable_email, no_mx, alias_of_existing, declared_agent, many_identities, probable_match, related_identities, confirmed_abuse, repeat_action, account_browsers, datacenter, network_burst, email_pattern, domain_burst, device_burst, young_domain, recent_domain, operator_burst, action_rate, tor, vpn, proxy, anonymizer, hosting, network_changed, account_new_browser, role_address, email_never_seen, browser_tampered, virtual_machine, private_mode, private_signup, language_change, automation, residential_proxy, address_rate, voip_phone, prepaid_card, verification_failed, email_unverified, card_mismatch, relay_email, verified_id, paid_history, wallet_card, legit_outcome, verified_phone, card_on_file, tenure, email_tenure, no_token, token_expired, unknown_visitor, no_identity, or one added later."},"weight":{"type":"number"}},"required":["code","weight"]}}},"required":["score","judged","reasons"]},"Reason":{"type":"object","properties":{"code":{"type":"string","description":"The reason's code: listed_block, receipt_reused, disposable_email, no_mx, alias_of_existing, declared_agent, many_identities, probable_match, related_identities, confirmed_abuse, repeat_action, account_browsers, datacenter, network_burst, email_pattern, domain_burst, device_burst, young_domain, recent_domain, operator_burst, action_rate, tor, vpn, proxy, anonymizer, hosting, network_changed, account_new_browser, role_address, email_never_seen, browser_tampered, virtual_machine, private_mode, private_signup, language_change, automation, residential_proxy, address_rate, voip_phone, prepaid_card, verification_failed, email_unverified, card_mismatch, relay_email, verified_id, paid_history, wallet_card, legit_outcome, verified_phone, card_on_file, tenure, email_tenure, no_token, token_expired, unknown_visitor, no_identity, or one added later."},"text":{"type":"string"},"weight":{"type":"number"},"base":{"type":"number"}},"required":["code","text","weight"]},"Answered":{"allOf":[{"$ref":"#/components/schemas/Evaluation"},{"type":"object","properties":{"answered_at":{"type":"string","format":"date-time","description":"When Heedeo gave it."},"action":{"type":"string","description":"The action it was for."}},"required":["answered_at","action"]}]},"Standing":{"type":"object","properties":{"visitor":{"$ref":"#/components/schemas/Person"},"listed":{"type":["string","null"],"enum":["allow","block",null],"description":"Your label on the person now."},"network":{"anyOf":[{"$ref":"#/components/schemas/Network"},{"type":"null"}]},"environment":{"anyOf":[{"$ref":"#/components/schemas/Environment"},{"type":"null"}]},"score":{"type":"number","description":"The standing score: account_abuse's on the decision."},"thresholds":{"type":"object","properties":{"verify":{"type":"number"},"review":{"type":"number"},"block":{"type":["number","null"]}}},"suggested":{"type":"string","description":"The band the standing score reaches; no verdict on the person: read risks. The band: allow, verify, review, block, or one added later."},"risks":{"type":"object","additionalProperties":{"type":"object","properties":{"score":{"type":"number"},"reasons":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"weight":{"type":"number"}}}}}}},"related":{"type":"array","items":{"$ref":"#/components/schemas/Related"}},"trust":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"text":{"type":"string"},"weight":{"type":"number"}}}},"outcomes":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","description":"What the report is about: event, visitor, account, or one added later."},"outcome":{"type":"string","enum":["abuse","legit"]},"label":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"}}}},"recent":{"type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"at":{"type":"string","format":"date-time"},"browser_id":{"type":"string","format":"uuid"},"suggested":{"type":["string","null"],"description":"The band: allow, verify, review, block, or one added later."},"outcome":{"type":["string","null"],"enum":["abuse","legit",null]}}}}},"required":["visitor","listed","score","risks"]},"Arrivals":{"type":"object","properties":{"count":{"type":"number"},"first":{"type":["string","null"],"format":"date-time"},"last":{"type":["string","null"],"format":"date-time"}},"required":["count","first","last"]},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"The key into x-heedeo-errors: missing_key, invalid_key, publishable_key, secret_key, key_mismatch, origin_not_allowed, invalid_bundle, invalid_request, not_found, too_large, rate_limited, internal, busy, or one added later."},"message":{"type":"string","description":"What happened, in words; for a person, not a program."},"trace_id":{"type":"string","description":"The id of this request in Heedeo's log, as the heedeo-trace-id header gives it: quote it to Heedeo support. Not request_id, your own key for an attempt."}},"required":["code","message","trace_id"],"additionalProperties":true}},"required":["error"]}}},"x-heedeo-errors":{"missing_key":{"401":{"means":"No key was sent.","next":"Set the secret key where the server runs. Until then nobody is checked."}},"invalid_key":{"401":{"means":"The key is not one of yours.","next":"Set the secret key where the server runs. Until then nobody is checked."}},"publishable_key":{"403":{"means":"A publishable key was sent where the secret key belongs.","next":"Send the secret key (`sk_`)."}},"secret_key":{"403":{"means":"A secret key was sent to identify, where the publishable key belongs.","next":"Load the script with the publishable key (`pk_`), and keep the secret key on your server."}},"key_mismatch":{"403":{"means":"Identify on a host that serves one workspace (your own domain, or its prefix on Heedeo's) was sent another workspace's publishable key.","next":"Use the publishable key of the workspace that host belongs to."}},"origin_not_allowed":{"403":{"means":"The page's origin is not one the workspace allows to identify (the Domains page).","next":"Add the page's origin on the Domains page."}},"invalid_bundle":{"400":{"means":"Identify's request is not one the script sends.","next":"Load the script from Heedeo or your own route to it; do not call identify by hand."}},"invalid_request":{"400":{"means":"The call is malformed. A token Heedeo cannot read is not an error: the event is scored as no token.","next":"Read `message` and fix the call."},"409":{"means":"The `request_id` already answered another call: another event (another action, or another token), or on `/v1/lists` and `/v1/outcomes` another write (another target, action or outcome). A `request_id` is one attempt at one write, and its stored answer is never given to a different one. A report sent again under its `request_id` after you deleted the person it was on is refused too: the report went with them and is not written again. A report on an account another of your visitors holds too, or on a browser a split left with another visitor, is not one of these: it stays with that visitor, and its repeat answers as before, naming them.","next":"Make one `request_id` for each action, and for each label write and report you mean; send the same one on its retries."}},"not_found":{"404":{"means":"No such path, or no visitor or event of yours has that id (`GET` and `DELETE /v1/visitors/:id`, `POST /v1/outcomes`), or no evaluation of yours has it (`GET /v1/evaluations/:id`: an answer is kept 30 days, or as long as a flag holds its event, and deleted with its visitor). An id deleted before is not this: a second deletion answers 200.","next":"Check the path and the id you sent: an event id for an evaluation, a visitor id for a visitor."}},"too_large":{"413":{"means":"The body is over 64 KB (`/v1/evaluations`, `/v1/lists`, `/v1/outcomes` and identify's request).","next":"Send a smaller body."}},"rate_limited":{"429":{"means":"Too many calls, with a Retry-After. From identify, past 120 a minute from one end user's address or 6,000 a minute for one workspace: `identify()` then rejects, and your page sends no token. The dashboard's own forms answer it too.","next":"Your server asks for verification where your app has a step (section 3: https://app.heedeo.com/docs/integration/3.md)."}},"internal":{"500":{"means":"Heedeo failed while answering.","next":"Carry on as if Heedeo were not there (section 5: https://app.heedeo.com/docs/integration/5.md). That is never a refusal."}},"busy":{"503":{"means":"Another event on the same token arrived at the same moment.","next":"Send the call again with the same `request_id` (the package's helper does)."}}}}