openapi: 3.1.0
info:
  title: Potarix Enricher API
  description: >-
    Company-data enrichment API. Resolve a company name to its website, find a
    person's verified work email, recover decision-maker emails from a domain,
    turn a LinkedIn URL into an email, scrape a company-wide email roster, or run
    the full enricher in one call. Each successful lookup spends credits from the
    caller's balance. New accounts get 25 free trial credits.


    Auth: every billable and account endpoint takes a bearer token in the
    Authorization header. The token is a Potarix API key that starts with
    "ptk_live_". Agents can self-serve a key with no browser via
    POST /auth/signup (new account) or POST /auth/api-keys (existing account),
    both of which take email + password and return the key once.


    Credit costs per call: find-website 2, find-email/person 25,
    find-email/decision-maker 25, find-email/linkedin 10, find-email/company 25
    (flat per call). find-all is the sum of the sub-calls it runs. Whiffed
    lookups (no result) are not charged. A repeat lookup of the same input by the
    same user is served from cache for free.


    Versioning and deprecation: this is API v1, served at the stable base URL
    above. Backwards-incompatible changes ship under a new major version (for
    example a /v2 base path) while the current version stays available. Clients
    may pin a version with the optional Api-Version request header. Deprecations
    are announced via the Deprecation and Sunset response headers with at least
    90 days notice.


    Rate limits: every response carries RateLimit-Limit, RateLimit-Remaining,
    and RateLimit-Reset headers so agents can self-pace and back off politely.


    Idempotency: enrichment lookups are idempotent and cached per caller, and
    accept an optional Idempotency-Key header so automated clients can safely
    retry without double-charging credits.
  version: "1.0.0"
  contact:
    name: Potarix
    url: https://enricher.potarix.com
servers:
  - url: https://api.potarix.com/enricher
    description: Production
tags:
  - name: enrichment
    description: The billable data-lookup endpoints.
  - name: account
    description: Profile, credit balance, and API-key self-service.
  - name: billing
    description: Credit purchase and off-session top-up.
security:
  - ApiKeyAuth: []
  - OAuth2ClientCredentials:
      - enricher:all
