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

# Submit Lead

> Submits a customer application. Leadhub validates the data, checks eligibility and distributes the lead to matching advertisers in real time.

### Required fields
- `product_id`: ID of the product the customer is applying for
- `publisher_id`: Your publisher ID
- `customer.personal_data`: first name and last name are required
- `customer.contact.email`: the customer's unique identifier in the system
- `product_data`: loan amount, purpose and term
- `consents`: customer must have accepted consents of type `terms` and `privacy_policy`

### Rejection reasons
| Reason | Description |
|--------|-------------|
| `customer_already_exists` | A customer with this email is already in the system |
| `duplicate_submission` | This customer was submitted too recently |
| `eligibility_failed` | Customer does not meet eligibility criteria |
| `age_restriction` | Customer's age is outside the accepted range |
| `income_insufficient` | Customer's income is below the minimum threshold |
| `geographic_restriction` | Customer's location is not supported |
| `consents_missing` | Required consents were not accepted |
| `invalid_product` | Product not found or not active |
| `blacklisted` | Customer is on the internal blacklist |
| `fraud_suspected` | Fraud signals detected in the submission |




## OpenAPI

````yaml POST /lead
openapi: 3.1.0
info:
  title: Leadhub API
  version: 2.0.0
  description: >
    Leadhub connects publishers (affiliates) with financial advertisers.
    Publishers submit customer leads and receive a simple accept/reject response
    with a redirect URL when the lead is accepted.


    ## Authentication


    All requests must include your API Key in the `apikey` header. Contact
    Leadhub to obtain your `publisher_id` and API Key.


    ## Publisher integration flow


    1. Publisher submits a lead via `POST /lead` with customer data

    2. Leadhub validates data, checks eligibility and distributes to matching
    advertisers

    3. Response indicates whether the lead was **new**, **distributed** or
    **rejected**

    4. If distributed, redirect the customer to the `redirect_url` to continue
    the process

    5. When the customer converts, Leadhub sends a notification to the
    publisher's configured callback URL


    ## Advertiser notifications


    When a lead converts, Leadhub notifies the publisher via a GET request to
    the publisher's callback URL:


    ```

    GET {publisher_callback_url}?lead_id={lead_id}&event={event}

    ```


    Configure your callback URL with your account manager.
servers:
  - url: https://{tenant}.techea.eu/api/v1
    description: Production
  - url: https://sandbox.techea.eu/api/v1
    description: Sandbox
security:
  - ApiKeyAuth: []
tags:
  - name: publisher
    description: Endpoints for publishers (affiliates)
  - name: advertiser
    description: Endpoints for advertisers (lenders)
