openapi: 3.1.0
info:
  title: Lexeon API
  version: "1.1.0"
  summary: Submit merchant applications to Lexeon and read what Lexeon knows about each business.
  description: |
    The Lexeon API lives at one address, `https://api.lexeon.ai`, and uses one API key per customer.

    ## Businesses and applications
    A **business** is one merchant as you see it. An **application** is one submission about a
    business. A business can have several applications (for example a resubmission), and every
    business keeps one stable Lexeon business id: the id of its first application.

    Each business answers with the same core details plus a block per stage:

    * `onboarding`: its applications in Lexeon Gateway, and whether one has been sent for monitoring.
    * `monitoring` (on `GET /v1/businesses/{id}`): its Lexeon Prism monitoring status and score, or
      `null` while it is not monitored.

    ## Authentication
    Every request carries your Lexeon API key in the `Authorization` header:

        Authorization: Bearer <your API key>

    Keys are stored by Lexeon only as hashes, can be revoked at any time, and only ever see your own
    organisation's data. Each key carries scopes: `applications.read`, `applications.write`,
    `businesses.read` and `businesses.write`. Keep keys secret and send them over HTTPS only.

    ## Limits and versioning
    Above the request limit the API answers `429`; wait for the number of seconds in `Retry-After`.
    Published response shapes are a versioned contract: fields may be added, but a breaking change
    ships as a new version with advance notice.

    ## Monitoring
    Lexeon never starts monitoring a business on its own. Monitoring starts only with an explicit call
    to `POST /v1/businesses/{id}/monitoring` (or `POST /v1/applications/{id}/lexeon-send` for one
    application), or the equivalent button in the Lexeon console.
  contact:
    name: Lexeon support
    email: support@lexeon.ai
servers:
  - url: https://api.lexeon.ai
    description: Lexeon API
security:
  - bearerKey: []
tags:
  - name: Businesses
    description: One entry per merchant, across its applications and monitoring.
  - name: Applications
    description: Individual merchant applications.
