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

# Create a bulk job from a CSV file

> Asynchronous bulk processing. Submit a CSV (multipart/form-data with a
`file` field containing an `email` column). Required scope: `bulk`.
Required plan: Scale+ (`FEATURE_NOT_AVAILABLE` otherwise). The
reservation consumes the quota window in which it is created.
Configuration-dependent limits: up to 1,000,000 rows and 50 MiB by
default (BULK_MAX_ROWS / BULK_MAX_FILE_BYTES). JSON `input_url` is not
supported in v1.




## OpenAPI

````yaml /docs/api/openapi.yaml post /v1/bulk/jobs
openapi: 3.1.0
info:
  title: Signup Risk API
  version: 1.8.0-direct012
  description: >
    # Signup Risk API


    > **Stop fake signups before they hit your database.**


    Real-time signup risk scoring for signup and lead-quality flows. Returns a

    deterministic 0–100 risk score with an `allow` / `review` / `block`

    recommendation, backed by disposable-email detection, free/business email

    classification, name/email alignment, IP reputation, DNS infrastructure

    reputation, role-address and domain-typo detection, synthetic-identity

    detection, industry-specific scoring profiles, asynchronous bulk file

    processing, and signed outbound webhook delivery.


    This is **not** a basic email-syntax API. It is a check-driven decision

    system purpose-built for signup trust and abuse prevention.


    ## Authentication


    The first-party API uses **Bearer API-key** authentication:


    ```

    Authorization: Bearer eisk_live_<public-id>_<secret>

    ```


    API keys are issued from the dashboard at `https://app.signuprisk.com`

    and have a reveal-once secret. Scopes (`analyze`, `bulk`, `webhooks`) permit

    a route; the account **plan** permits premium functionality. Scope and plan

    are independent — both must pass.


    Dashboard, billing, and playground routes use **Clerk session** Bearer JWTs

    (human dashboard sessions), never API keys. The public checker is anonymous.


    ## Quickstart


    ```bash

    curl https://api.signuprisk.com/v1/analyze-email \
      -H "Authorization: Bearer eisk_live_example_public_id_example_secret" \
      -H "Content-Type: application/json" \
      -d '{"email":"person@example.com"}'
    ```


    See the [Quickstart](https://docs.signuprisk.com/quickstart) for the full
    first-party guide.
  contact:
    name: Signup Risk API Support
    url: https://app.signuprisk.com/support
  license:
    name: Proprietary
    url: https://app.signuprisk.com/terms
servers:
  - url: https://api.signuprisk.com
    description: Production
  - url: http://localhost:8080
    description: Local development (not a production server)
security: []
tags:
  - name: Analysis
    description: Synchronous single and batch email risk analysis.
  - name: Bulk
    description: Asynchronous bulk file processing.
  - name: Customer Webhooks
    description: >
      Outbound (customer-facing) webhook configuration and delivery.


      Delivery wire format (each delivery is an HTTPS POST):
        - Algorithm: HMAC-SHA256 with the per-config reveal-once signing secret.
        - X-SignupRisk-Event: the event type (e.g. `signup.high_risk`).
        - X-SignupRisk-Delivery: the delivery id (STABLE across retries; receivers
          dedupe on it).
        - X-SignupRisk-Timestamp: unix seconds (receiver should reject deliveries
          older than ~5 minutes as replay protection).
        - X-SignupRisk-Signature: `t=<timestamp>,v1=<hex(hmac_sha256(secret, "<timestamp>.<raw_body_bytes>"))>`.
        - Content-Type: application/json. User-Agent: signup-risk-webhooks/1.0.
      The signature is computed over the literal `"<timestamp>.<raw_body>"`
      using

      the RAW request body bytes (do NOT re-serialize JSON before verifying).

      Receiver response semantics: 2xx = success (terminal); 3xx = followed up
      to

      10 hops (each re-SSRF-checked; exhausted = retryable); 4xx = terminal

      failure (not retried); 5xx/network/timeout = retried with exponential

      backoff (~1m, 5m, 25m, 2h, 6h) up to max_attempts (default 5), then
      `dead`.

      Direct-014B guarantee: at most one active sender owns a delivery claim.
  - name: API Keys
    description: Dashboard management of first-party API keys.
  - name: Dashboard
    description: Clerk-session-protected backend-for-frontend dashboard routes.
  - name: Admin Console
    description: |
      Internal operator surface for support (base `/v1/admin`); not a customer
      API. Every route requires an authenticated, active Clerk session (same
      scheme as dashboard routes) carrying the custom `admin_role` session-JWT
      claim (`admin` or `support`). Both roles may read; writes additionally
      require `admin`. A missing or insufficient role is `403
      ADMIN_ROLE_REQUIRED`. Routes are registered only when the server feature
      flag `ADMIN_CONSOLE_ENABLED=true` is set. PII-free by design: user views
      carry no email fields; lookup by email is hash-matched server-side and
      responses contain only resolved ids/status.
  - name: Usage
    description: Account usage, quota windows, and history.
  - name: Billing
    description: Stripe self-service billing (Checkout, Portal, state).
  - name: Playground
    description: Authenticated dashboard playground that consumes real quota.
  - name: Public Checker
    description: Anonymous, limited, separately rate-limited public checker.
  - name: Public Playground
    description: >-
      Anonymous pre-signup playground returning the full envelope under a strict
      per-IP daily limit.
  - name: Operations
    description: Health and liveness probes.
  - name: Provider Webhooks
    description: Inbound provider-owned callback routes (Clerk, Stripe).
externalDocs:
  description: DIRECT-012 package documentation and references
  url: >-
    https://github.com/saviodo/email-intelligence-api/blob/main/docs/features/DIRECT-012-public-api-documentation-openapi-migration.md
paths:
  /v1/bulk/jobs:
    post:
      tags:
        - Bulk
      summary: Create a bulk job from a CSV file
      description: |
        Asynchronous bulk processing. Submit a CSV (multipart/form-data with a
        `file` field containing an `email` column). Required scope: `bulk`.
        Required plan: Scale+ (`FEATURE_NOT_AVAILABLE` otherwise). The
        reservation consumes the quota window in which it is created.
        Configuration-dependent limits: up to 1,000,000 rows and 50 MiB by
        default (BULK_MAX_ROWS / BULK_MAX_FILE_BYTES). JSON `input_url` is not
        supported in v1.
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                file:
                  type: string
                  format: binary
      responses:
        '202':
          description: Job accepted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkJobCreateResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '413':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalError'
      security:
        - ApiKeyBearer: []
components:
  parameters:
    RequestIdHeader:
      in: header
      name: X-Request-ID
      required: false
      schema:
        type: string
        maxLength: 128
      description: |
        Optional client-supplied request id. The server always echoes an
        `X-Request-ID` response header; malformed/too-long values are replaced.
        Safe to include in support reports. Not an idempotency key.
  schemas:
    BulkJobCreateResponse:
      type: object
      required:
        - job
        - progress
        - results
        - metadata
      properties:
        job:
          type: object
          required:
            - id
            - status
            - created_at
            - expires_at
            - reused
          properties:
            id:
              type: string
            status:
              $ref: '#/components/schemas/BulkJobStatus'
            created_at:
              $ref: '#/components/schemas/RFC3339Timestamp'
            expires_at:
              $ref: '#/components/schemas/RFC3339Timestamp'
            reused:
              type: boolean
        progress:
          type: object
          required:
            - total_rows
            - processed_rows
            - successful_rows
            - failed_rows
            - percent
          properties:
            total_rows:
              type: integer
            processed_rows:
              type: integer
            successful_rows:
              type: integer
            failed_rows:
              type: integer
            percent:
              type: integer
        results:
          type: object
          required:
            - available
            - poll_url
          properties:
            available:
              type: boolean
            url:
              type:
                - string
                - 'null'
            poll_url:
              type: string
        metadata:
          type: object
          required:
            - warnings
          properties:
            warnings:
              type: array
              items:
                $ref: '#/components/schemas/Warning'
    BulkJobStatus:
      type: string
      enum:
        - pending
        - running
        - completed
        - failed
        - cancelled
        - quota_exceeded
    RFC3339Timestamp:
      type: string
      format: date-time
      description: RFC 3339 timestamp (UTC).
    Warning:
      type: object
      required:
        - code
        - message
        - type
        - upgrade_required
        - recommended_plan
      properties:
        code:
          type: string
        message:
          type: string
        type:
          type: string
        upgrade_required:
          type: boolean
        required_plan:
          type:
            - string
            - 'null'
        recommended_plan:
          type:
            - string
            - 'null'
    PublicErrorEnvelope:
      type: object
      required:
        - error
      properties:
        error:
          $ref: '#/components/schemas/PublicError'
    PublicError:
      type: object
      required:
        - code
        - message
        - type
        - request_id
        - retryable
      description: |
        The provider-neutral public error (DIRECT-011). The legacy
        `upgrade_url` field is intentionally absent; remediation is delivered
        via `action`. `ACCOUNT_DISABLED` is HTTP 403 (authorization), not 401.
      properties:
        code:
          $ref: '#/components/schemas/PublicErrorCode'
        message:
          type: string
        type:
          $ref: '#/components/schemas/PublicErrorType'
        request_id:
          type: string
        retryable:
          type: boolean
        details:
          type: object
          additionalProperties: true
          description: Optional safe, typed details (e.g. required_scope, required_plan).
        action:
          allOf:
            - $ref: '#/components/schemas/ErrorAction'
          description: Optional first-party remediation action.
        field_errors:
          type: array
          items:
            $ref: '#/components/schemas/FieldError'
        retry_after_seconds:
          type:
            - integer
            - 'null'
          description: Seconds to wait before retrying (quota/rate-limit/service).
        upgrade_required:
          type:
            - boolean
            - 'null'
          description: Legacy plan-upgrade flag (additive; prefer `action`).
        required_plan:
          type:
            - string
            - 'null'
          description: Legacy minimum plan for the gated feature.
        recommended_plan:
          type:
            - string
            - 'null'
          description: Legacy recommended plan for the gated feature.
    PublicErrorCode:
      type: string
      description: |
        Centrally registered public error code (DIRECT-011 registry plus the
        DIRECT-012 legacy surface codes). `UNAUTHORIZED` is intentionally not
        listed: it is unreachable and unpublished.
      enum:
        - MISSING_API_KEY
        - INVALID_API_KEY
        - REVOKED_API_KEY
        - EXPIRED_API_KEY
        - MALFORMED_API_KEY
        - MISSING_SESSION
        - INVALID_SESSION
        - EXPIRED_SESSION
        - INSUFFICIENT_SCOPE
        - FEATURE_NOT_AVAILABLE
        - PLAN_REQUIRED
        - ACCOUNT_DISABLED
        - USER_DISABLED
        - ACCOUNT_SYNC_PENDING
        - ADMIN_ROLE_REQUIRED
        - INVALID_REQUEST
        - INVALID_EMAIL
        - MALFORMED_EMAIL
        - INVALID_PAGINATION
        - INVALID_DATE_RANGE
        - INVALID_API_KEY_NAME
        - INVALID_API_KEY_SCOPES
        - INVALID_BILLING_REQUEST
        - DUPLICATE_REQUEST
        - METHOD_NOT_ALLOWED
        - LIMIT_EXCEEDED
        - PLAYGROUND_INVALID_REQUEST
        - CHECKER_INVALID_EMAIL
        - PUBLIC_PLAYGROUND_INVALID_REQUEST
        - MONTHLY_QUOTA_EXCEEDED
        - EXPLANATION_QUOTA_EXCEEDED
        - RATE_LIMIT_EXCEEDED
        - PUBLIC_CHECKER_RATE_LIMITED
        - EXPLANATION_LIMIT_EXCEEDED
        - PLAYGROUND_QUOTA_EXCEEDED
        - PLAYGROUND_RATE_LIMITED
        - CHECKER_RATE_LIMITED
        - PUBLIC_PLAYGROUND_RATE_LIMITED
        - PLAN_NOT_SELF_SERVICE
        - SUBSCRIPTION_EXISTS
        - CHECKOUT_PENDING
        - BILLING_NOT_CONFIGURED
        - BILLING_UNAVAILABLE
        - BILLING_GATEWAY_ERROR
        - API_KEY_NOT_FOUND
        - BULK_JOB_NOT_FOUND
        - WEBHOOK_NOT_FOUND
        - ACCOUNT_NOT_FOUND
        - NOT_FOUND
        - SERVICE_UNAVAILABLE
        - ANALYSIS_UNAVAILABLE
        - CHECKER_UNAVAILABLE
        - PLAYGROUND_UNAVAILABLE
        - USAGE_UNAVAILABLE
        - CHECKER_ANALYSIS_FAILED
        - PUBLIC_PLAYGROUND_UNAVAILABLE
        - PUBLIC_PLAYGROUND_ANALYSIS_FAILED
        - INTERNAL_ERROR
    PublicErrorType:
      type: string
      enum:
        - authentication_error
        - authorization_error
        - validation_error
        - quota_error
        - rate_limit_error
        - billing_error
        - conflict_error
        - not_found_error
        - service_error
        - internal_error
    ErrorAction:
      type: object
      required:
        - type
      properties:
        type:
          $ref: '#/components/schemas/ActionType'
        url:
          type:
            - string
            - 'null'
        label:
          type:
            - string
            - 'null'
        required_plan:
          type:
            - string
            - 'null'
    FieldError:
      type: object
      required:
        - field
        - code
        - message
      properties:
        field:
          type: string
        code:
          type: string
        message:
          type: string
    ActionType:
      type: string
      enum:
        - upgrade_plan
        - manage_billing
        - create_api_key
        - sign_in
        - contact_sales
        - retry_later
  responses:
    BadRequest:
      description: Validation error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PublicErrorEnvelope'
    Unauthorized:
      description: Authentication error (missing/invalid API key).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PublicErrorEnvelope'
    Forbidden:
      description: >-
        Authorization error (insufficient scope, feature gate, disabled, or
        missing/insufficient admin role — ADMIN_ROLE_REQUIRED).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PublicErrorEnvelope'
    TooManyRequests:
      description: Quota or short-window rate limit exceeded.
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds to wait before retrying.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PublicErrorEnvelope'
    InternalError:
      description: Internal error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PublicErrorEnvelope'
  securitySchemes:
    ApiKeyBearer:
      type: http
      scheme: bearer
      bearerFormat: eisk_live_<public-id>_<secret>
      description: |
        First-party API key. Send the canonical key as a Bearer token:
        ```
        Authorization: Bearer eisk_live_eik_<24hex>_<64hex>
        ```
        Used on all API-client routes (`/v1/analyze-email`, batch, bulk,
        customer webhooks). Scopes (`analyze`, `bulk`, `webhooks`) authorize a
        route; the account plan authorizes premium functionality.

````