{
  "openapi": "3.1.0",
  "info": {
    "title": "Orisift API",
    "version": "1",
    "summary": "Validation and risk evidence for phone numbers, email addresses, IP addresses and domains.",
    "description": "One endpoint checks one identifier and returns the same response shape for all four types: a verdict, a risk score, the signals behind it with their sources, the per-capability states, and the limitations that applied.\n\nThis specification describes the production API at https://orisift.com and nothing else. Staging-only features and internal operational endpoints are deliberately absent.\n\n**Branch on structured fields, not on prose.** `headline`, `summary` and `recommendation` are for display and their wording can change without a version bump. `verdict`, `capabilities` and `coverage_limitations` are the contract.\n\n**Versioning.** Additive fields may appear without notice. Removals and changes of meaning ship as a new path version.",
    "contact": {
      "name": "Orisift",
      "email": "support@orisift.com",
      "url": "https://orisift.com/docs"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://orisift.com/terms"
    }
  },
  "servers": [
    {
      "url": "https://orisift.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Lookup",
      "description": "Check one identifier."
    },
    {
      "name": "Signup screening",
      "description": "Check several identifiers from one signup as a single billable unit."
    }
  ],
  "paths": {
    "/v1/lookup": {
      "post": {
        "tags": [
          "Lookup"
        ],
        "operationId": "lookup",
        "summary": "Check one identifier",
        "description": "Checks a single identifier. The type is detected from the input unless `type` is supplied.\n\n**One identifier per request.** Four fields on a form means four requests, each charged at its own rate.\n\n**Cost.** Phone, email and IP cost 1 credit; domain costs 2. A completed check that establishes no evidence is not charged and returns `usage.not_charged`.\n\n**Retries.** Send `Idempotency-Key` and a retry replays the stored result without a second charge. Reusing a key for different inputs or options is refused with 409.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "An opaque key of your choosing. A repeat of the same request with the same key replays the original result and is not charged again. Reusing it for a different input, type, country or policy is refused with 409.",
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "example": "signup-8f21c4-email"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LookupRequest"
              },
              "examples": {
                "international": {
                  "summary": "International phone number",
                  "value": {
                    "input": "+12125550142"
                  }
                },
                "national": {
                  "summary": "National number, country supplied",
                  "value": {
                    "input": "020 7946 0000",
                    "type": "phone",
                    "country": "GB"
                  }
                },
                "withPolicy": {
                  "summary": "Ask for a structured action",
                  "value": {
                    "input": "someone@example.com",
                    "type": "email",
                    "policy": "contact_standard",
                    "policy_version": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The check completed. This includes verdicts of `rejected` and `insufficient_evidence`: a conclusion against the identifier is a successful request.",
            "headers": {
              "x-request-id": {
                "description": "Quote this when reporting a problem.",
                "schema": {
                  "type": "string"
                }
              },
              "x-orisift-credits-charged": {
                "description": "Credits taken for this request. `0` when nothing was established.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "x-orisift-credit-balance": {
                "description": "Remaining balance after this request.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "x-truelook-credits-charged": {
                "description": "Legacy alias of `x-orisift-credits-charged`, carrying the same value. Retained for compatibility with integrations written before the Orisift header names existed. Prefer `x-orisift-credits-charged` in new code.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "x-truelook-credit-balance": {
                "description": "Legacy alias of `x-orisift-credit-balance`, carrying the same value. Retained for compatibility with integrations written before the Orisift header names existed. Prefer `x-orisift-credit-balance` in new code.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "server-timing": {
                "description": "Total server duration.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Lookup"
                }
              }
            }
          },
          "400": {
            "description": "The request body is not valid JSON, or a field failed validation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "invalid_request",
                    "message": "input is required",
                    "request_id": "req_0ce5bce7e8cd4dcf9b6e",
                    "param": "input"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No API key, or a key that is not valid, has been revoked, or belongs to a suspended account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "authentication_required",
                    "message": "Provide your API key as `Authorization: Bearer tl_live_...`.",
                    "request_id": "req_0ce5bce7e8cd4dcf9b6e"
                  }
                }
              }
            }
          },
          "402": {
            "description": "The credit balance cannot cover this lookup. Nothing was run and nothing was charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "insufficient_credits",
                    "message": "Your credit balance cannot cover this lookup. Top up or upgrade to continue.",
                    "request_id": "req_0ce5bce7e8cd4dcf9b6e",
                    "balance": 0,
                    "required": 2
                  }
                }
              }
            }
          },
          "405": {
            "description": "Wrong method. Use POST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "This `Idempotency-Key` was already used for a different request. Nothing was reserved, no lookup ran and no credit was spent. Use a new key for a different request, or resend the original unchanged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "idempotency_key_reused",
                    "message": "This Idempotency-Key was already used for a different request. Use a new key for a different request, or resend the original request unchanged.",
                    "request_id": "req_0ce5bce7e8cd4dcf9b6e",
                    "hint": "Keys must not be reused across different inputs or options."
                  }
                }
              }
            }
          },
          "422": {
            "description": "The input could not be recognised as any supported identifier. Pass `type` to skip detection, or `country` for a national-format phone number.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Plan rate limit exceeded. Retry after the interval in the `retry-after` header.",
            "headers": {
              "retry-after": {
                "description": "Seconds to wait.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "rate_limited",
                    "message": "Rate limit of 120 requests per minute exceeded for your plan.",
                    "request_id": "req_0ce5bce7e8cd4dcf9b6e",
                    "retry_after": 30
                  }
                }
              }
            }
          },
          "500": {
            "description": "The check could not be completed. Any credit reservation is released; if that release itself failed, reconciliation returns it within fifteen minutes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "options": {
        "tags": [
          "Lookup"
        ],
        "operationId": "lookupPreflight",
        "summary": "CORS preflight",
        "description": "The API is server to server, and the preflight says so: a browser preflight is answered with 405 and `Allow: POST` rather than CORS headers, with an `x-orisift-cors` header explaining why. That header is also sent under its legacy name, `x-truelook-cors`, with the same value.\n\n**This is a signal, not a security control.** Refusing the preflight does not protect an API key that has been embedded in frontend code: such a key is already visible to anyone who views the page or its bundle, and no CORS behaviour can undo that. The refusal only tells you that browser use is unsupported. Keys must be held server-side and never shipped to a client.",
        "security": [],
        "responses": {
          "405": {
            "description": "Browser use is unsupported. Hold the key server-side; a key in client code is already exposed regardless of what this endpoint answers.",
            "headers": {
              "allow": {
                "description": "The one method this endpoint accepts.",
                "schema": {
                  "type": "string",
                  "const": "POST"
                }
              },
              "x-orisift-cors": {
                "description": "Why the preflight was refused.",
                "schema": {
                  "type": "string"
                }
              },
              "x-truelook-cors": {
                "description": "Legacy alias of `x-orisift-cors`, carrying the same value. Retained for compatibility with integrations written before the Orisift header names existed. Prefer `x-orisift-cors` in new code.",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Lookup"
        ],
        "operationId": "lookupWrongMethod",
        "summary": "Not supported",
        "security": [],
        "responses": {
          "405": {
            "description": "Use POST for /v1/lookup.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/signup": {
      "post": {
        "tags": [
          "Signup screening"
        ],
        "operationId": "screenSignup",
        "summary": "Screen several identifiers from one signup",
        "description": "Checks up to four identifiers as a single billable unit and, when a policy is named, returns one recommended action for the whole signup.\n\n**Server-side only.** The key spends credits; a key in browser code is a leaked key.\n\n**Charging.** `quote.maximum_credits` is stated before the work runs and reserved in full. The actual charge is the standalone cost of the checks that established evidence, capped, and is settled down from the quote. A screening is never more expensive than running the same establishing checks separately, and costs nothing if none established.\n\n**Replay.** The same `Idempotency-Key` with the same body returns the stored screening and charges nothing; `usage.credits` then repeats what that screening originally cost. Any change to the identifiers, `country`, `policy`, `policy_version` or `email_verification` makes it a different request and is refused with 409.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "An opaque key of your choosing. A repeat of the same request with the same key replays the original screening and is not charged again. Reusing it for a different request is refused with 409; nothing is reserved and no credit is spent.",
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "example": "signup-8f21c4"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScreeningRequest"
              },
              "example": {
                "email": "someone@stripe.com",
                "phone": "020 7946 0000",
                "domain": "example.com",
                "policy": "contact_standard"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The screening ran. A partial outcome is a success: checks that could not complete are reported in `checks[].incomplete` with their reason class, and the evidence other checks established is kept.",
            "headers": {
              "x-request-id": {
                "description": "Quote this when reporting a problem.",
                "schema": {
                  "type": "string"
                }
              },
              "x-orisift-credits-charged": {
                "description": "Credits taken for this screening, matching `usage.credits`. `0` when no check established evidence. On an idempotent replay this is the ORIGINAL cost, not an additional debit.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "x-orisift-credit-balance": {
                "description": "Remaining balance after this screening, matching `usage.balance`.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "x-truelook-credits-charged": {
                "description": "Legacy alias of `x-orisift-credits-charged`, carrying the same value. Retained for compatibility with integrations written before the Orisift header names existed. Prefer `x-orisift-credits-charged` in new code.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "x-truelook-credit-balance": {
                "description": "Legacy alias of `x-orisift-credit-balance`, carrying the same value. Retained for compatibility with integrations written before the Orisift header names existed. Prefer `x-orisift-credit-balance` in new code.",
                "schema": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "server-timing": {
                "description": "Total server duration.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Screening"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request: no identifier supplied, `country` without `phone`, an unknown policy version, `email_verification: \"deep\"` with no provider connected, or an unknown field. Nothing was reserved and nothing was charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "invalid_request",
                    "message": "Unknown field: context. This endpoint rejects fields it does not use rather than ignoring them.",
                    "request_id": "req_0ce5bce7e8cd4dcf9b6e",
                    "param": "context"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid or revoked API key, or a suspended account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "The credit balance cannot cover the quoted maximum. The request was refused rather than started and then found unaffordable. Nothing was charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "insufficient_credits",
                    "message": "A screening costs at most 2 credits: the standalone cost of the identifiers supplied, capped.",
                    "request_id": "req_0ce5bce7e8cd4dcf9b6e",
                    "balance": 1,
                    "required": 2
                  }
                }
              }
            }
          },
          "405": {
            "description": "Use POST. The preflight is refused because this API is server-to-server.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The Idempotency-Key cannot answer this request, for one of two reasons, distinguished by `error.type`.\n\n`idempotency_key_reused`: the key was already used for a DIFFERENT request. Nothing was reserved, no screening ran and no credit was spent. The response is deliberately silent about the earlier request: a key is not a secret, so describing what it was first used for would turn a guessed key into a disclosure.\n\n`idempotency_key_expired`: the key matches a screening that has passed its retention period, so there is no stored result to return. No check was executed and no accounting changed: the balance and the ledger are exactly as they were. Use a NEW key to run a fresh screening, which is billed normally. Retrying with the same key will always return this.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "idempotency_key_reused",
                    "message": "This Idempotency-Key was already used for a different screening.",
                    "request_id": "req_0ce5bce7e8cd4dcf9b6e",
                    "hint": "Any change to the identifiers, `country`, `policy`, `policy_version` or `email_verification` makes this a different request."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit for your plan exceeded. `retry_after` is in seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The screening service hit an error. The reservation is released and nothing is charged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "options": {
        "tags": [
          "Signup screening"
        ],
        "operationId": "screenSignupPreflight",
        "summary": "CORS preflight",
        "description": "The API is server to server, and the preflight says so: a browser preflight is answered with 405 and `Allow: POST` rather than CORS headers, with an `x-orisift-cors` header explaining why. That header is also sent under its legacy name, `x-truelook-cors`, with the same value.\n\n**This is a signal, not a security control.** Refusing the preflight does not protect an API key that has been embedded in frontend code: such a key is already visible to anyone who views the page or its bundle, and no CORS behaviour can undo that. The refusal only tells you that browser use is unsupported. Keys must be held server-side and never shipped to a client.",
        "security": [],
        "responses": {
          "405": {
            "description": "Browser use is unsupported. Hold the key server-side; a key in client code is already exposed regardless of what this endpoint answers.",
            "headers": {
              "allow": {
                "description": "The one method this endpoint accepts.",
                "schema": {
                  "type": "string",
                  "const": "POST"
                }
              },
              "x-orisift-cors": {
                "description": "Why the preflight was refused.",
                "schema": {
                  "type": "string"
                }
              },
              "x-truelook-cors": {
                "description": "Legacy alias of `x-orisift-cors`, carrying the same value. Retained for compatibility with integrations written before the Orisift header names existed. Prefer `x-orisift-cors` in new code.",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Signup screening"
        ],
        "operationId": "screenSignupWrongMethod",
        "summary": "Not supported",
        "security": [],
        "responses": {
          "405": {
            "description": "Use POST for /v1/signup.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Your API key, as `Authorization: Bearer tl_live_...`. Create one in the dashboard.\n\nServer to server only. A key shipped to a browser is exposed to anyone who views the page or its bundle, and nothing this API does can protect it once that has happened. Call the API from your backend and never place the key in client code."
      }
    },
    "schemas": {
      "LookupRequest": {
        "type": "object",
        "required": [
          "input"
        ],
        "additionalProperties": false,
        "properties": {
          "input": {
            "type": "string",
            "minLength": 1,
            "maxLength": 320,
            "description": "The identifier to check. One only.",
            "example": "+12125550142"
          },
          "type": {
            "type": "string",
            "enum": [
              "phone",
              "email",
              "ip",
              "domain"
            ],
            "description": "Skips detection and selects a checker. It does not assert the value is valid."
          },
          "country": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2,
            "pattern": "^[A-Za-z]{2}$",
            "description": "ISO 3166-1 alpha-2. Required to interpret a phone number in national format.",
            "example": "GB"
          },
          "retain_input": {
            "type": "boolean",
            "default": false,
            "description": "Store the submitted value against this lookup. Off by default."
          },
          "explain": {
            "type": "boolean",
            "default": false,
            "description": "Ask for an AI explanation. Off by default, drawn from a separate monthly allowance, and it never changes the verdict or the score."
          },
          "policy": {
            "type": "string",
            "enum": [
              "contact_standard"
            ],
            "description": "Ask for a recommended action under a named policy. Omitting it returns exactly the response you received before this field existed."
          },
          "policy_version": {
            "type": "integer",
            "minimum": 1,
            "description": "Pin a policy version. Omitted means the latest at the time of the request. The version that was used always comes back in `action.policy_version`."
          }
        }
      },
      "Lookup": {
        "type": "object",
        "required": [
          "object",
          "api_version",
          "id",
          "created",
          "type",
          "verdict",
          "risk_score",
          "evidence_coverage",
          "headline",
          "summary",
          "recommendation",
          "attributes",
          "signals",
          "capabilities",
          "coverage_limitations",
          "analysis",
          "usage",
          "latency_ms"
        ],
        "properties": {
          "object": {
            "type": "string",
            "const": "lookup"
          },
          "api_version": {
            "type": "string",
            "const": "1"
          },
          "id": {
            "type": "string",
            "example": "lk_67e75a1d70e94a0692b178e6"
          },
          "created": {
            "type": "string",
            "format": "date-time"
          },
          "type": {
            "type": "string",
            "enum": [
              "phone",
              "email",
              "ip",
              "domain"
            ]
          },
          "country": {
            "type": [
              "string",
              "null"
            ],
            "description": "Resolved country, where one applies."
          },
          "normalized": {
            "type": [
              "string",
              "null"
            ],
            "description": "Display form. Phones use international formatting with spaces, not compact E.164; read the E.164 attribute if you need that."
          },
          "verdict": {
            "$ref": "#/components/schemas/Verdict"
          },
          "risk_score": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 100,
            "description": "Higher means more risk under the scoring rules. Not a probability of fraud. Null when the verdict is insufficient_evidence."
          },
          "evidence_coverage": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "How much of the available evidence was obtained. Not a confidence in the verdict."
          },
          "headline": {
            "type": "string",
            "description": "Display only. Wording can change without a version bump."
          },
          "summary": {
            "type": "string",
            "description": "Display only. Wording can change without a version bump."
          },
          "recommendation": {
            "type": "string",
            "description": "Display only. For a machine-readable decision, send `policy` and read `action`."
          },
          "attributes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Attribute"
            }
          },
          "signals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Signal"
            }
          },
          "capabilities": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Capability"
            }
          },
          "coverage_limitations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Coverage"
            }
          },
          "analysis": {
            "$ref": "#/components/schemas/Analysis"
          },
          "usage": {
            "$ref": "#/components/schemas/Usage"
          },
          "latency_ms": {
            "type": "integer",
            "description": "Execution duration. Not an uptime or latency guarantee."
          },
          "action": {
            "$ref": "#/components/schemas/Action"
          }
        }
      },
      "Verdict": {
        "type": "string",
        "enum": [
          "verified",
          "risky",
          "rejected",
          "insufficient_evidence"
        ],
        "description": "`insufficient_evidence` is its own outcome and is never an automatic rejection: it means the checks could not establish enough, not that the identifier is bad."
      },
      "Attribute": {
        "type": "object",
        "required": [
          "label",
          "value",
          "observed"
        ],
        "properties": {
          "label": {
            "type": "string"
          },
          "value": {
            "type": "string"
          },
          "observed": {
            "type": "boolean",
            "description": "False means derived rather than observed."
          }
        }
      },
      "Signal": {
        "type": "object",
        "required": [
          "label",
          "value",
          "tone",
          "source",
          "provenance"
        ],
        "properties": {
          "label": {
            "type": "string"
          },
          "value": {
            "type": "string"
          },
          "tone": {
            "type": "string",
            "enum": [
              "good",
              "warn",
              "bad",
              "neutral",
              "unknown"
            ]
          },
          "source": {
            "type": "string",
            "description": "Where this finding came from, named.",
            "example": "National regulator allocations"
          },
          "provenance": {
            "$ref": "#/components/schemas/Provenance"
          }
        }
      },
      "Provenance": {
        "type": "string",
        "enum": [
          "live_dns",
          "live_rdns",
          "static_registry",
          "library",
          "heuristic",
          "ai_inference",
          "unavailable"
        ],
        "description": "How the finding was obtained. `heuristic` is explicitly not authoritative; `static_registry` is data shipped with the service rather than queried live."
      },
      "Capability": {
        "type": "object",
        "required": [
          "id",
          "state",
          "detail"
        ],
        "properties": {
          "id": {
            "type": "string",
            "enum": [
              "syntax",
              "plan_validity",
              "infrastructure",
              "reputation",
              "carrier",
              "reachability"
            ]
          },
          "state": {
            "$ref": "#/components/schemas/CapabilityState"
          },
          "detail": {
            "type": "string"
          }
        }
      },
      "CapabilityState": {
        "type": "string",
        "enum": [
          "verified",
          "failed",
          "inferred",
          "unsupported",
          "unavailable"
        ],
        "description": "`unsupported` means Orisift does not offer this capability and never counted it against the identifier. `unavailable` means it is offered and could not be obtained this time. The two are deliberately different."
      },
      "Coverage": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string",
            "example": "no_carrier_provider"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "Analysis": {
        "type": "object",
        "required": [
          "checks",
          "ai",
          "model"
        ],
        "properties": {
          "checks": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The checks that ran, named."
          },
          "ai": {
            "type": "string",
            "enum": [
              "disabled",
              "ok",
              "failed"
            ],
            "description": "`disabled` and `failed` both mean rules-based scoring was used. `ok` means the optional AI step completed; it does not mean its inference was independently verified."
          },
          "model": {
            "type": [
              "string",
              "null"
            ],
            "description": "A model name can be present while `ai` is not `ok`. Read `ai` to tell whether it ran."
          }
        }
      },
      "Usage": {
        "type": "object",
        "required": [
          "credits",
          "credit_cost_table",
          "balance",
          "idempotent_replay"
        ],
        "properties": {
          "credits": {
            "type": "integer",
            "minimum": 0,
            "description": "Credits for this lookup. `0` when the check established no evidence."
          },
          "credit_cost_table": {
            "$ref": "#/components/schemas/CreditCostTable"
          },
          "balance": {
            "type": "integer",
            "minimum": 0
          },
          "idempotent_replay": {
            "type": "boolean",
            "description": "True when this is a replay of an earlier request under the same Idempotency-Key. A replay reports the original cost and debits nothing further."
          },
          "not_charged": {
            "$ref": "#/components/schemas/NotCharged"
          }
        }
      },
      "CreditCostTable": {
        "type": "object",
        "required": [
          "phone",
          "email",
          "ip",
          "domain"
        ],
        "description": "Credit cost per identifier type. Returned on every response so an integration never has to hard-code it.",
        "properties": {
          "phone": {
            "type": "integer",
            "const": 1
          },
          "email": {
            "type": "integer",
            "const": 1
          },
          "ip": {
            "type": "integer",
            "const": 1
          },
          "domain": {
            "type": "integer",
            "const": 2
          }
        }
      },
      "NotCharged": {
        "type": "object",
        "required": [
          "reason",
          "capabilities"
        ],
        "description": "Present only when nothing was charged because the check established no evidence. Absent on every charged lookup.\n\nThis is not \"failed checks are free\". A check that establishes an identifier is invalid has established something, and is charged.",
        "properties": {
          "reason": {
            "type": "string",
            "enum": [
              "no_evidence_established"
            ]
          },
          "capabilities": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The capabilities that were attempted and could not be obtained."
          }
        }
      },
      "Action": {
        "type": "object",
        "required": [
          "type",
          "policy",
          "policy_version",
          "reasons",
          "not_established"
        ],
        "description": "Present only when `policy` was sent.\n\nAbsent after a policy WAS named means deciding failed and the fault was logged. It is never filled in with a default, because a fabricated `continue` is the one failure an integrator cannot detect.",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "continue",
              "ask_to_correct",
              "verify_contact",
              "review",
              "retry_later"
            ]
          },
          "policy": {
            "type": "string",
            "enum": [
              "contact_standard"
            ]
          },
          "policy_version": {
            "type": "integer",
            "minimum": 1,
            "description": "The version actually used, always returned, so a response never leaves it ambiguous."
          },
          "reasons": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Reason"
            }
          },
          "not_established": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Capability gaps, kept apart from `reasons` on purpose. \"Orisift does not verify mailboxes\" is on every email result ever returned; putting it in `reasons` would make it look like something that counted against this address."
          },
          "retry_after_seconds": {
            "type": "integer",
            "description": "Present only on `retry_later`. A constant hint, not a measurement."
          }
        }
      },
      "Reason": {
        "type": "object",
        "required": [
          "code",
          "class",
          "correctable_by",
          "evidence",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "Machine-readable. Branch on this.",
            "example": "phone_reserved_range"
          },
          "class": {
            "type": "string",
            "enum": [
              "request_context",
              "conclusive_negative",
              "evidence_gap",
              "capability_gap",
              "advisory"
            ]
          },
          "correctable_by": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "end_user",
              "caller",
              null
            ],
            "description": "`caller` means your integration must fix the request; `end_user` means the person who supplied the identifier must."
          },
          "evidence": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "message": {
            "type": "string",
            "description": "Display only."
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "type",
              "message",
              "request_id"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "invalid_request",
                  "authentication_required",
                  "invalid_api_key",
                  "insufficient_credits",
                  "method_not_allowed",
                  "idempotency_key_reused",
                  "unrecognised_input",
                  "rate_limited",
                  "internal_error"
                ]
              },
              "message": {
                "type": "string"
              },
              "request_id": {
                "type": "string"
              },
              "param": {
                "type": "string",
                "description": "On invalid_request, the field at fault."
              },
              "hint": {
                "type": "string"
              },
              "balance": {
                "type": "integer",
                "description": "On insufficient_credits."
              },
              "required": {
                "type": "integer",
                "description": "On insufficient_credits."
              },
              "retry_after": {
                "type": "integer",
                "description": "On rate_limited, seconds."
              }
            }
          }
        }
      },
      "ScreeningRequest": {
        "type": "object",
        "additionalProperties": false,
        "description": "At least one of email, phone, ip or domain. Unknown fields are refused with 400 rather than ignored, so a field you believed was taken into account never silently is not.",
        "properties": {
          "email": {
            "type": "string",
            "minLength": 1,
            "maxLength": 320,
            "example": "someone@stripe.com"
          },
          "phone": {
            "type": "string",
            "minLength": 1,
            "maxLength": 320,
            "example": "+12125550142"
          },
          "ip": {
            "type": "string",
            "minLength": 1,
            "maxLength": 320,
            "example": "8.8.8.8"
          },
          "domain": {
            "type": "string",
            "minLength": 1,
            "maxLength": 320,
            "example": "stripe.com"
          },
          "country": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2,
            "pattern": "^[A-Za-z]{2}$",
            "description": "ISO 3166-1 alpha-2. Applies to `phone` only; sending it without `phone` is a 400, because it would otherwise do nothing.",
            "example": "US"
          },
          "policy": {
            "type": "string",
            "enum": [
              "contact_standard"
            ],
            "description": "Ask for a recommended action. Omit it and `recommended_action` is absent from the response, which is the compatibility guarantee."
          },
          "policy_version": {
            "type": "integer",
            "minimum": 1,
            "description": "Pin a policy version. Omitted means the latest at the time of the request; the integer it resolved to always comes back in the action, so a response never leaves the version ambiguous. A version that does not exist is a 400."
          },
          "email_verification": {
            "type": "string",
            "enum": [
              "fast",
              "deep"
            ],
            "default": "fast",
            "description": "`deep` is refused with 400 unless a provider is connected. It is never silently downgraded to `fast`: a caller who asked for mailbox verification and received a format check would believe something about the address that nothing established."
          }
        }
      },
      "ScreeningCheckStatus": {
        "type": "string",
        "enum": [
          "completed",
          "partial",
          "attempted",
          "skipped"
        ],
        "description": "What happened to the check, kept separate from why. `completed`: ran to the end, every capability it offers resolved. `partial`: ran, produced usable evidence, and one or more capabilities could not resolve. `attempted`: started and could not complete; no usable evidence. `skipped`: never started, because validation or configuration prevented it."
      },
      "ScreeningIncomplete": {
        "type": "object",
        "required": [
          "capability",
          "code",
          "reason_class",
          "correctable_by",
          "detail"
        ],
        "properties": {
          "capability": {
            "type": "string",
            "example": "plan_validity"
          },
          "code": {
            "type": "string",
            "example": "country_required"
          },
          "reason_class": {
            "type": "string",
            "enum": [
              "request_context",
              "transient_dependency",
              "unsupported_capability"
            ],
            "description": "`request_context`: something about the request prevented it. `transient_dependency`: a resolver or registry did not answer in time. `unsupported_capability`: Orisift does not offer it."
          },
          "correctable_by": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "caller",
              null
            ]
          },
          "detail": {
            "type": "string"
          }
        }
      },
      "ScreeningCheck": {
        "type": "object",
        "required": [
          "type",
          "status",
          "established",
          "incomplete",
          "latency_ms"
        ],
        "description": "One identifier's result. Carries no `credits` field: a screening is one charge and a per-check figure here would contradict it.",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "email",
              "phone",
              "ip",
              "domain"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/ScreeningCheckStatus"
          },
          "established": {
            "type": "boolean",
            "description": "Did this check produce evidence that counts. Drives charging; does not drive the action. A `partial` check is usually `true`, which is the point of the distinction."
          },
          "verdict": {
            "$ref": "#/components/schemas/Verdict"
          },
          "risk_score": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 100
          },
          "evidence_coverage": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "headline": {
            "type": "string"
          },
          "normalized": {
            "type": "string"
          },
          "signals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Signal"
            }
          },
          "capabilities": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Capability"
            }
          },
          "coverage_limitations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Coverage"
            }
          },
          "lookup_id": {
            "type": "string",
            "example": "lk_67e75a1d70e94a06"
          },
          "incomplete": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ScreeningIncomplete"
            }
          },
          "latency_ms": {
            "type": "integer"
          }
        }
      },
      "ScreeningReason": {
        "type": "object",
        "required": [
          "input",
          "code",
          "class",
          "audience",
          "correctable_by",
          "evidence",
          "message"
        ],
        "properties": {
          "input": {
            "type": "string",
            "enum": [
              "email",
              "phone",
              "ip",
              "domain"
            ]
          },
          "code": {
            "type": "string"
          },
          "class": {
            "type": "string"
          },
          "audience": {
            "type": "string",
            "enum": [
              "caller",
              "end_user"
            ],
            "description": "Who this reason is for. An integration must never render a caller reason as an instruction to the person signing up."
          },
          "correctable_by": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "caller",
              "end_user",
              null
            ]
          },
          "evidence": {
            "type": "object",
            "description": "Which check and capability the finding came from.",
            "properties": {
              "check": {
                "type": "string"
              },
              "capability": {
                "type": "string"
              }
            }
          },
          "message": {
            "type": "string"
          }
        }
      },
      "ScreeningAction": {
        "type": "object",
        "required": [
          "type",
          "basis",
          "aggregation_policy",
          "aggregation_policy_version",
          "audience",
          "audience_note",
          "derived_from",
          "reasons",
          "per_check_actions",
          "does_not_resolve",
          "not_established"
        ],
        "description": "Present only when `policy` was named. Absent after one WAS named means deciding failed and `action_unavailable` is present instead; it is never filled in with a default, because a fabricated `continue` is the one failure an integration cannot detect.",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "continue",
              "ask_to_correct",
              "verify_contact",
              "review",
              "retry_later"
            ]
          },
          "basis": {
            "type": "string",
            "enum": [
              "policy_aggregation"
            ]
          },
          "aggregation_policy": {
            "type": "string",
            "enum": [
              "screening_standard"
            ]
          },
          "aggregation_policy_version": {
            "type": "integer"
          },
          "audience": {
            "type": "string",
            "enum": [
              "caller",
              "end_user"
            ],
            "description": "Describes THIS action. `caller` means your request needs correcting, not the person's details: show them nothing and retry with a fixed request and a new idempotency key."
          },
          "audience_note": {
            "type": "string"
          },
          "derived_from": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "reasons": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ScreeningReason"
            }
          },
          "per_check_actions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "input": {
                  "type": "string"
                },
                "type": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "does_not_resolve": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Reason codes this action does not settle. A completed contact challenge shows someone with access replied at that moment; it does not resolve a disposable-domain finding."
          },
          "not_established": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ScreeningActionUnavailable": {
        "type": "object",
        "required": [
          "reason",
          "policy",
          "policy_version",
          "checks",
          "detail"
        ],
        "description": "A policy was named and could not be evaluated for every check that produced a result. Distinct from a simply absent `recommended_action`, which means no policy was asked for: a broken evaluation and an uninterested caller must not look the same. The screening is NOT charged for when this is present.",
        "properties": {
          "reason": {
            "type": "string",
            "enum": [
              "policy_evaluation_failed"
            ]
          },
          "policy": {
            "type": "string"
          },
          "policy_version": {
            "type": [
              "integer",
              "null"
            ]
          },
          "checks": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "detail": {
            "type": "string"
          }
        }
      },
      "ScreeningQuote": {
        "type": "object",
        "required": [
          "maximum_credits",
          "pricing_policy",
          "pricing_version",
          "basis"
        ],
        "description": "Stated before the work ran, so the charge can be checked against it.",
        "properties": {
          "maximum_credits": {
            "type": "integer",
            "description": "Reserved in full up front, so a request is refused rather than started and found unaffordable."
          },
          "pricing_policy": {
            "type": "string",
            "enum": [
              "screening_capped_sum"
            ]
          },
          "pricing_version": {
            "type": "integer"
          },
          "basis": {
            "type": "string"
          }
        }
      },
      "ScreeningUsage": {
        "type": "object",
        "required": [
          "billed",
          "credits",
          "billing_unit",
          "standalone_equivalent",
          "billing_note",
          "balance",
          "idempotent_replay",
          "established_evidence",
          "no_evidence"
        ],
        "properties": {
          "billed": {
            "type": "boolean"
          },
          "credits": {
            "type": "integer",
            "description": "What THIS SCREENING cost, once. On a replay it is the original screening's charge repeated unchanged, not what the replayed request cost -- a replay costs nothing. Check `idempotent_replay` before reading it as a debit."
          },
          "billing_unit": {
            "type": "string",
            "enum": [
              "screening"
            ]
          },
          "standalone_equivalent": {
            "type": "integer",
            "description": "What the establishing checks would have cost as separate /v1/lookup calls, so the comparison is in the response rather than left for you to work out."
          },
          "billing_note": {
            "type": "string"
          },
          "balance": {
            "type": "integer"
          },
          "idempotent_replay": {
            "type": "boolean"
          },
          "established_evidence": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "no_evidence": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "DeepEmail": {
        "type": "object",
        "description": "Present only when `email_verification: \"deep\"` was accepted. Never inside `checks`. Every field below is reported so a simulated finding cannot be mistaken for evidence.",
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "simulated"
            ]
          },
          "simulated": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "provider": {
            "type": "string"
          },
          "disclaimer": {
            "type": "string"
          },
          "outcome": {
            "type": "string",
            "enum": [
              "deliverable",
              "undeliverable",
              "catch_all",
              "unknown",
              "timeout",
              "provider_error"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "completed",
              "attempted"
            ]
          },
          "establishes_evidence": {
            "type": "boolean",
            "enum": [
              false
            ],
            "description": "Always false. A fixture is not evidence and cannot make a screening billable."
          },
          "affects_recommended_action": {
            "type": "boolean",
            "enum": [
              false
            ],
            "description": "Always false. A fixture cannot tell you what to do about a real person."
          },
          "billable": {
            "type": "boolean",
            "enum": [
              false
            ]
          },
          "billing_note": {
            "type": "string"
          },
          "detail": {
            "type": "string"
          },
          "latency_ms": {
            "type": "integer"
          }
        }
      },
      "Screening": {
        "type": "object",
        "required": [
          "object",
          "api_version",
          "id",
          "created",
          "checks",
          "coverage",
          "cross_signal",
          "quote",
          "usage",
          "latency_ms"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "signup_screening"
            ]
          },
          "api_version": {
            "type": "string",
            "enum": [
              "1"
            ]
          },
          "id": {
            "type": "string",
            "example": "sgn_4f36d77ffc89c524"
          },
          "created": {
            "type": "string",
            "format": "date-time"
          },
          "checks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ScreeningCheck"
            }
          },
          "coverage": {
            "type": "object",
            "required": [
              "requested",
              "not_requested",
              "summary"
            ],
            "description": "`not_requested` carries no reason, no action, no penalty and no inference. An omitted identifier is a fact about your request, not about the person.",
            "properties": {
              "requested": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "not_requested": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "summary": {
                "type": "object",
                "additionalProperties": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "cross_signal": {
            "type": "object",
            "description": "Not implemented. No identifier is used to draw any conclusion about another; each result stands on its own evidence. This is not correlated fraud detection and must not be described as such.",
            "properties": {
              "implemented": {
                "type": "boolean",
                "enum": [
                  false
                ]
              },
              "findings": {
                "type": "array",
                "items": {}
              },
              "statement": {
                "type": "string"
              }
            }
          },
          "deep_email": {
            "$ref": "#/components/schemas/DeepEmail"
          },
          "recommended_action": {
            "$ref": "#/components/schemas/ScreeningAction"
          },
          "action_unavailable": {
            "$ref": "#/components/schemas/ScreeningActionUnavailable"
          },
          "quote": {
            "$ref": "#/components/schemas/ScreeningQuote"
          },
          "usage": {
            "$ref": "#/components/schemas/ScreeningUsage"
          },
          "latency_ms": {
            "type": "object",
            "properties": {
              "total": {
                "type": "integer"
              },
              "slowest_check": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      }
    }
  },
  "x-rate-limits": {
    "description": "Requests per minute, enforced per account and, for API traffic, per key. A retry counts toward the limit. Credit allowance is separate from requests per minute. No concurrency guarantee is published.",
    "perMinuteByPlan": {
      "free": 30,
      "starter": 120,
      "growth": 600,
      "professional": 1500
    }
  }
}
