> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gethuntd.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Submit Email Check

> Check whether one email address is a user of up to 20 products

<Note>
  **Availability:** Agency plan, on `https://app.gethuntd.com`. Other plans receive
  `403 ENDPOINT_NOT_ON_PLAN`. Counts against your monthly checks per source.
  [Contact Huntd](mailto:support@gethuntd.com) to enable it.
</Note>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST "https://app.gethuntd.com/api/v1/public/verify" \
    -H "Content-Type: application/json" \
    -H "X-API-Key: hntd_abc12345_yoursecretkey" \
    -d '{"email": "dana@acme.com", "sources": ["gong", "extend"]}'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://app.gethuntd.com/api/v1/public/verify', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': process.env.HUNTD_API_KEY
    },
    body: JSON.stringify({ email: 'dana@acme.com', sources: ['gong', 'extend'] })
  });

  const job = await response.json();
  console.log('Job ID:', job.job_id, 'poll after:', job.poll_after);
  ```

  ```python Python theme={null}
  import requests
  import os

  response = requests.post(
      'https://app.gethuntd.com/api/v1/public/verify',
      headers={
          'Content-Type': 'application/json',
          'X-API-Key': os.environ['HUNTD_API_KEY']
      },
      json={'email': 'dana@acme.com', 'sources': ['gong', 'extend']}
  )

  job = response.json()
  print(f"Job ID: {job['job_id']}, poll after: {job['poll_after']}")
  ```
</RequestExample>

<ResponseExample>
  ```json 202 theme={null}
  {
    "success": true,
    "job_id": "8f4c2a90-6c1e-4b58-9a2f-1d3e5b7c9011",
    "email": "dana@acme.com",
    "sources": ["gong", "extend"],
    "status": "queued",
    "poll_after": "2026-09-15T09:00:00.000Z"
  }
  ```

  ```json 202 (cached) theme={null}
  {
    "success": true,
    "job_id": "8f4c2a90-6c1e-4b58-9a2f-1d3e5b7c9011",
    "email": "dana@acme.com",
    "sources": ["gong", "extend"],
    "status": "completed",
    "poll_after": null
  }
  ```

  ```json 400 theme={null}
  {
    "success": false,
    "error": {
      "code": "INVALID_PARAMETER",
      "message": "`email` is not a valid address."
    }
  }
  ```

  ```json 401 theme={null}
  {
    "success": false,
    "error": {
      "code": "INVALID_API_KEY",
      "message": "API key is missing, invalid, or has been revoked."
    }
  }
  ```

  ```json 403 theme={null}
  {
    "success": false,
    "error": {
      "code": "SOURCE_NOT_ALLOWED",
      "message": "Your organization does not have access to source `gong`."
    }
  }
  ```

  ```json 429 theme={null}
  {
    "success": false,
    "error": {
      "code": "QUOTA_EXCEEDED",
      "message": "Monthly cap reached for source `gong` (10000 of 10000 checks this month)."
    }
  }
  ```
</ResponseExample>

## Overview

Check whether one email address is a user of up to 20 products. In this API a product is called a
**source**, named by a slug such as `gong` or `extend`.

This is an async operation:

1. Submit an email and a list of sources
2. Receive a `job_id` and a `poll_after` time immediately (202 Accepted)
3. Once `poll_after` has passed, poll [Get Email Check Result](/api-reference/email-check/get-result)
   until `status` is `completed`
4. Read `sources[slug].is_user`: `true`, `false`, or `null`

The response is always `202` with a job, even when every answer is already cached (then `status`
is `completed` and `poll_after` is `null`), so your client only needs one code path.

## Timing

* **Checks only run Monday to Friday, 09:00 to 18:00** in your account's timezone. A job submitted
  on Friday evening starts on Monday morning, and its `poll_after` says so.
* **A fresh check takes minutes**, not seconds. A cached answer is instant.
* `poll_after` can be days away. In production, save `job_id` and `poll_after` and check back from
  a scheduled job instead of keeping a process asleep.

## Which Sources You Can Check

A source slug is accepted only when all three are true. Each failure has its own error:

| Rule | If it fails |
| - | - |
| Your organization has access to the source | `403 SOURCE_NOT_ALLOWED` |
| The source can check individual people (company-level and demo-only sources can't) | `422 SOURCE_NOT_VERIFIABLE` |
| The source isn't paused for maintenance | `423 SOURCE_UNAVAILABLE` |

Access is checked first, so you never learn about sources you haven't bought. The sources you have
access to are listed in the Huntd dashboard.

## Limits

| Limit | Value | Error |
| - | - | - |
| Sources per request | 20 | `400 INVALID_PARAMETER` |
| Request body | 64 KB | `413 INVALID_BODY` |
| Requests per source | 5 per minute, per organization | `429 RATE_LIMITED` |
| Requests per API key | 60 per minute by default, polls included | `429 RATE_LIMIT_EXCEEDED` |
| Checks per source per month | 10,000 by default (20,000 and 30,000 tiers available) | `429 QUOTA_EXCEEDED` |

* The per-source limit is **all-or-nothing**: if any source in the request is over its limit, the
  whole request is refused and nothing is used up.
* Monthly checks reset at the start of each UTC calendar month and count cached answers too. Ask
  your account manager to change tier.
* `RATE_LIMITED` and `RATE_LIMIT_EXCEEDED` carry `Retry-After` (seconds); `QUOTA_EXCEEDED` doesn't,
  because it lasts until the next month.

## Errors

| HTTP | Error Code | Description |
| - | - | - |
| 400 | `INVALID_PARAMETER` | `email` missing or malformed, or `sources` empty, not an array, over 20 entries, or containing a non-string |
| 400 / 413 / 415 | `INVALID_BODY` | Body isn't JSON, is over 64 KB, or has an unsupported content type or charset |
| 401 | `INVALID_API_KEY` | API key is missing, invalid, or has been revoked |
| 403 | `ENDPOINT_NOT_ON_PLAN` | Your plan doesn't include email checks |
| 403 | `SOURCE_NOT_ALLOWED` | Your organization doesn't have access to one of the sources |
| 403 | `SUBSCRIPTION_INACTIVE` | Your workspace has no active subscription. Contact your account manager. |
| 422 | `SOURCE_NOT_VERIFIABLE` | One of the sources can't check individual people. Remove it. |
| 423 | `SOURCE_UNAVAILABLE` | A source is paused for maintenance. Retry later. |
| 429 | `RATE_LIMITED` | Over 5 requests per minute for one of the sources. Wait `Retry-After` seconds. |
| 429 | `RATE_LIMIT_EXCEEDED` | Over your API key's per-minute limit. Wait `Retry-After` seconds. |
| 429 | `QUOTA_EXCEEDED` | Monthly checks used up for one of the sources |
| 500 | `INTERNAL_ERROR` | Something broke on our side. Safe to retry. |
| 503 | `ORG_KIND_UNAVAILABLE` | Your plan briefly couldn't be confirmed. Retry. |

A request that returns an error created no job and used no monthly checks, so it's always safe to
retry.


## OpenAPI

````yaml api-reference/public-api.json POST /api/v1/public/verify
openapi: 3.0.3
info:
  title: Huntd Public API
  version: 1.0.0
  description: >-
    Read what Huntd tracks for your organization (enterprise plan), or check one
    email address against the products you track (Agency plan).
  contact:
    name: Huntd Support
    email: support@gethuntd.com
servers:
  - url: https://app.gethuntd.com
    description: Public API
security:
  - ApiKeyAuth: []
tags:
  - name: Public API
  - name: Email Check
paths:
  /api/v1/public/verify:
    post:
      tags:
        - Email Check
      summary: Submit Email Check
      description: >-
        Check whether one email address is a user of up to 20 sources. Returns a
        job ID to poll for the answers. Agency plan only.
      operationId: submitEmailCheck
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailCheckRequest'
            example:
              email: dana@acme.com
              sources:
                - gong
                - extend
      responses:
        '202':
          description: Job accepted. Always 202, even when every answer came from cache.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmailCheckJobResponse'
              examples:
                queued:
                  summary: Fresh check queued
                  value:
                    success: true
                    job_id: 8f4c2a90-6c1e-4b58-9a2f-1d3e5b7c9011
                    email: dana@acme.com
                    sources:
                      - gong
                      - extend
                    status: queued
                    poll_after: '2026-09-15T09:00:00.000Z'
                cached:
                  summary: Every answer came from cache
                  value:
                    success: true
                    job_id: 8f4c2a90-6c1e-4b58-9a2f-1d3e5b7c9011
                    email: dana@acme.com
                    sources:
                      - gong
                      - extend
                    status: completed
                    poll_after: null
        '400':
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              examples:
                invalidEmail:
                  summary: Malformed email
                  value:
                    success: false
                    error:
                      code: INVALID_PARAMETER
                      message: '`email` is not a valid address.'
                tooManySources:
                  summary: More than 20 sources
                  value:
                    success: false
                    error:
                      code: INVALID_PARAMETER
                      message: '`sources` may name at most 20 sources.'
                invalidBody:
                  summary: Body is not JSON
                  value:
                    success: false
                    error:
                      code: INVALID_BODY
                      message: Request body must be JSON and within the size limit.
        '401':
          description: Invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                success: false
                error:
                  code: INVALID_API_KEY
                  message: API key is missing, invalid, or has been revoked.
        '403':
          description: >-
            Not on the Agency plan, or no access to a source, or no active
            subscription
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              examples:
                notOnPlan:
                  summary: Plan does not include email checks
                  value:
                    success: false
                    error:
                      code: ENDPOINT_NOT_ON_PLAN
                      message: This endpoint is not available on your plan.
                sourceNotAllowed:
                  summary: No access to a source
                  value:
                    success: false
                    error:
                      code: SOURCE_NOT_ALLOWED
                      message: Your organization does not have access to source `gong`.
                subscriptionInactive:
                  summary: Workspace has no active subscription
                  value:
                    success: false
                    error:
                      code: SUBSCRIPTION_INACTIVE
                      message: >-
                        This workspace doesn't have an active subscription.
                        Please contact your account manager to restore access.
        '413':
          description: Body over 64 KB
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                success: false
                error:
                  code: INVALID_BODY
                  message: Request body must be JSON and within the size limit.
        '415':
          description: Unsupported content type, charset or encoding
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                success: false
                error:
                  code: INVALID_BODY
                  message: >-
                    Request body uses an unsupported content type, charset or
                    encoding.
        '422':
          description: Source cannot check individual people
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                success: false
                error:
                  code: SOURCE_NOT_VERIFIABLE
                  message: >-
                    Source `source_name` cannot be verified for an email
                    address.
        '423':
          description: Source paused for maintenance
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                success: false
                error:
                  code: SOURCE_UNAVAILABLE
                  message: >-
                    Source `source_name` is temporarily unavailable for
                    maintenance.
        '429':
          description: Rate limit or monthly cap reached
          headers:
            Retry-After:
              description: >-
                Seconds to wait. Sent with RATE_LIMITED and RATE_LIMIT_EXCEEDED,
                not with QUOTA_EXCEEDED.
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              examples:
                perSource:
                  summary: Over 5 requests per minute for a source
                  value:
                    success: false
                    error:
                      code: RATE_LIMITED
                      message: >-
                        Rate limit: at most 5 verify requests per minute for
                        source `gong`. Retry in 12s.
                perKey:
                  summary: Over the API key's per-minute limit
                  value:
                    success: false
                    error:
                      code: RATE_LIMIT_EXCEEDED
                      message: >-
                        Rate limit exceeded — 60 requests per minute. Retry in
                        23s.
                monthlyCap:
                  summary: Monthly cap reached for a source
                  value:
                    success: false
                    error:
                      code: QUOTA_EXCEEDED
                      message: >-
                        Monthly cap reached for source `gong` (10000 of 10000
                        checks this month).
        '500':
          description: Internal error. Safe to retry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                success: false
                error:
                  code: INTERNAL_ERROR
                  message: Internal server error.
        '503':
          description: Plan briefly could not be confirmed. Retry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
              example:
                success: false
                error:
                  code: ORG_KIND_UNAVAILABLE
                  message: >-
                    Your organization's plan could not be checked. Please retry
                    in a moment.
components:
  schemas:
    EmailCheckRequest:
      type: object
      required:
        - email
        - sources
      properties:
        email:
          type: string
          format: email
          maxLength: 320
          description: The one address to check. Lowercased and trimmed for you.
          example: dana@acme.com
        sources:
          type: array
          minItems: 1
          maxItems: 20
          items:
            type: string
          description: >-
            Source slugs to check the address against (1 to 20). A source is a
            product such as `gong` or `extend`. Duplicates are ignored.
          example:
            - gong
            - extend
    EmailCheckJobResponse:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - true
        job_id:
          type: string
          format: uuid
          description: 'Pass to Get Email Check Result. Opaque: don''t parse it.'
          example: 8f4c2a90-6c1e-4b58-9a2f-1d3e5b7c9011
        email:
          type: string
          description: The address as it will be checked, after lowercasing and trimming.
          example: dana@acme.com
        sources:
          type: array
          items:
            type: string
          description: The sources this job covers, duplicates removed.
          example:
            - gong
            - extend
        status:
          type: string
          enum:
            - queued
            - completed
          description: >-
            `queued` if at least one source needs a fresh check; `completed` if
            every answer came from cache.
        poll_after:
          type: string
          format: date-time
          nullable: true
          description: >-
            Earliest moment work can start (UTC). Don't poll before it. `null`
            when every answer came from cache.
    ApiError:
      type: object
      description: Every error uses this envelope. Branch on `error.code`, not the message.
      properties:
        success:
          type: boolean
          enum:
            - false
        error:
          type: object
          properties:
            code:
              type: string
              enum:
                - INVALID_PARAMETER
                - INVALID_BODY
                - INVALID_API_KEY
                - ENDPOINT_NOT_ON_PLAN
                - SOURCE_NOT_ALLOWED
                - SUBSCRIPTION_INACTIVE
                - JOB_NOT_FOUND
                - SOURCE_NOT_VERIFIABLE
                - SOURCE_UNAVAILABLE
                - RATE_LIMITED
                - RATE_LIMIT_EXCEEDED
                - QUOTA_EXCEEDED
                - STREAM_FAILED
                - INTERNAL_ERROR
                - ORG_KIND_UNAVAILABLE
            message:
              type: string
              description: Human-readable. May change.
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: >-
        API key in format: hntd_{id}_{secret}. Create one in Settings → API &
        webhooks. `Authorization: Bearer` is also accepted.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.