paths:
  /find-website:
    post:
      operationId: findWebsite
      summary: Resolve a company name to its official website
      description: >-
        Given a company name, return the best-guess official website URL with a
        source label and confidence score. Pass an optional context hint (e.g.
        "company that just raised funding") to disambiguate brands whose namesake
        dominates search results. Costs 2 credits. Returns 200 even when no
        website is found (website_url will be null). A repeat lookup of the same
        name by the same caller is free (cached).
      tags:
        - enrichment
      security:
        - ApiKeyAuth: []
        - OAuth2ClientCredentials:
            - enricher:read
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
        - $ref: "#/components/parameters/ApiVersion"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FindWebsiteRequest"
            example:
              company_name: Tycho
              context: company that raised funding
      responses:
        "200":
          description: Lookup completed (website_url may be null if none found).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FindWebsiteResponse"
              example:
                company_name: Tycho
                website_url: https://tycho.ai
                source: website_guesser
                confidence: 0.92
                cached: false
                credits_remaining: 998
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/InsufficientCredits"
        "500":
          $ref: "#/components/responses/ServerError"
  /find-email/person:
    post:
      operationId: findEmailPerson
      summary: Find a person's verified work email
      description: >-
        Find and verify a single person's work email by running a provider
        waterfall (Findymail, AnyMailFinder, Hunter, BetterContact). Supply
        either first_name + last_name or full_name, plus either a domain or a
        company_name. Costs 25 credits on a hit. Returns 404 if no valid email is
        found across all providers (no charge). Repeat lookups of the same input
        by the same caller are free (cached).
      tags:
        - enrichment
      security:
        - ApiKeyAuth: []
        - OAuth2ClientCredentials:
            - enricher:read
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
        - $ref: "#/components/parameters/ApiVersion"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FindEmailPersonRequest"
            example:
              first_name: Jane
              last_name: Doe
              domain: acme.com
      responses:
        "200":
          description: Verified email found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FindEmailPersonResponse"
              example:
                email: jane@acme.com
                email_status: valid
                source: findymail
                cached: false
                credits_remaining: 973
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/InsufficientCredits"
        "404":
          $ref: "#/components/responses/EmailNotFound"
  /find-email/decision-maker:
    post:
      operationId: findEmailDecisionMaker
      summary: Find a decision-maker email by role at a domain
      description: >-
        Given a company domain and a decision-maker role category (e.g. ceo,
        sales, operations, buyer, finance, hr, marketing), recover the matching
        decision-maker's name, title, LinkedIn URL, and verified email. Costs 25
        credits on a hit. Returns 404 if no match is found (no charge). Repeat
        lookups of the same input by the same caller are free (cached).
      tags:
        - enrichment
      security:
        - ApiKeyAuth: []
        - OAuth2ClientCredentials:
            - enricher:read
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
        - $ref: "#/components/parameters/ApiVersion"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FindEmailDecisionMakerRequest"
            example:
              decision_maker_category: ceo
              domain: acme.com
      responses:
        "200":
          description: Decision-maker found with a verified email.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FindEmailDecisionMakerResponse"
              example:
                name: Jane Doe
                email: jane@acme.com
                linkedin_url: https://www.linkedin.com/in/janedoe
                job_title: Chief Executive Officer
                email_status: valid
                source: anymailfinder
                cached: false
                credits_remaining: 948
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/InsufficientCredits"
        "404":
          $ref: "#/components/responses/EmailNotFound"
  /find-email/linkedin:
    post:
      operationId: findEmailLinkedin
      summary: Turn a LinkedIn profile URL into a verified email
      description: >-
        Given a LinkedIn profile URL, return the person's verified work email
        plus name, title, and the LinkedIn URL echoed back. Costs 10 credits on a
        hit. Returns 404 if no valid email is found (no charge). Repeat lookups of
        the same URL by the same caller are free (cached).
      tags:
        - enrichment
      security:
        - ApiKeyAuth: []
        - OAuth2ClientCredentials:
            - enricher:read
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
        - $ref: "#/components/parameters/ApiVersion"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FindEmailLinkedinRequest"
            example:
              linkedin_url: https://www.linkedin.com/in/janedoe
      responses:
        "200":
          description: Verified email found for the profile.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FindEmailLinkedinResponse"
              example:
                name: Jane Doe
                email: jane@acme.com
                linkedin_url: https://www.linkedin.com/in/janedoe
                job_title: Chief Executive Officer
                email_status: valid
                source: findymail
                cached: false
                credits_remaining: 938
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/InsufficientCredits"
        "404":
          $ref: "#/components/responses/EmailNotFound"
  /find-email/company:
    post:
      operationId: findEmailCompany
      summary: Scrape a company-wide email roster from a domain
      description: >-
        Given a company domain, return a roster of public/work emails with names,
        titles, and LinkedIn URLs where available (capped at 500). Flat 25 credits
        per call regardless of how many emails come back. Returns 404 if no emails
        are found (no charge). Repeat lookups of the same domain by the same caller
        are free (cached).
      tags:
        - enrichment
      security:
        - ApiKeyAuth: []
        - OAuth2ClientCredentials:
            - enricher:read
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
        - $ref: "#/components/parameters/ApiVersion"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FindEmailCompanyRequest"
            example:
              domain: acme.com
      responses:
        "200":
          description: Email roster returned (may be empty only when cached).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FindEmailCompanyResponse"
              example:
                emails:
                  - email: jane@acme.com
                    name: Jane Doe
                    title: CEO
                    linkedin_url: https://www.linkedin.com/in/janedoe
                  - email: john@acme.com
                    name: John Smith
                    title: VP Sales
                    linkedin_url: null
                count: 2
                source: hunter
                cached: false
                credits_remaining: 913
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/InsufficientCredits"
        "404":
          $ref: "#/components/responses/EmailNotFound"
  /find-all:
    post:
      operationId: findAll
      summary: Run the full enricher in one call (website plus emails plus decision-makers)
      description: >-
        Kitchen-sink endpoint. Resolves the company website, then fans out in
        parallel to scrape company-wide emails and find N decision-makers by role
        (defaults to ceo, sales, operations; up to 6). Cost is the natural sum of
        the sub-calls that succeed; whiffed sub-calls cost zero. Returns 200 with
        whatever succeeded and a non-fatal errors array. credits_charged reports
        the net credits taken for this specific call. Set skip_company_emails to
        true to skip the company-wide email scrape and save credits.
      tags:
        - enrichment
      security:
        - ApiKeyAuth: []
        - OAuth2ClientCredentials:
            - enricher:read
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
        - $ref: "#/components/parameters/ApiVersion"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FindAllRequest"
            example:
              company_name: Acme Inc
              context: company that raised funding
              dm_categories:
                - ceo
                - sales
              skip_company_emails: false
      responses:
        "200":
          description: Enrichment completed; partial results possible (see errors).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FindAllResponse"
              example:
                company_name: Acme Inc
                domain: acme.com
                website:
                  url: https://acme.com
                  source: website_guesser
                  confidence: 0.9
                  cached: false
                decision_makers:
                  - category: ceo
                    name: Jane Doe
                    email: jane@acme.com
                    linkedin_url: https://www.linkedin.com/in/janedoe
                    job_title: CEO
                    email_status: valid
                    source: anymailfinder
                    cached: false
                    error: null
                company_emails:
                  - email: hello@acme.com
                    name: null
                    title: null
                    linkedin_url: null
                company_emails_count: 1
                company_emails_source: hunter
                company_emails_cached: false
                total_emails: 2
                credits_charged: 52
                credits_remaining: 861
                errors: []
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/InsufficientCredits"
        "500":
          $ref: "#/components/responses/ServerError"
  /auth/signup:
    post:
      operationId: authSignup
      summary: Create an account and mint an API key (headless, no browser)
      description: >-
        Create a new Potarix Enricher account from email + password and return a
        fresh API key in one round trip. The key is shown ONCE, store it
        immediately. Grants 25 free trial credits. Returns 409 if an account
        already exists for that email; use POST /auth/api-keys to mint a key for
        an existing account instead. No bearer token required.
      tags:
        - account
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/HeadlessAuthRequest"
            example:
              email: founder@example.com
              password: a-strong-password
              label: chatgpt-app
      responses:
        "200":
          description: Account created; API key returned once.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HeadlessAuthResponse"
              example:
                user_id: user_2abcXYZ
                email: founder@example.com
                api_key: ptk_live_4c2a72c9d3e1f0a8b6c5d4e3f2a1b0c9
                key_prefix: ptk_live_4c2a72c9
                credits_remaining: 25
                created_at: "2026-05-24T12:00:00Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "409":
          description: An account already exists for that email.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorDetail"
              example:
                detail:
                  error: user_exists
                  message: An account already exists for that email. Use POST /auth/api-keys to mint a key.
  /auth/api-keys:
    post:
      operationId: authMintApiKey
      summary: Verify email plus password and mint a fresh API key
      description: >-
        For an existing account: verify email + password and mint a new API key.
        The key is shown ONCE, store it immediately. Returns 404 if no account
        exists for the email and 401 if the password is wrong. No bearer token
        required (this is the credential-based login path).
      tags:
        - account
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/HeadlessAuthRequest"
            example:
              email: founder@example.com
              password: a-strong-password
              label: chatgpt-app
      responses:
        "200":
          description: Credentials verified; API key returned once.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HeadlessAuthResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          description: Invalid email or password.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorDetail"
        "404":
          description: No account found for that email.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorDetail"
  /me:
    get:
      operationId: getMe
      summary: Get profile, credit balance, and payment status
      description: >-
        Return the caller's user_id, email, credit balance, total credits ever
        purchased, whether a card is saved (has_saved_card), and active API-key
        count. This is the status check an agent should call first to decide
        whether it can run lookups or needs to top up. Accepts an API key bearer
        token.
      tags:
        - account
      security:
        - ApiKeyAuth: []
        - OAuth2ClientCredentials:
            - enricher:read
      responses:
        "200":
          description: Profile and balance.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MeResponse"
              example:
                user_id: user_2abcXYZ
                email: founder@example.com
                credits_remaining: 861
                total_purchased: 1000
                has_saved_card: true
                api_key_count: 2
        "401":
          $ref: "#/components/responses/Unauthorized"
  /credits:
    get:
      operationId: getCredits
      summary: Get the caller's credit balance
      description: >-
        Return the caller's current credit balance and total credits ever
        purchased. Lighter than /me (no email, card, or key-count lookup).
        Accepts an API key bearer token.
      tags:
        - account
      security:
        - ApiKeyAuth: []
        - OAuth2ClientCredentials:
            - enricher:read
      responses:
        "200":
          description: Credit balance.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreditsResponse"
              example:
                user_id: user_2abcXYZ
                credits_remaining: 861
                total_purchased: 1000
        "401":
          $ref: "#/components/responses/Unauthorized"
  /billing/products:
    get:
      operationId: listBillingProducts
      summary: List the credit purchase tiers
      description: >-
        Return the available credit packs (key, name, credit count, USD price,
        and the Stripe price id for the active mode). No auth required.
      tags:
        - billing
      security: []
      responses:
        "200":
          description: Available credit tiers.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Tier"
              example:
                - key: 1k
                  name: 1,000 credits
                  credits: 1000
                  amount_usd: 10
                  price_id: price_abc123
                - key: 5k
                  name: 5,000 credits
                  credits: 5000
                  amount_usd: 50
                  price_id: price_def456
                - key: 25k
                  name: 25,000 credits
                  credits: 25000
                  amount_usd: 250
                  price_id: price_ghi789
  /billing/checkout-session:
    post:
      operationId: createCheckoutSession
      summary: Create a Stripe Checkout URL to buy credits
      description: >-
        Create a hosted Stripe Checkout URL for the chosen tier. A human must open
        the returned URL once in a browser to enter card details; the card is
        saved for future off-session top-ups. After this one browser trip, an
        agent can buy more credits silently via POST /billing/topup. Accepts an
        API key bearer token. Credits are granted by the Stripe webhook after
        payment, not synchronously here.
      tags:
        - billing
      security:
        - ApiKeyAuth: []
        - OAuth2ClientCredentials:
            - enricher:write
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TierKeyRequest"
            example:
              tier_key: 5k
      responses:
        "200":
          description: Checkout session created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CheckoutSessionResponse"
              example:
                url: https://checkout.stripe.com/c/pay/cs_live_a1b2c3
                session_id: cs_live_a1b2c3
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "502":
          $ref: "#/components/responses/UpstreamError"
        "503":
          $ref: "#/components/responses/Unconfigured"
  /billing/checkout:
    post:
      operationId: createCheckout
      summary: Create a Stripe Checkout URL (short alias)
      description: >-
        Friendlier alias of /billing/checkout-session with identical behavior and
        response. Accepts an API key bearer token.
      tags:
        - billing
      security:
        - ApiKeyAuth: []
        - OAuth2ClientCredentials:
            - enricher:write
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TierKeyRequest"
            example:
              tier_key: 5k
      responses:
        "200":
          description: Checkout session created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CheckoutSessionResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "502":
          $ref: "#/components/responses/UpstreamError"
        "503":
          $ref: "#/components/responses/Unconfigured"
  /billing/topup:
    post:
      operationId: topupCredits
      summary: Buy credits off-session using the saved card
      description: >-
        Charge the caller's saved card off-session and add the tier's credits
        immediately. Requires that the user has completed checkout at least once
        (so a card is on file). Returns status "succeeded" with the new balance on
        success. Returns status "requires_action" with a next_action_url when the
        card needs SCA/3DS; hand that URL to a human to confirm. Returns 402 with
        error "no_saved_card" if no card is on file yet (do a checkout first).
        Accepts an API key bearer token.
      tags:
        - billing
      security:
        - ApiKeyAuth: []
        - OAuth2ClientCredentials:
            - enricher:write
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TierKeyRequest"
            example:
              tier_key: 5k
      responses:
        "200":
          description: Top-up result (succeeded or requires_action).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TopupResponse"
              example:
                status: succeeded
                credits_added: 5000
                credits_remaining: 5861
                payment_intent_id: pi_live_a1b2c3
                next_action_url: null
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          description: No saved card on file, or the payment did not complete.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorDetail"
              example:
                detail:
                  error: no_saved_card
                  message: No saved card on file. POST /billing/checkout once to add one (browser trip), then top-ups are silent.
        "502":
          $ref: "#/components/responses/UpstreamError"