paths:
  /lead:
    post:
      tags:
        - publisher
      summary: Submit lead
      description: >
        Submits a customer application. Leadhub validates the data, checks
        eligibility and distributes the lead to matching advertisers in real
        time.


        ### Required fields

        - `product_id`: ID of the product the customer is applying for

        - `publisher_id`: Your publisher ID

        - `customer.personal_data`: first name and last name are required

        - `customer.contact.email`: the customer's unique identifier in the
        system

        - `product_data`: loan amount, purpose and term

        - `consents`: customer must have accepted consents of type `terms` and
        `privacy_policy`


        ### Rejection reasons

        | Reason | Description |

        |--------|-------------|

        | `customer_already_exists` | A customer with this email is already in
        the system |

        | `duplicate_submission` | This customer was submitted too recently |

        | `eligibility_failed` | Customer does not meet eligibility criteria |

        | `age_restriction` | Customer's age is outside the accepted range |

        | `income_insufficient` | Customer's income is below the minimum
        threshold |

        | `geographic_restriction` | Customer's location is not supported |

        | `consents_missing` | Required consents were not accepted |

        | `invalid_product` | Product not found or not active |

        | `blacklisted` | Customer is on the internal blacklist |

        | `fraud_suspected` | Fraud signals detected in the submission |
      operationId: submitLead
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LeadRequest'
            example:
              product_id: 36068625-4707-4ef8-a419-2aad1483db49
              publisher_id: b96bdbc6-437a-4e2f-9b0e-2d91a5767f3b
              customer:
                external_id: pub-12345-abc
                personal_data:
                  first_name: John
                  last_name: Smith
                  second_surname: Johnson
                  birthday: '1988-05-10'
                  identification_number: 12345678A
                  nacionality: ES
                  gender: male
                contact:
                  email:
                    address: john.smith@email.com
                    status: verified
                    verified_at: '2026-02-01T10:00:00Z'
                    verification_method: otp
                  phone:
                    area_code: '+34'
                    number: '600111222'
                    status: verified
                    verified_at: '2026-02-01T10:00:00Z'
                    verification_method: otp
                address:
                  street: Calle Mayor
                  house: '1'
                  city: Madrid
                  region: Madrid
                  postal_code: '28001'
                  country: ES
                profile:
                  occupation: self_employed
                  monthly_income: 2500
                  monthly_expenses: 1500
                  marital_status: married
                  children: 1
                  education: university_degree
                  housing_tenure: rental
                  housing_type: apartment
                  company_sector: technology_telecommunications
                financial_instruments:
                  - type: bank_account
                    currency: EUR
                    iban: ES9121000418450200051332
                    status: not_verified
              product_data:
                loan_amount: 10000
                loan_purpose: home_improvement
                loan_term_months: 36
              consents:
                entries:
                  - type: terms
                    accepted: true
                    accepted_at: '2026-02-16T10:22:31Z'
                    version: v3.2
                    document_url: https://publisher.com/legal/terms/v3.2
                  - type: privacy_policy
                    accepted: true
                    accepted_at: '2026-02-16T10:22:31Z'
                    version: v4.1
                    document_url: https://publisher.com/legal/privacy/v4.1
                  - type: marketing
                    accepted: false
                    accepted_at: null
                    version: v2.0
                metadata:
                  ip_address: 84.23.12.55
                  user_agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
                  collected_at: '2026-02-16T10:22:31Z'
              tracking:
                click_id: abc123xyz
                sub_id: publisher_sub_1
                campaign_id: camp_789
                source: https://www.example.com
      responses:
        '200':
          description: >
            Lead received and processed. Check the `status` field:

            - `new`: lead accepted — no advertiser match yet, do not redirect

            - `distributed`: lead distributed to at least one advertiser —
            redirect customer to `redirect_url`

            - `rejected`: lead not accepted — check `rejection_reason`
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LeadResponse'
              examples:
                new:
                  summary: New — accepted, no advertiser match yet
                  value:
                    lead_id: a1b2c3d4-0000-0000-0000-111122223333
                    status: new
                distributed:
                  summary: Distributed — redirect customer
                  value:
                    lead_id: a1b2c3d4-0000-0000-0000-111122223333
                    status: distributed
                    redirect_url: https://advertiser.example.com/apply?token=xyz123
                rejected_existing:
                  summary: Rejected — customer already exists
                  value:
                    status: rejected
                    rejection_reason: customer_already_exists
                rejected_eligibility:
                  summary: Rejected — eligibility failed
                  value:
                    status: rejected
                    rejection_reason: eligibility_failed
        '400':
          description: Malformed request or invalid field values
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: API Key missing or invalid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: >-
            Semantically invalid data (consents not accepted, product not found,
            etc.)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  schemas:
    LeadRequest:
      type: object
      required:
        - product_id
        - publisher_id
        - customer
        - product_data
        - consents
      properties:
        product_id:
          type: string
          format: uuid
          description: ID of the product the customer is applying for. Provided by TECHEA.
        publisher_id:
          type: string
          format: uuid
          description: Your publisher ID in TECHEA.
        customer:
          $ref: '#/components/schemas/Customer'
        product_data:
          $ref: '#/components/schemas/LoanProductData'
        consents:
          $ref: '#/components/schemas/Consents'
        tracking:
          $ref: '#/components/schemas/Tracking'
    LeadResponse:
      type: object
      properties:
        lead_id:
          type:
            - string
            - 'null'
          format: uuid
          description: >
            ID generated by TECHEA. Used for advertiser event tracking.

            Null if the lead was rejected before being created (e.g.
            `customer_already_exists`).
        status:
          $ref: '#/components/schemas/LeadStatus'
        redirect_url:
          type:
            - string
            - 'null'
          format: uri
          description: |
            URL to redirect the customer to complete the process.
            Only present when `status = distributed`.
        rejection_reason:
          $ref: '#/components/schemas/RejectionReason'
    ErrorResponse:
      type: object
      properties:
        error:
          type: string
          description: Error code
        message:
          type: string
          description: Human-readable description
        details:
          type: array
          description: Field-level validation errors. Only present in `400` responses.
          items:
            type: object
            properties:
              field:
                type: string
              issue:
                type: string
      example:
        error: validation_error
        message: Request body validation failed
        details:
          - field: product_data.loan_amount
            issue: must be between 500 and 75000
    Customer:
      type: object
      required:
        - personal_data
        - contact
      properties:
        external_id:
          type: string
          description: Customer identifier in your system. Stored for cross-reference.
        personal_data:
          $ref: '#/components/schemas/PersonalData'
        contact:
          $ref: '#/components/schemas/Contact'
        address:
          $ref: '#/components/schemas/Address'
        profile:
          $ref: '#/components/schemas/Profile'
        financial_instruments:
          type: array
          items:
            $ref: '#/components/schemas/FinancialInstrument'
    LoanProductData:
      type: object
      required:
        - loan_amount
        - loan_purpose
        - loan_term_months
      properties:
        loan_amount:
          type: integer
          minimum: 500
          maximum: 75000
          description: Requested amount in euros
        loan_purpose:
          $ref: '#/components/schemas/LoanPurpose'
        loan_term_months:
          type: integer
          minimum: 3
          maximum: 84
          description: Repayment term in months
    Consents:
      type: object
      required:
        - entries
        - metadata
      properties:
        entries:
          type: array
          description: Must include `terms` and `privacy_policy` with `accepted = true`.
          items:
            $ref: '#/components/schemas/ConsentEntry'
        metadata:
          $ref: '#/components/schemas/ConsentMetadata'
    Tracking:
      type: object
      description: Optional campaign attribution data
      properties:
        click_id:
          type: string
        sub_id:
          type: string
        campaign_id:
          type: string
        source:
          type: string
          format: uri
          description: URL of the brand's website where the lead originated.
          example: https://www.example.com
    LeadStatus:
      type: string
      description: >
        Lead status from the publisher's perspective:

        - `new`: lead accepted — no advertiser match yet, do not redirect

        - `distributed`: lead sent to at least one advertiser — redirect
        customer to `redirect_url`

        - `rejected`: not accepted — check `rejection_reason`
      enum:
        - new
        - distributed
        - rejected
    RejectionReason:
      type:
        - string
        - 'null'
      description: Reason for rejection. Only present when `status = rejected`.
      enum:
        - customer_already_exists
        - duplicate_submission
        - eligibility_failed
        - age_restriction
        - income_insufficient
        - geographic_restriction
        - consents_missing
        - invalid_product
        - blacklisted
        - fraud_suspected
        - null
      x-enumDescriptions:
        customer_already_exists: A customer with this email is already registered for this product
        duplicate_submission: This customer was submitted too recently — try again later
        eligibility_failed: Customer does not meet the eligibility criteria for this product
        age_restriction: Customer's age is outside the accepted range
        income_insufficient: Customer's declared income is below the minimum threshold
        geographic_restriction: Customer's location is not supported for this product
        consents_missing: Required consents (`terms` and `privacy_policy`) were not accepted
        invalid_product: Product not found or not active
        blacklisted: Customer is on the internal blacklist
        fraud_suspected: Fraud signals detected in the submission
    PersonalData:
      type: object
      required:
        - first_name
        - last_name
        - gender
      properties:
        first_name:
          type: string
        last_name:
          type: string
        second_surname:
          type: string
        birthday:
          type: string
          format: date
        identification_number:
          type: string
          description: National ID, NIE or passport number
        nacionality:
          type: string
          default: ES
        gender:
          $ref: '#/components/schemas/Gender'
    Contact:
      type: object
      required:
        - email
      properties:
        email:
          $ref: '#/components/schemas/EmailContact'
        phone:
          $ref: '#/components/schemas/PhoneContact'
    Address:
      type: object
      properties:
        street:
          type: string
        house:
          type: string
        floor:
          type: string
        apartment:
          type: string
        city:
          type: string
        region:
          type: string
        postal_code:
          type: string
        country:
          type: string
          default: ES
    Profile:
      type: object
      required:
        - occupation
        - monthly_income
      properties:
        occupation:
          $ref: '#/components/schemas/OccupationType'
        monthly_income:
          type: integer
          description: Net monthly income in euros
        monthly_expenses:
          type: integer
          description: Monthly expenses in euros
        marital_status:
          $ref: '#/components/schemas/MaritalStatus'
        children:
          type: integer
          default: 0
        education:
          $ref: '#/components/schemas/EducationLevel'
        housing_tenure:
          $ref: '#/components/schemas/HousingTenure'
        housing_type:
          $ref: '#/components/schemas/HousingType'
        company_sector:
          $ref: '#/components/schemas/CompanySector'
    FinancialInstrument:
      type: object
      properties:
        type:
          type: string
          enum:
            - bank_account
            - credit_card
            - debit_card
        currency:
          type: string
          default: EUR
        iban:
          type: string
        status:
          $ref: '#/components/schemas/VerificationStatus'
    LoanPurpose:
      type: string
      enum:
        - personal_expenses
        - bill_payment
        - medical_expenses
        - home_improvement
        - vehicle_purchase
        - education
        - big_purchase
        - business
        - debt_consolidation
        - leisure
        - unforeseen_costs
        - family
        - wedding
        - other
    ConsentEntry:
      type: object
      required:
        - type
        - accepted
        - accepted_at
        - version
      properties:
        type:
          type: string
          description: >-
            Consent type identifier defined by the publisher. Common values are
            `terms`, `privacy_policy`, `marketing`.
          example: terms
        accepted:
          type: boolean
        accepted_at:
          type:
            - string
            - 'null'
          format: date-time
        version:
          type: string
          description: Version of the consent document as defined by the publisher.
          example: v3.2
        document_url:
          type:
            - string
            - 'null'
          description: >-
            URL of the exact document the user accepted. Recommended for GDPR
            auditability.
          example: https://publisher.com/legal/terms/v3.2
    ConsentMetadata:
      type: object
      required:
        - ip_address
        - collected_at
      description: Required for GDPR compliance.
      properties:
        ip_address:
          type: string
          format: ipv4
        user_agent:
          type: string
        collected_at:
          type: string
          format: date-time
    Gender:
      type: string
      enum:
        - male
        - female
        - other
    EmailContact:
      type: object
      required:
        - address
      properties:
        address:
          type: string
          format: email
          description: >-
            The customer's email address. Acts as the unique identifier per
            product — submitting the same email for the same product returns
            `customer_already_exists`.
        status:
          $ref: '#/components/schemas/VerificationStatus'
        verified_at:
          type: string
          format: date-time
        verification_method:
          $ref: '#/components/schemas/VerificationMethod'
        source:
          type: string
    PhoneContact:
      type: object
      properties:
        area_code:
          type: string
          default: '+34'
        number:
          type: string
        status:
          $ref: '#/components/schemas/VerificationStatus'
        verified_at:
          type: string
          format: date-time
        verification_method:
          $ref: '#/components/schemas/VerificationMethod'
        source:
          type: string
    OccupationType:
      type: string
      enum:
        - indefinite_employee
        - part_time_employee
        - temporary_employee
        - self_employed
        - civil_servant
        - military
        - homemaker
        - student
        - unemployed
        - social_welfare
        - retired
        - pensioner
        - other
    MaritalStatus:
      type: string
      enum:
        - married
        - single
        - widowed
        - separated
        - divorced
        - domestic_partnership
        - other
    EducationLevel:
      type: string
      enum:
        - none
        - primary
        - secondary
        - high_school
        - vocational
        - university_degree
        - masters
        - doctorate
        - other
    HousingTenure:
      type: string
      enum:
        - owner_with_mortgage
        - owner_without_mortgage
        - rental
        - other
    HousingType:
      type: string
      enum:
        - apartment
        - house
        - studio
        - villa
        - townhouse
        - duplex
        - other
    CompanySector:
      type: string
      enum:
        - banking_finance_insurance
        - construction_real_estate
        - education
        - energy_communications
        - healthcare
        - hospitality_tourism
        - manufacturing_production
        - military_defense
        - public_administration
        - raw_materials_mining
        - sales_retail
        - technology_telecommunications
        - transport_logistics
        - leisure_entertainment
        - other
    VerificationStatus:
      type: string
      enum:
        - verified
        - not_verified
        - pending
    VerificationMethod:
      type: string
      enum:
        - otp
        - sms
        - link
        - manual
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: apikey
      description: API Key provided by TECHEA

````