paths:
  /v1/businesses:
    get:
      tags: [Businesses]
      operationId: listBusinesses
      summary: List businesses
      description: |
        Your program's businesses, newest first (by when the business was first seen), each with its
        onboarding block. Use `GET /v1/businesses/{id}` for the monitoring block. Requires the
        `businesses.read` scope.
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
      responses:
        "200":
          description: One page of businesses
          content:
            application/json:
              schema:
                type: object
                required: [data, next_cursor, errors]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/Business" }
                  next_cursor:
                    type: [string, "null"]
                    description: Pass as `cursor` for the next page; null on the last page.
                  errors: { type: array, maxItems: 0, items: {} }
              example:
                data:
                  - id: 1626de7d-96c1-49cb-8935-26ed5ab83305
                    merchant: { legal_name: Example Roofing Co LLC, dba: Example Roofing, website: https://example.com, state: NY }
                    first_seen_at: "2026-10-01T12:00:00.000Z"
                    onboarding:
                      application_count: 1
                      latest_application: { id: 1626de7d-96c1-49cb-8935-26ed5ab83305, status: approved_conditional, submitted_at: "2026-10-01T12:00:00.000Z", updated_at: "2026-10-08T15:59:27.000Z" }
                      sent_to_prism: true
                next_cursor: null
                errors: []
        "400": { $ref: "#/components/responses/GatewayBadRequest" }
        "401": { $ref: "#/components/responses/GatewayUnauthorized" }
        "403": { $ref: "#/components/responses/GatewayForbidden" }
  /v1/businesses/{id}:
    get:
      tags: [Businesses]
      operationId: getBusiness
      summary: Get a business
      description: |
        One business with its onboarding block and its monitoring block. `monitoring` is `null` while
        the business is not monitored. If monitoring details cannot be reached for a moment, the
        business is still returned with `monitoring: null` and an entry in `errors` with the code
        `MONITORING_UNAVAILABLE`. Requires the `businesses.read` scope.
      parameters:
        - $ref: "#/components/parameters/BusinessId"
      responses:
        "200":
          description: The business
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/BusinessDetail" }
                  errors:
                    type: array
                    items:
                      type: object
                      properties:
                        code: { type: string, examples: [MONITORING_UNAVAILABLE] }
                        message: { type: string }
              example:
                data:
                  id: 1626de7d-96c1-49cb-8935-26ed5ab83305
                  merchant: { legal_name: Example Roofing Co LLC, dba: Example Roofing, website: https://example.com, state: NY }
                  first_seen_at: "2026-10-01T12:00:00.000Z"
                  onboarding:
                    application_count: 1
                    latest_application: { id: 1626de7d-96c1-49cb-8935-26ed5ab83305, status: approved_conditional, submitted_at: "2026-10-01T12:00:00.000Z", updated_at: "2026-10-08T15:59:27.000Z" }
                    sent_to_prism: true
                  monitoring:
                    status: active
                    monitored_since: "2026-10-08T15:59:27.313Z"
                    removed_at: null
                    prism_score: 775
                    tier: T3
                    last_scored_at: "2026-10-08T16:05:10.003Z"
                errors: []
        "401": { $ref: "#/components/responses/GatewayUnauthorized" }
        "403": { $ref: "#/components/responses/GatewayForbidden" }
        "404": { $ref: "#/components/responses/BusinessNotFound" }
  /v1/businesses/{id}/fields:
    get:
      tags: [Businesses]
      operationId: getBusinessFields
      summary: Get a business's enriched fields
      description: |
        The enriched fields of the business's most recent application, in the same shape as
        `GET /v1/applications/{id}/fields`. Requires the `businesses.read` scope.
      parameters:
        - $ref: "#/components/parameters/BusinessId"
      responses:
        "200":
          description: The business's latest fields
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      business_id: { type: string }
                      source_application_id: { type: string, format: uuid, description: The application the fields came from. }
                      fields:
                        type: array
                        items: { $ref: "#/components/schemas/Field" }
                  errors: { type: array, maxItems: 0, items: {} }
              example:
                data:
                  business_id: 1626de7d-96c1-49cb-8935-26ed5ab83305
                  source_application_id: 1626de7d-96c1-49cb-8935-26ed5ab83305
                  fields:
                    - { field_id: website_resolves, label: Website Resolves, value: true, provenance: VERIFIED, source: http_check, captured_at: "2026-10-08T15:40:00.000Z" }
                errors: []
        "401": { $ref: "#/components/responses/GatewayUnauthorized" }
        "403": { $ref: "#/components/responses/GatewayForbidden" }
        "404": { $ref: "#/components/responses/BusinessNotFound" }
  /v1/businesses/{id}/monitoring:
    post:
      tags: [Businesses]
      operationId: startMonitoring
      summary: Start monitoring a business
      description: |
        Sends the business's most recent application to Lexeon Prism for ongoing monitoring. The
        application must be approved. `202` means Prism accepted it; Prism then scores it in the
        background, and the monitoring block on `GET /v1/businesses/{id}` fills in. Requires the
        `businesses.write` scope.
      parameters:
        - $ref: "#/components/parameters/BusinessId"
      responses:
        "202":
          description: Accepted for monitoring
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      business_id: { type: string }
                      application_id: { type: string, format: uuid, description: The application that was sent. }
                      status: { type: string, const: accepted }
                      prism_receipt_id: { type: [string, "null"] }
                      prism_batch_id: { type: [string, "null"] }
                  errors: { type: array, maxItems: 0, items: {} }
              example:
                data: { business_id: 1626de7d-96c1-49cb-8935-26ed5ab83305, application_id: 1626de7d-96c1-49cb-8935-26ed5ab83305, status: accepted, prism_receipt_id: rcpt_8f2c, prism_batch_id: IB-0044 }
                errors: []
        "401": { $ref: "#/components/responses/GatewayUnauthorized" }
        "403": { $ref: "#/components/responses/GatewayForbidden" }
        "404": { $ref: "#/components/responses/BusinessNotFound" }
        "422":
          description: The business cannot be sent (for example its latest application is not approved, or was already accepted)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/GatewayError" }
              example: { data: null, errors: [{ code: NOT_SENT, message: "Cannot send from status 'manual_review'. The merchant must be approved before it can be sent to PRISM Dock." }] }
  /v1/applications:
    post:
      tags: [Applications]
      operationId: submitApplication
      summary: Submit a merchant application
      description: |
        Creates an application in your program. Gateway then resolves the merchant's identity and
        enriches it. Requires a key with the `applications.write` scope. Either `legalName` or `dba`
        is required.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [merchant]
              properties:
                merchant:
                  $ref: "#/components/schemas/MerchantInput"
            example:
              merchant:
                legalName: Example Roofing Co LLC
                dba: Example Roofing
                state: NY
                url: https://example.com
                city: Albany
                naics: "238160"
      responses:
        "202":
          description: Accepted for processing
          content:
            application/json:
              schema:
                type: object
                properties:
                  application_id: { type: string, format: uuid }
                  status: { type: string, examples: [received] }
                  duplicate_of:
                    type: [string, "null"]
                    description: Id of an existing application for the same merchant, if any.
              example:
                application_id: 1626de7d-96c1-49cb-8935-26ed5ab83305
                status: received
                duplicate_of: null
        "400":
          description: The merchant details are not valid
          content:
            application/json:
              schema: { $ref: "#/components/schemas/GatewayAuthError" }
              example: { error: "state: Invalid US state" }
        "401": { $ref: "#/components/responses/GatewayUnauthorized" }
        "403": { $ref: "#/components/responses/GatewayForbidden" }
    get:
      tags: [Applications]
      operationId: listApplications
      summary: List applications
      description: Your program's applications, newest first. Requires the `applications.read` scope.
      parameters:
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
        - name: cursor
          in: query
          description: The `next_cursor` from the previous page.
          schema: { type: string }
      responses:
        "200":
          description: One page of applications
          content:
            application/json:
              schema:
                type: object
                required: [data, next_cursor, errors]
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/Application" }
                  next_cursor:
                    type: [string, "null"]
                    description: Pass as `cursor` for the next page; null on the last page.
                  errors: { type: array, maxItems: 0, items: {} }
              example:
                data:
                  - id: 1626de7d-96c1-49cb-8935-26ed5ab83305
                    business_id: 1626de7d-96c1-49cb-8935-26ed5ab83305
                    status: approved_conditional
                    merchant: { legal_name: Example Roofing Co LLC, dba: Example Roofing, website: https://example.com, state: NY }
                    prism_delivery: accepted
                    submitted_at: "2026-10-01T12:00:00.000Z"
                    updated_at: "2026-10-08T15:59:27.000Z"
                next_cursor: MjAyNi0xMC0wMVQxMjowMDowMC4wMDBafDE2MjZkZTdk
                errors: []
        "400": { $ref: "#/components/responses/GatewayBadRequest" }
        "401": { $ref: "#/components/responses/GatewayUnauthorized" }
        "403": { $ref: "#/components/responses/GatewayForbidden" }
  /v1/applications/{id}/fields:
    get:
      tags: [Applications]
      operationId: getApplicationFields
      summary: Get an application's enriched fields
      description: |
        The best available value for each enriched field, with its provenance (how it was established),
        source and capture time. Internal working fields and empty values are not included. Requires
        the `applications.read` scope.
      parameters:
        - $ref: "#/components/parameters/ApplicationId"
      responses:
        "200":
          description: The application's fields
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      application_id: { type: string, format: uuid }
                      status: { type: string }
                      fields:
                        type: array
                        items: { $ref: "#/components/schemas/Field" }
                  errors: { type: array, maxItems: 0, items: {} }
              example:
                data:
                  application_id: 1626de7d-96c1-49cb-8935-26ed5ab83305
                  status: approved_conditional
                  fields:
                    - { field_id: domain_age_years, label: Domain Age (years), value: 12, provenance: REFERENCED, source: whois, captured_at: "2026-10-08T15:40:00.000Z" }
                errors: []
        "401": { $ref: "#/components/responses/GatewayUnauthorized" }
        "403": { $ref: "#/components/responses/GatewayForbidden" }
        "404": { $ref: "#/components/responses/GatewayNotFound" }
  /v1/applications/{id}/lexeon-send:
    post:
      tags: [Applications]
      operationId: sendApplicationToPrism
      summary: Send an application to Prism
      description: |
        Explicitly sends an approved application to Prism for ongoing monitoring. `202` means Prism
        accepted it; Prism then files and processes it. An application already accepted is not sent
        again. Requires the `applications.write` scope.
      parameters:
        - $ref: "#/components/parameters/ApplicationId"
      responses:
        "202":
          description: Accepted by Prism
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      application_id: { type: string, format: uuid }
                      status: { type: string, const: accepted }
                      prism_receipt_id: { type: [string, "null"] }
                      prism_batch_id: { type: [string, "null"] }
                      lex_mid: { type: [string, "null"], description: Prism's merchant reference. }
                  errors: { type: array, maxItems: 0, items: {} }
              example:
                data: { application_id: 1626de7d-96c1-49cb-8935-26ed5ab83305, status: accepted, prism_receipt_id: rcpt_8f2c, prism_batch_id: IB-0044, lex_mid: null }
                errors: []
        "401": { $ref: "#/components/responses/GatewayUnauthorized" }
        "403": { $ref: "#/components/responses/GatewayForbidden" }
        "404": { $ref: "#/components/responses/GatewayNotFound" }
        "422":
          description: The application cannot be sent (for example not approved, or already accepted)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/GatewayAuthError" }
              example: { error: "Cannot send from status 'manual_review'. The merchant must be approved before it can be sent to PRISM Dock." }
components:
  securitySchemes:
    bearerKey:
      type: http
      scheme: bearer
      description: "An API key issued by Lexeon, sent as `Authorization: Bearer <key>`."
  parameters:
    Limit:
      name: limit
      in: query
      schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
    Cursor:
      name: cursor
      in: query
      description: The `next_cursor` from the previous page.
      schema: { type: string }
    BusinessId:
      name: id
      in: path
      required: true
      description: The Lexeon business id (the id of the business's first application).
      schema: { type: string }
    ApplicationId:
      name: id
      in: path
      required: true
      description: The application id returned when it was submitted.
      schema: { type: string, format: uuid }
  schemas:
    MerchantInput:
      type: object
      required: [state, url]
      properties:
        legalName: { type: string, maxLength: 200 }
        dba: { type: string, maxLength: 200 }
        state: { type: string, minLength: 2, maxLength: 2, description: Two-letter US state. }
        url: { type: string, maxLength: 500, description: The merchant's website. }
        city: { type: string, maxLength: 100 }
        naics: { type: string, pattern: "^[0-9]{6}$" }
        principal: { type: string, maxLength: 200 }
        phone: { type: string, maxLength: 30 }
        contactEmail: { type: string, format: email }
        address: { type: string, maxLength: 300 }
        ein: { type: string, maxLength: 20 }
    Application:
      type: object
      required: [id, business_id, status, merchant, prism_delivery, submitted_at, updated_at]
      properties:
        id: { type: string, format: uuid }
        business_id: { type: string, description: The Lexeon business this application belongs to. }
        status: { type: string, description: "Gateway status, for example received, manual_review, approved, approved_conditional, declined." }
        merchant:
          type: object
          properties:
            legal_name: { type: [string, "null"] }
            dba: { type: [string, "null"] }
            website: { type: [string, "null"] }
            state: { type: [string, "null"] }
        prism_delivery:
          type: string
          description: "not_sent until sent; then accepted, enrolled, failed or unknown."
        submitted_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
    Field:
      type: object
      required: [field_id, label, value, provenance]
      properties:
        field_id: { type: string }
        label: { type: string }
        value: { description: The field's value (string, number, boolean or object). }
        provenance:
          type: [string, "null"]
          description: "How the value was established: VERIFIED, REFERENCED, FOOTNOTED, COMPOSED or INFERRED."
        source: { type: [string, "null"] }
        captured_at: { type: [string, "null"], format: date-time }
    Business:
      type: object
      required: [id, merchant, first_seen_at, onboarding]
      properties:
        id: { type: string, description: The Lexeon business id. }
        merchant:
          type: object
          description: From the business's most recent application.
          properties:
            legal_name: { type: [string, "null"] }
            dba: { type: [string, "null"] }
            website: { type: [string, "null"] }
            state: { type: [string, "null"] }
        first_seen_at: { type: string, format: date-time }
        onboarding:
          type: object
          properties:
            application_count: { type: integer }
            latest_application:
              type: object
              properties:
                id: { type: string, format: uuid }
                status: { type: string }
                submitted_at: { type: string, format: date-time }
                updated_at: { type: string, format: date-time }
            sent_to_prism: { type: boolean, description: True once any of its applications was accepted for monitoring. }
    BusinessDetail:
      allOf:
        - $ref: "#/components/schemas/Business"
        - type: object
          required: [monitoring]
          properties:
            monitoring:
              oneOf:
                - $ref: "#/components/schemas/Monitoring"
                - type: "null"
    Monitoring:
      type: object
      properties:
        status: { type: string, description: "Monitoring status, for example active." }
        monitored_since: { type: string, format: date-time }
        removed_at: { type: [string, "null"], format: date-time }
        prism_score: { type: [integer, "null"], description: Prism score, 0 to 1000. }
        tier: { type: [string, "null"] }
        last_scored_at: { type: [string, "null"], format: date-time }
    GatewayAuthError:
      type: object
      properties:
        error: { type: string }
    GatewayError:
      type: object
      properties:
        data: { type: "null" }
        errors:
          type: array
          items:
            type: object
            properties:
              code: { type: string, examples: [INVALID_LIMIT, INVALID_CURSOR, NOT_FOUND] }
              message: { type: string }
  responses:
    GatewayBadRequest:
      description: A query parameter is not valid
      content:
        application/json:
          schema: { $ref: "#/components/schemas/GatewayError" }
          example: { data: null, errors: [{ code: INVALID_LIMIT, message: "limit must be a whole number from 1 to 100" }] }
    GatewayUnauthorized:
      description: The API key is missing, invalid or revoked
      content:
        application/json:
          schema: { $ref: "#/components/schemas/GatewayAuthError" }
          example: { error: Invalid key }
    GatewayForbidden:
      description: The key does not have the scope this call needs
      content:
        application/json:
          schema: { $ref: "#/components/schemas/GatewayAuthError" }
          example: { error: "Scope 'applications.write' required" }
    GatewayNotFound:
      description: No application with that id in your program
      content:
        application/json:
          schema: { $ref: "#/components/schemas/GatewayError" }
          example: { data: null, errors: [{ code: NOT_FOUND, message: No application with that id in this program }] }
    BusinessNotFound:
      description: No business with that id in your program
      content:
        application/json:
          schema: { $ref: "#/components/schemas/GatewayError" }
          example: { data: null, errors: [{ code: NOT_FOUND, message: No business with that id in this program }] }