components:
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      description: >-
        Potarix Enricher API key. Send it as: Authorization: Bearer
        ptk_live_xxx. Mint one with POST /auth/signup or POST /auth/api-keys.
    OAuth2ClientCredentials:
      type: oauth2
      description: >-
        OAuth 2.0 client-credentials flow (RFC 6749 section 4.4) for agents that
        prefer short-lived bearer tokens. Use your ptk_live_ API key as the
        client_secret (client_id is the key prefix). The minted access token
        resolves to the same account and credit balance as the API key.
        Discovery: /.well-known/oauth-authorization-server (RFC 8414) and
        /.well-known/oauth-protected-resource (RFC 9728).
      flows:
        clientCredentials:
          tokenUrl: https://api.potarix.com/enricher/oauth/token
          scopes:
            enricher:all: Full access to every enrichment, account, and billing endpoint.
            enricher:read: Read-only enrichment and account lookups.
            enricher:write: Billable mutations such as billing top-ups and checkout.
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
        format: uuid
      description: >-
        Optional client-supplied idempotency key (e.g. a UUID). A retried request
        with the same key and body returns the original result instead of
        repeating billable work. Enrichment lookups are additionally cached per
        caller, so repeats are free regardless.
    ApiVersion:
      name: Api-Version
      in: header
      required: false
      schema:
        type: string
        example: "2026-05-01"
      description: >-
        Optional API version pin. Omit to use the current stable version (v1).
        Breaking changes ship under a new version; deprecations are announced via
        Deprecation and Sunset response headers with at least 90 days notice.
  responses:
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorDetail"
          example:
            detail: Invalid API key
    BadRequest:
      description: Invalid or missing request parameters.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorDetail"
          example:
            detail: domain is required.
    InsufficientCredits:
      description: Not enough credits to perform this lookup. Top up via /billing/topup or /billing/checkout.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/InsufficientCreditsError"
          example:
            detail:
              error: insufficient_credits
              message: Need 25 credits, have 3. Please purchase more.
              credits_remaining: 3
    EmailNotFound:
      description: No valid email found across all providers (not charged).
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorDetail"
          example:
            detail: No valid email found.
    ServerError:
      description: Internal error during the lookup.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorDetail"
          example:
            detail: Website lookup failed.
    UpstreamError:
      description: Payment provider (Stripe) error.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorDetail"
          example:
            detail: "Stripe error: your card was declined."
    Unconfigured:
      description: Requested tier is not configured for the active billing mode.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorDetail"
          example:
            detail: Tier 5k not configured for live mode.
  schemas:
    FindWebsiteRequest:
      type: object
      required:
        - company_name
      properties:
        company_name:
          type: string
          description: The company name to resolve to a website.
          examples:
            - Tycho
        context:
          type:
            - string
            - "null"
          description: >-
            Optional hint about what kind of entity this is, used to disambiguate
            namesakes. Common values include "company that raised funding", "new
            YC startup", "recently funded company". When provided, the shared
            cache is bypassed for this lookup.
          examples:
            - company that raised funding
    FindWebsiteResponse:
      type: object
      required:
        - company_name
        - cached
      properties:
        company_name:
          type: string
        website_url:
          type:
            - string
            - "null"
          description: Resolved website URL, or null if none was found.
        source:
          type:
            - string
            - "null"
          description: Where the result came from (e.g. website_guesser, serper).
        confidence:
          type:
            - number
            - "null"
          description: Confidence score 0 to 1, when available.
        cached:
          type: boolean
          description: True if served from cache (free for this caller).
        credits_remaining:
          type:
            - integer
            - "null"
          description: Caller's credit balance after this call.
    FindEmailPersonRequest:
      type: object
      description: >-
        Supply enough to identify the person and their company. Provide either
        first_name plus last_name, or full_name; and either domain or
        company_name.
      properties:
        first_name:
          type:
            - string
            - "null"
        last_name:
          type:
            - string
            - "null"
        full_name:
          type:
            - string
            - "null"
        domain:
          type:
            - string
            - "null"
          description: Company domain (e.g. acme.com). Preferred over company_name.
        company_name:
          type:
            - string
            - "null"
          description: Company name, used when the domain is unknown.
    FindEmailPersonResponse:
      type: object
      required:
        - cached
      properties:
        email:
          type:
            - string
            - "null"
        email_status:
          type:
            - string
            - "null"
          description: Verification status (e.g. valid, risky, unknown).
        source:
          type:
            - string
            - "null"
          description: Provider that returned the email (findymail, anymailfinder, hunter, bettercontact).
        cached:
          type: boolean
        credits_remaining:
          type:
            - integer
            - "null"
    FindEmailDecisionMakerRequest:
      type: object
      required:
        - decision_maker_category
        - domain
      properties:
        decision_maker_category:
          type: string
          description: >-
            Role category to target. Common values: ceo, sales, operations,
            buyer, finance, hr, marketing.
          examples:
            - ceo
        domain:
          type: string
          description: Company domain (e.g. acme.com).
          examples:
            - acme.com
    FindEmailDecisionMakerResponse:
      type: object
      required:
        - cached
      properties:
        name:
          type:
            - string
            - "null"
        email:
          type:
            - string
            - "null"
        linkedin_url:
          type:
            - string
            - "null"
        job_title:
          type:
            - string
            - "null"
        email_status:
          type:
            - string
            - "null"
        source:
          type:
            - string
            - "null"
        cached:
          type: boolean
        credits_remaining:
          type:
            - integer
            - "null"
    FindEmailLinkedinRequest:
      type: object
      required:
        - linkedin_url
      properties:
        linkedin_url:
          type: string
          description: Full LinkedIn profile URL.
          examples:
            - https://www.linkedin.com/in/janedoe
    FindEmailLinkedinResponse:
      type: object
      required:
        - cached
      properties:
        name:
          type:
            - string
            - "null"
        email:
          type:
            - string
            - "null"
        linkedin_url:
          type:
            - string
            - "null"
        job_title:
          type:
            - string
            - "null"
        email_status:
          type:
            - string
            - "null"
        source:
          type:
            - string
            - "null"
        cached:
          type: boolean
        credits_remaining:
          type:
            - integer
            - "null"
    FindEmailCompanyRequest:
      type: object
      required:
        - domain
      properties:
        domain:
          type: string
          description: Company domain to scrape emails for (e.g. acme.com).
          examples:
            - acme.com
        limit:
          type:
            - integer
            - "null"
          minimum: 1
          maximum: 500
          description: Optional page size (1-500). Omit to return all results (max 500) in one page.
        cursor:
          type:
            - string
            - "null"
          description: Optional opaque pagination cursor from a previous response next_cursor.
    CompanyEmailItem:
      type: object
      required:
        - email
      properties:
        email:
          type: string
        name:
          type:
            - string
            - "null"
        title:
          type:
            - string
            - "null"
        linkedin_url:
          type:
            - string
            - "null"
    FindEmailCompanyResponse:
      type: object
      required:
        - emails
        - count
        - cached
      properties:
        emails:
          type: array
          items:
            $ref: "#/components/schemas/CompanyEmailItem"
        count:
          type: integer
          description: Number of emails returned in this page (capped at 500).
        has_more:
          type: boolean
          description: True when more results remain; pass next_cursor to fetch the next page.
        next_cursor:
          type:
            - string
            - "null"
          description: Opaque cursor for the next page, or null when the roster is complete.
        source:
          type:
            - string
            - "null"
        cached:
          type: boolean
        credits_remaining:
          type:
            - integer
            - "null"
    FindAllRequest:
      type: object
      required:
        - company_name
      properties:
        company_name:
          type: string
        context:
          type:
            - string
            - "null"
          description: Optional hint passed through to website resolution to disambiguate namesakes.
        dm_categories:
          type:
            - array
            - "null"
          items:
            type: string
          description: >-
            Decision-maker role categories to pull. Defaults to ceo, sales,
            operations. Maximum 6.
          examples:
            - - ceo
              - sales
        skip_company_emails:
          type: boolean
          default: false
          description: When true, skip the company-wide email scrape to save credits.
    FindAllWebsite:
      type: object
      required:
        - cached
      properties:
        url:
          type:
            - string
            - "null"
        source:
          type:
            - string
            - "null"
        confidence:
          type:
            - number
            - "null"
        cached:
          type: boolean
    FindAllDecisionMaker:
      type: object
      required:
        - category
        - cached
      properties:
        category:
          type: string
        name:
          type:
            - string
            - "null"
        email:
          type:
            - string
            - "null"
        linkedin_url:
          type:
            - string
            - "null"
        job_title:
          type:
            - string
            - "null"
        email_status:
          type:
            - string
            - "null"
        source:
          type:
            - string
            - "null"
        cached:
          type: boolean
        error:
          type:
            - string
            - "null"
          description: Set when this decision-maker lookup failed but the rest of find-all succeeded.
    FindAllResponse:
      type: object
      required:
        - company_name
        - website
        - credits_charged
        - credits_remaining
      properties:
        company_name:
          type: string
        domain:
          type:
            - string
            - "null"
        website:
          $ref: "#/components/schemas/FindAllWebsite"
        decision_makers:
          type: array
          items:
            $ref: "#/components/schemas/FindAllDecisionMaker"
        company_emails:
          type: array
          items:
            $ref: "#/components/schemas/CompanyEmailItem"
        company_emails_count:
          type: integer
        company_emails_source:
          type:
            - string
            - "null"
        company_emails_cached:
          type: boolean
        total_emails:
          type: integer
          description: Company emails plus decision-makers that returned an email.
        credits_charged:
          type: integer
          description: Net credits taken for this find-all call.
        credits_remaining:
          type: integer
        errors:
          type: array
          items:
            type: string
          description: Non-fatal failures of sub-lookups; the call still returns 200.
    HeadlessAuthRequest:
      type: object
      required:
        - email
        - password
      properties:
        email:
          type: string
          format: email
        password:
          type: string
          description: Minimum 8 characters on signup.
          minLength: 8
        label:
          type:
            - string
            - "null"
          description: Optional label for the minted API key (e.g. chatgpt-app).
    HeadlessAuthResponse:
      type: object
      required:
        - user_id
        - email
        - api_key
        - key_prefix
        - credits_remaining
        - created_at
      properties:
        user_id:
          type: string
        email:
          type: string
        api_key:
          type: string
          description: Full plaintext API key. Shown ONCE. Store it immediately.
        key_prefix:
          type: string
          description: First chars of the key, safe to display (e.g. ptk_live_4c2a72c9).
        credits_remaining:
          type: integer
        created_at:
          type: string
          format: date-time
    MeResponse:
      type: object
      required:
        - user_id
        - credits_remaining
        - total_purchased
        - has_saved_card
        - api_key_count
      properties:
        user_id:
          type: string
        email:
          type:
            - string
            - "null"
        credits_remaining:
          type: integer
        total_purchased:
          type: integer
        has_saved_card:
          type: boolean
          description: True if a card is on file (off-session top-ups available).
        api_key_count:
          type: integer
    CreditsResponse:
      type: object
      required:
        - user_id
        - credits_remaining
        - total_purchased
      properties:
        user_id:
          type: string
        credits_remaining:
          type: integer
        total_purchased:
          type: integer
    Tier:
      type: object
      required:
        - key
        - name
        - credits
        - amount_usd
      properties:
        key:
          type: string
          enum:
            - 1k
            - 5k
            - 25k
        name:
          type: string
        credits:
          type: integer
        amount_usd:
          type: integer
        price_id:
          type:
            - string
            - "null"
    TierKeyRequest:
      type: object
      required:
        - tier_key
      properties:
        tier_key:
          type: string
          enum:
            - 1k
            - 5k
            - 25k
          description: Which credit pack to buy.
    CheckoutSessionResponse:
      type: object
      required:
        - url
        - session_id
      properties:
        url:
          type: string
          description: Hosted Stripe Checkout URL. A human must open this to enter card details.
        session_id:
          type: string
    TopupResponse:
      type: object
      required:
        - status
        - credits_added
        - credits_remaining
        - payment_intent_id
      properties:
        status:
          type: string
          enum:
            - succeeded
            - requires_action
        credits_added:
          type: integer
        credits_remaining:
          type: integer
        payment_intent_id:
          type: string
        next_action_url:
          type:
            - string
            - "null"
          description: Set when status is requires_action; hand to a human to confirm SCA/3DS.
    ErrorDetail:
      type: object
      description: >-
        Standard error envelope. detail is either a human-readable string or a
        structured error object carrying a stable machine-readable error code.
      required:
        - detail
      properties:
        detail:
          description: Error message string, or a structured error object.
          anyOf:
            - type: string
            - $ref: "#/components/schemas/StructuredError"
    StructuredError:
      type: object
      description: Typed error object with a stable, machine-readable error code.
      required:
        - error
        - message
      properties:
        error:
          type: string
          description: Stable machine-readable error code agents can branch on.
          enum:
            - invalid_token
            - insufficient_credits
            - no_saved_card
            - user_exists
            - bad_request
            - email_not_found
            - not_found
            - upstream_error
            - unconfigured
            - server_error
        message:
          type: string
          description: Human-readable explanation with a suggested recovery step.
        credits_remaining:
          type:
            - integer
            - "null"
          description: Caller credit balance, included on credit-related errors.
    InsufficientCreditsError:
      type: object
      properties:
        detail:
          type: object
          required:
            - error
            - message
            - credits_remaining
          properties:
            error:
              type: string
              const: insufficient_credits
            message:
              type: string
            credits_remaining:
              type: integer
