# Generated by openapi/build.ts (npm run spec:build). Do not edit by hand.
openapi: 3.1.0
info:
  title: Valjean Specialty agent API
  version: 1.0.0
  summary: Check appetite, send submissions, answer what's needed, follow files, get quotes and ask to bind.
  description: "For retail agencies' own software and AI assistants. Every key sees only its own agency's files. A named Valjean broker works every file: chooses the markets, confirms anything you send, presents quotes and completes binding. Market names appear only on quotes your broker presents."
  contact:
    name: Valjean Specialty
    email: submissions@valjeanspecialty.com
    url: https://valjeanspecialty.com
  license:
    name: "Proprietary: for Valjean Specialty agencies"
    url: https://valjeanspecialty.com
  x-mcp:
    url: https://api.valjeanspecialty.com/api/mcp
    transport: streamable-http
    tools:
      - whoami
      - check_appetite
      - get_requirements
      - create_submission
      - list_submissions
      - get_submission
      - answer_questions
      - underwriter_questions
      - add_document
      - get_upload_link
      - finish_upload
      - send_documents
      - whats_new
      - list_documents
      - get_document_link
      - request_bind
      - list_bind_requests
      - withdraw_bind_request
      - ask_instant_quotes
      - list_policies
      - get_policy
      - list_invoices
      - list_quotes
servers:
  - url: https://api.valjeanspecialty.com/api/agent/v1
    description: Live and demo (the key decides which book you see)
security:
  - bearerAuth: []
  - apiKeyHeader: []
  - oauth2:
      - read
tags:
  - name: Account
    description: Check a key and what it can do.
  - name: Appetite
    description: Will Valjean likely be able to place a risk.
  - name: Requirements
    description: What a class of risk needs, before a file exists.
  - name: Submissions
    description: Send files and follow them.
  - name: Answers
    description: Answer what's needed, once for every market.
  - name: Documents
    description: Upload and download documents.
  - name: Quotes
    description: Quotes your broker has presented.
  - name: Bind
    description: Your written instruction to bind, and its progress.
  - name: Instant quotes
    description: Ask markets connected by API for instant quotes.
  - name: Events
    description: A cursor feed of everything that changed.
  - name: Webhooks
    description: Signed pushes of the same events.
  - name: Policies
    description: Policies placed through Valjean.
  - name: Invoices
    description: Invoices sent to your agency.
  - name: Public
    description: "No key needed: the website's appetite check and class guides."
paths:
  /me:
    get:
      operationId: getMe
      summary: Who this key belongs to
      description: |-
        Your agency, the key's name and scope, and whether it can submit. A good first call to check a key works.

        Any key (read or read-and-submit).
      tags:
        - Account
      x-valjean-scope: read
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - read
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Me"
              example:
                agency: Palmetto Coast Insurance Group
                key: Our AMS
                scope: submit
                can_submit: true
                environment: live
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X GET "https://api.valjeanspecialty.com/api/agent/v1/me" \
              -H "Authorization: Bearer $VALJEAN_KEY"
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/me", {
              method: "GET",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, },
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.get(
                "https://api.valjeanspecialty.com/api/agent/v1/me",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
            )
            print(res.json())
      x-agentic-access:
        action: read
        consequence: Reads only; changes nothing.
        human_in_the_loop: None needed.
        reversible: Nothing to undo.
        confirm_with_user: false
  /appetite:
    post:
      operationId: checkAppetite
      summary: Will Valjean likely place it
      description: |-
        Checks Valjean's appetite on file for the lines, state and class. Counts only markets Valjean can reach (appointed, or at most a possible fit while an appointment is under way). Never names a market. Anything but a good fit includes how_to_send.

        Any key (read or read-and-submit).
      tags:
        - Appetite
      x-valjean-scope: read
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - read
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                lines:
                  minItems: 1
                  maxItems: 6
                  type: array
                  items:
                    type: string
                    minLength: 1
                    maxLength: 60
                state:
                  type: string
                  minLength: 2
                  maxLength: 40
                naics:
                  type: string
                  pattern: ^[0-9]{2,6}$
                description:
                  type: string
                  maxLength: 300
                target_premium:
                  type: number
                  minimum: 0
                  maximum: 1000000000
                tiv:
                  type: number
                  minimum: 0
                  maximum: 100000000000
              required:
                - lines
                - state
              additionalProperties: false
            example:
              lines:
                - GL
                - LL
              state: SC
              description: seafood restaurant with a full bar
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AppetiteAnswer"
              example:
                fit: possible
                appetite_on_file: true
                headline: "Possible fit in SC: a broker will confirm."
                market_count: 2
                market_count_label: 2 markets
                lines:
                  - line: GL
                    fit: good
                  - line: LL
                    fit: possible
                notes: []
                needs:
                  - "Annual sales split: food vs liquor"
                  - Hours of operation and closing time
                class_matched: Hospitality
                state: SC
                how_to_send:
                  email: submissions@valjeanspecialty.com
                  include:
                    - Insured name, address and a short description of operations
                    - Five years of currently valued loss runs
                  note: A broker reviews every submission, including ones outside the appetite on file.
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X POST "https://api.valjeanspecialty.com/api/agent/v1/appetite" \
              -H "Authorization: Bearer $VALJEAN_KEY" \
              -H "Content-Type: application/json" \
              -d '{"lines":["GL","LL"],"state":"SC","description":"seafood restaurant with a full bar"}'
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/appetite", {
              method: "POST",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, "Content-Type": "application/json" },
              body: JSON.stringify({
                "lines": [
                  "GL",
                  "LL"
                ],
                "state": "SC",
                "description": "seafood restaurant with a full bar"
              }),
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.post(
                "https://api.valjeanspecialty.com/api/agent/v1/appetite",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
                json={
                  "lines": [
                    "GL",
                    "LL"
                  ],
                  "state": "SC",
                  "description": "seafood restaurant with a full bar"
                },
            )
            print(res.json())
      x-agentic-access:
        action: read
        consequence: Reads only; changes nothing.
        human_in_the_loop: None needed.
        reversible: Nothing to undo.
        confirm_with_user: false
  /requirements:
    post:
      operationId: getRequirements
      summary: What Valjean will need for this risk
      description: |-
        Everything the class needs before a file exists: the class checklist plus markets' own asks, each with a key to answer by later and an answer type.

        Any key (read or read-and-submit).
      tags:
        - Requirements
      x-valjean-scope: read
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - read
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                lines:
                  minItems: 1
                  maxItems: 6
                  type: array
                  items:
                    type: string
                    minLength: 1
                    maxLength: 60
                state:
                  type: string
                  minLength: 2
                  maxLength: 40
                naics:
                  type: string
                  pattern: ^[0-9]{2,6}$
                description:
                  type: string
                  maxLength: 300
              required:
                - lines
                - state
              additionalProperties: false
            example:
              lines:
                - GL
                - LL
              state: SC
              description: bar and grill
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Requirements"
              example:
                class_matched: Hospitality
                state: SC
                lines:
                  - GL
                  - LL
                items:
                  - key: sales_split
                    label: "Annual sales split: food vs liquor"
                    type: field
                    level: required
                    help: null
                    answer:
                      type: text
                  - key: hours
                    label: Hours of operation and closing time
                    type: question
                    level: required
                    help: null
                    answer:
                      type: text
                  - key: loss_runs
                    label: Currently valued loss runs – 5 years
                    type: document
                    level: required
                    help: null
                    answer:
                      type: file
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X POST "https://api.valjeanspecialty.com/api/agent/v1/requirements" \
              -H "Authorization: Bearer $VALJEAN_KEY" \
              -H "Content-Type: application/json" \
              -d '{"lines":["GL","LL"],"state":"SC","description":"bar and grill"}'
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/requirements", {
              method: "POST",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, "Content-Type": "application/json" },
              body: JSON.stringify({
                "lines": [
                  "GL",
                  "LL"
                ],
                "state": "SC",
                "description": "bar and grill"
              }),
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.post(
                "https://api.valjeanspecialty.com/api/agent/v1/requirements",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
                json={
                  "lines": [
                    "GL",
                    "LL"
                  ],
                  "state": "SC",
                  "description": "bar and grill"
                },
            )
            print(res.json())
      x-agentic-access:
        action: read
        consequence: Reads only; changes nothing.
        human_in_the_loop: None needed.
        reversible: Nothing to undo.
        confirm_with_user: false
  /submissions:
    get:
      operationId: listSubmissions
      summary: List your submissions
      description: |-
        Your agency's submissions, most recently updated first. status takes stage names, comma-separated (new, needs_info, marketing, quoted, bound, declined, lost, closed).

        Any key (read or read-and-submit).
      tags:
        - Submissions
      x-valjean-scope: read
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - read
      parameters:
        - name: status
          in: query
          required: false
          schema:
            type: string
            pattern: ^[a-z_,]+$
        - name: updated_since
          in: query
          required: false
          schema:
            type: string
            format: date-time
        - name: search
          in: query
          required: false
          schema:
            type: string
            maxLength: 100
        - name: limit
          in: query
          required: false
          schema:
            default: 25
            type: integer
            minimum: 1
            maximum: 100
        - name: offset
          in: query
          required: false
          schema:
            default: 0
            type: integer
            minimum: 0
            maximum: 10000
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SubmissionList"
              example:
                total: 1
                submissions:
                  - id: 6f2a1c9e-4b3d-4e8a-9c21-7d4e5f6a8b90
                    reference: VS-000624
                    insured: Shem Creek Oyster Bar LLC
                    lines:
                      - General Liability
                      - Liquor Liability
                    effective_date: 2026-12-15
                    status: Waiting on information from you
                    stage: needs_info
                    received_at: 2026-11-02T14:15:11Z
                    updated_at: 2026-11-02T14:16:02Z
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X GET "https://api.valjeanspecialty.com/api/agent/v1/submissions" \
              -H "Authorization: Bearer $VALJEAN_KEY"
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/submissions", {
              method: "GET",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, },
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.get(
                "https://api.valjeanspecialty.com/api/agent/v1/submissions",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
            )
            print(res.json())
      x-agentic-access:
        action: read
        consequence: Reads only; changes nothing.
        human_in_the_loop: None needed.
        reversible: Nothing to undo.
        confirm_with_user: false
    post:
      operationId: createSubmission
      summary: Send a submission
      description: |-
        Creates a file from structured facts. Always send client_reference or an Idempotency-Key header: a retry with the same value returns the same file (200, duplicate: true) instead of creating another. The response lists what's still needed.

        Needs a **read-and-submit** key and an agency with a signed broker agreement.
      tags:
        - Submissions
      x-valjean-scope: submit
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - submit
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: "Your own id for this request (up to 80 letters, numbers, . _ : -). Used as client_reference when the body has none; a retry with the same key returns the first file (200, duplicate: true). If both are sent they must match."
          schema:
            type: string
            pattern: ^[\w.:-]{1,80}$
          example: AMS-10442
        - name: dry_run
          in: query
          required: false
          description: 1 to check the request and see what would happen, without saving or sending anything.
          schema:
            type: string
            enum:
              - "1"
              - "true"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                client_reference:
                  type: string
                  pattern: ^[\w.:-]{1,80}$
                insured:
                  type: object
                  properties:
                    name:
                      type: string
                      minLength: 1
                      maxLength: 200
                    state:
                      type: string
                      minLength: 2
                      maxLength: 40
                    city:
                      type: string
                      maxLength: 100
                    address:
                      type: string
                      maxLength: 200
                    postal_code:
                      type: string
                      pattern: ^\d{5}(-\d{4})?$
                  required:
                    - name
                    - state
                  additionalProperties: false
                lines:
                  minItems: 1
                  maxItems: 6
                  type: array
                  items:
                    type: string
                    minLength: 1
                    maxLength: 60
                effective_date:
                  type: string
                  format: date
                naics:
                  type: string
                  pattern: ^[0-9]{2,6}$
                class_description:
                  type: string
                  maxLength: 300
                operations:
                  type: string
                  maxLength: 4000
                annual_revenue:
                  type: number
                  minimum: 0
                  maximum: 100000000000
                annual_payroll:
                  type: number
                  minimum: 0
                  maximum: 100000000000
                tiv:
                  type: number
                  minimum: 0
                  maximum: 100000000000
                years_in_business:
                  type: integer
                  minimum: 0
                  maximum: 300
                target_premium:
                  type: number
                  minimum: 0
                  maximum: 100000000000
                expiring_premium:
                  type: number
                  minimum: 0
                  maximum: 100000000000
                prior_carrier:
                  type: string
                  maxLength: 200
                requested_limits:
                  type: string
                  maxLength: 500
                contact_email:
                  type: string
                  format: email
                  pattern: ^(?:[A-Za-z0-9_'+\-]+\.)*[A-Za-z0-9_'+\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$
                notes:
                  type: string
                  maxLength: 4000
              required:
                - insured
                - lines
                - effective_date
              additionalProperties: false
            example:
              client_reference: AMS-10442
              insured:
                name: Shem Creek Oyster Bar LLC
                state: SC
                city: Mount Pleasant
              lines:
                - GL
                - LL
              effective_date: 2026-12-15
              class_description: Bar and restaurant
              operations: Seafood restaurant with a full bar, closes at midnight
              annual_revenue: 2100000
      responses:
        "200":
          description: "With ?dry_run=1: every check passed and nothing was saved or sent."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DryRun"
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "201":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SubmissionCreated"
              example:
                id: 6f2a1c9e-4b3d-4e8a-9c21-7d4e5f6a8b90
                reference: VS-000624
                insured: Shem Creek Oyster Bar LLC
                lines:
                  - General Liability
                  - Liquor Liability
                effective_date: 2026-12-15
                status: Waiting on information from you
                stage: needs_info
                received_at: 2026-11-02T14:15:11Z
                updated_at: 2026-11-02T14:16:02Z
                files:
                  - ACORD 125.pdf
                needs:
                  - key: hours
                    label: Hours of operation and closing time
                    type: question
                    level: required
                    how: answer
                    answer:
                      type: text
                  - key: capacity
                    label: Seating / occupancy capacity
                    type: question
                    level: recommended
                    how: answer
                    answer:
                      type: number
                  - key: loss_runs
                    label: Currently valued loss runs – 5 years
                    type: document
                    level: required
                    how: upload a document
                    answer:
                      type: file
                quotes_ready: 0
                markets:
                  reviewing: 0
                  declined: 0
                  quoted: 0
                  no_response: 0
                  total: 0
                  presented: 0
                  text: Not sent to markets yet.
                broker:
                  name: "[Broker name]"
                  email: broker@valjeanspecialty.com
                duplicate: false
                status_url: https://valjops.vercel.app/s/EXAMPLE-status-link
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X POST "https://api.valjeanspecialty.com/api/agent/v1/submissions" \
              -H "Authorization: Bearer $VALJEAN_KEY" \
              -H "Content-Type: application/json" \
              -d '{"client_reference":"AMS-10442","insured":{"name":"Shem Creek Oyster Bar LLC","state":"SC","city":"Mount Pleasant"},"lines":["GL","LL"],"effective_date":"2026-12-15","class_description":"Bar and restaurant","operations":"Seafood restaurant with a full bar, closes at midnight","annual_revenue":2100000}'
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/submissions", {
              method: "POST",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, "Content-Type": "application/json" },
              body: JSON.stringify({
                "client_reference": "AMS-10442",
                "insured": {
                  "name": "Shem Creek Oyster Bar LLC",
                  "state": "SC",
                  "city": "Mount Pleasant"
                },
                "lines": [
                  "GL",
                  "LL"
                ],
                "effective_date": "2026-12-15",
                "class_description": "Bar and restaurant",
                "operations": "Seafood restaurant with a full bar, closes at midnight",
                "annual_revenue": 2100000
              }),
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.post(
                "https://api.valjeanspecialty.com/api/agent/v1/submissions",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
                json={
                  "client_reference": "AMS-10442",
                  "insured": {
                    "name": "Shem Creek Oyster Bar LLC",
                    "state": "SC",
                    "city": "Mount Pleasant"
                  },
                  "lines": [
                    "GL",
                    "LL"
                  ],
                  "effective_date": "2026-12-15",
                  "class_description": "Bar and restaurant",
                  "operations": "Seafood restaurant with a full bar, closes at midnight",
                  "annual_revenue": 2100000
                },
            )
            print(res.json())
      x-agentic-access:
        action: create
        consequence: Opens a file with Valjean for this agency.
        human_in_the_loop: A Valjean broker reviews every file before anything goes to a market.
        reversible: "Not by API: ask your broker to close the file. Retries with the same client_reference or Idempotency-Key never duplicate it."
        confirm_with_user: true
        dry_run: dry_run=1
        idempotency: Idempotency-Key header or client_reference
  /intake:
    post:
      operationId: sendDocuments
      summary: Send documents and a description
      description: |-
        Starts a file from documents plus a few sentences. Valjean reads the documents with AI and a broker confirms the details. multipart/form-data with "files" (up to 5, 4 MB in total) and the fields below (lines comma-separated), or JSON with the same fields and no files.

        Needs a **read-and-submit** key and an agency with a signed broker agreement.
      tags:
        - Submissions
      x-valjean-scope: submit
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - submit
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: "Your own id for this request (up to 80 letters, numbers, . _ : -). Used as client_reference when the body has none; a retry with the same key returns the first file (200, duplicate: true). If both are sent they must match."
          schema:
            type: string
            pattern: ^[\w.:-]{1,80}$
          example: AMS-10442
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                files:
                  type: array
                  items:
                    type: string
                    contentMediaType: application/octet-stream
                  maxItems: 5
                  description: Up to 5 files, 4 MB in total. PDF, images, Word, Excel, CSV or text.
                narrative:
                  type: string
                  maxLength: 8000
                insured_name:
                  type: string
                  maxLength: 200
                state:
                  type: string
                lines:
                  type: string
                  description: Comma-separated, e.g. GL,LL.
                effective_date:
                  type: string
                  format: date
                client_reference:
                  type: string
                contact_email:
                  type: string
                  format: email
            example:
              narrative: Family-owned seafood restaurant with a bar in Folly Beach, open 11 to 11.
              state: SC
              lines: GL,LL
              client_reference: INT-1
          application/json:
            schema:
              type: object
              properties:
                narrative:
                  type: string
                  minLength: 1
                  maxLength: 8000
                client_reference:
                  type: string
                  pattern: ^[\w.:-]{1,80}$
                insured_name:
                  type: string
                  minLength: 1
                  maxLength: 200
                state:
                  type: string
                  minLength: 2
                  maxLength: 40
                lines:
                  maxItems: 6
                  type: array
                  items:
                    type: string
                    minLength: 1
                    maxLength: 60
                effective_date:
                  type: string
                  format: date
                contact_email:
                  type: string
                  format: email
                  pattern: ^(?:[A-Za-z0-9_'+\-]+\.)*[A-Za-z0-9_'+\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$
              additionalProperties: false
      responses:
        "200":
          description: "A retry with the same client_reference or Idempotency-Key: the first file, with duplicate: true. Nothing new is created."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IntakeCreated"
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "201":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IntakeCreated"
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "413":
          $ref: "#/components/responses/TooLarge"
        "415":
          $ref: "#/components/responses/UnsupportedType"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X POST "https://api.valjeanspecialty.com/api/agent/v1/intake" \
              -H "Authorization: Bearer $VALJEAN_KEY" \
              -F "narrative=Seafood restaurant with a full bar in Mount Pleasant" \
              -F "state=SC" -F "lines=GL,LL" -F "client_reference=INT-1" \
              -F "files=@ACORD-125.pdf;type=application/pdf"
        - lang: javascript
          label: Node
          source: |-
            import fs from "node:fs";

            const form = new FormData();
            form.append("files", new Blob([await fs.promises.readFile("loss-runs.pdf")], { type: "application/pdf" }), "loss-runs.pdf");
            form.append("narrative", "Seafood restaurant with a full bar in Mount Pleasant");
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/intake", {
              method: "POST",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}` },
              body: form,
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.post(
                "https://api.valjeanspecialty.com/api/agent/v1/intake",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
                files={"files": ("loss-runs.pdf", open("loss-runs.pdf", "rb"), "application/pdf")},
            )
            print(res.json())
      x-agentic-access:
        action: create
        consequence: Opens a file from documents and a description.
        human_in_the_loop: A broker confirms the details read from the documents.
        reversible: "Not by API: ask your broker to close the file."
        confirm_with_user: true
        idempotency: Idempotency-Key header or client_reference
      x-todo:
        - JSON bodies are validated by intakeSchema (shown under application/json); multipart fields are read as strings first.
  /submissions/{id}:
    get:
      operationId: getSubmission
      summary: Where a submission stands
      description: |-
        Status in plain words, the files on it, what Valjean still needs (each with a key), anonymous market progress, your broker, and how many quotes are ready.

        Any key (read or read-and-submit).
      tags:
        - Submissions
      x-valjean-scope: read
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - read
      parameters:
        - name: id
          in: path
          required: true
          description: Submission id.
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Submission"
              example:
                id: 6f2a1c9e-4b3d-4e8a-9c21-7d4e5f6a8b90
                reference: VS-000624
                insured: Shem Creek Oyster Bar LLC
                lines:
                  - General Liability
                  - Liquor Liability
                effective_date: 2026-12-15
                status: Waiting on information from you
                stage: needs_info
                received_at: 2026-11-02T14:15:11Z
                updated_at: 2026-11-02T14:16:02Z
                files:
                  - ACORD 125.pdf
                needs:
                  - key: hours
                    label: Hours of operation and closing time
                    type: question
                    level: required
                    how: answer
                    answer:
                      type: text
                  - key: capacity
                    label: Seating / occupancy capacity
                    type: question
                    level: recommended
                    how: answer
                    answer:
                      type: number
                  - key: loss_runs
                    label: Currently valued loss runs – 5 years
                    type: document
                    level: required
                    how: upload a document
                    answer:
                      type: file
                quotes_ready: 0
                markets:
                  reviewing: 0
                  declined: 0
                  quoted: 0
                  no_response: 0
                  total: 0
                  presented: 0
                  text: Not sent to markets yet.
                broker:
                  name: "[Broker name]"
                  email: broker@valjeanspecialty.com
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X GET "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID" \
              -H "Authorization: Bearer $VALJEAN_KEY"
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID", {
              method: "GET",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, },
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.get(
                "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
            )
            print(res.json())
      x-agentic-access:
        action: read
        consequence: Reads only; changes nothing.
        human_in_the_loop: None needed.
        reversible: Nothing to undo.
        confirm_with_user: false
  /submissions/{id}/answers:
    patch:
      operationId: answerQuestions
      summary: Answer what's needed, once for every market
      description: |-
        Answers open items by key; one answer fills every market that asks the same thing. fields only fill blanks: anything Valjean already has is kept. Answers that don't fit the item's answer type come back in rejected and aren't saved. Or send message: the answers in plain sentences, and Valjean maps them to the open items (from_message shows what it understood; anything sent by key wins). A broker confirms everything. POST to the same path works too.

        Needs a **read-and-submit** key and an agency with a signed broker agreement.
      tags:
        - Answers
      x-valjean-scope: submit
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - submit
      parameters:
        - name: id
          in: path
          required: true
          description: Submission id.
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                answers:
                  default: []
                  maxItems: 50
                  type: array
                  items:
                    type: object
                    properties:
                      key:
                        type: string
                        pattern: ^[a-z0-9_]{1,80}$
                      value:
                        type: string
                        minLength: 1
                        maxLength: 4000
                    required:
                      - key
                      - value
                    additionalProperties: false
                fields:
                  default: {}
                  type: object
                  properties:
                    annual_revenue:
                      type: number
                      minimum: 0
                      maximum: 100000000000
                    annual_payroll:
                      type: number
                      minimum: 0
                      maximum: 100000000000
                    tiv:
                      type: number
                      minimum: 0
                      maximum: 100000000000
                    years_in_business:
                      type: integer
                      minimum: 0
                      maximum: 300
                    subcontractor_costs:
                      type: number
                      minimum: 0
                      maximum: 100000000000
                    naics_code:
                      type: string
                      pattern: ^[0-9]{2,6}$
                    class_description:
                      type: string
                      maxLength: 300
                    operations_description:
                      type: string
                      maxLength: 4000
                    requested_limits:
                      type: string
                      maxLength: 500
                    prior_carrier:
                      type: string
                      maxLength: 200
                    expiring_premium:
                      type: number
                      minimum: 0
                      maximum: 100000000000
                    target_premium:
                      type: number
                      minimum: 0
                      maximum: 100000000000
                  additionalProperties: false
                message:
                  type: string
                  minLength: 1
                  maxLength: 4000
              additionalProperties: false
            example:
              answers:
                - key: hours
                  value: 11am to midnight, 7 days
              message: Seats 120. Payroll is $640K and they've been open 12 years.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AnswerResult"
              example:
                answered:
                  - hours
                  - capacity
                unmatched: []
                rejected: []
                upload_instead: []
                fields_filled:
                  - annual_payroll
                  - years_in_business
                fields_kept: []
                from_message:
                  answers:
                    - key: capacity
                      value: "120"
                  fields:
                    - annual_payroll
                    - years_in_business
                  not_understood: []
                note: Answers go to your broker to confirm. Anything already on file was kept.
                needs: []
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
        "503":
          $ref: "#/components/responses/Unavailable"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X PATCH "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/answers" \
              -H "Authorization: Bearer $VALJEAN_KEY" \
              -H "Content-Type: application/json" \
              -d '{"answers":[{"key":"hours","value":"11am to midnight, 7 days"}],"message":"Seats 120. Payroll is $640K and they've been open 12 years."}'
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/answers", {
              method: "PATCH",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, "Content-Type": "application/json" },
              body: JSON.stringify({
                "answers": [
                  {
                    "key": "hours",
                    "value": "11am to midnight, 7 days"
                  }
                ],
                "message": "Seats 120. Payroll is $640K and they've been open 12 years."
              }),
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.patch(
                "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/answers",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
                json={
                  "answers": [
                    {
                      "key": "hours",
                      "value": "11am to midnight, 7 days"
                    }
                  ],
                  "message": "Seats 120. Payroll is $640K and they've been open 12 years."
                },
            )
            print(res.json())
      x-agentic-access:
        action: update
        consequence: Records answers on the file.
        human_in_the_loop: A broker confirms every answer; anything already on file is kept.
        reversible: Send a corrected answer for the same key; your broker sees both.
        confirm_with_user: true
    post:
      operationId: answerQuestionsViaPost
      summary: Answer what's needed, once for every market (POST form)
      description: |-
        Same as PATCH /submissions/{id}/answers; kept for clients that can't send PATCH.

        Answers open items by key; one answer fills every market that asks the same thing. fields only fill blanks: anything Valjean already has is kept. Answers that don't fit the item's answer type come back in rejected and aren't saved. Or send message: the answers in plain sentences, and Valjean maps them to the open items (from_message shows what it understood; anything sent by key wins). A broker confirms everything. POST to the same path works too.

        Needs a **read-and-submit** key and an agency with a signed broker agreement.
      tags:
        - Answers
      x-valjean-scope: submit
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - submit
      parameters:
        - name: id
          in: path
          required: true
          description: Submission id.
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                answers:
                  default: []
                  maxItems: 50
                  type: array
                  items:
                    type: object
                    properties:
                      key:
                        type: string
                        pattern: ^[a-z0-9_]{1,80}$
                      value:
                        type: string
                        minLength: 1
                        maxLength: 4000
                    required:
                      - key
                      - value
                    additionalProperties: false
                fields:
                  default: {}
                  type: object
                  properties:
                    annual_revenue:
                      type: number
                      minimum: 0
                      maximum: 100000000000
                    annual_payroll:
                      type: number
                      minimum: 0
                      maximum: 100000000000
                    tiv:
                      type: number
                      minimum: 0
                      maximum: 100000000000
                    years_in_business:
                      type: integer
                      minimum: 0
                      maximum: 300
                    subcontractor_costs:
                      type: number
                      minimum: 0
                      maximum: 100000000000
                    naics_code:
                      type: string
                      pattern: ^[0-9]{2,6}$
                    class_description:
                      type: string
                      maxLength: 300
                    operations_description:
                      type: string
                      maxLength: 4000
                    requested_limits:
                      type: string
                      maxLength: 500
                    prior_carrier:
                      type: string
                      maxLength: 200
                    expiring_premium:
                      type: number
                      minimum: 0
                      maximum: 100000000000
                    target_premium:
                      type: number
                      minimum: 0
                      maximum: 100000000000
                  additionalProperties: false
                message:
                  type: string
                  minLength: 1
                  maxLength: 4000
              additionalProperties: false
            example:
              answers:
                - key: hours
                  value: 11am to midnight, 7 days
              message: Seats 120. Payroll is $640K and they've been open 12 years.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AnswerResult"
              example:
                answered:
                  - hours
                  - capacity
                unmatched: []
                rejected: []
                upload_instead: []
                fields_filled:
                  - annual_payroll
                  - years_in_business
                fields_kept: []
                from_message:
                  answers:
                    - key: capacity
                      value: "120"
                  fields:
                    - annual_payroll
                    - years_in_business
                  not_understood: []
                note: Answers go to your broker to confirm. Anything already on file was kept.
                needs: []
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
        "503":
          $ref: "#/components/responses/Unavailable"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X POST "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/answers" \
              -H "Authorization: Bearer $VALJEAN_KEY" \
              -H "Content-Type: application/json" \
              -d '{"answers":[{"key":"hours","value":"11am to midnight, 7 days"}],"message":"Seats 120. Payroll is $640K and they've been open 12 years."}'
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/answers", {
              method: "POST",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, "Content-Type": "application/json" },
              body: JSON.stringify({
                "answers": [
                  {
                    "key": "hours",
                    "value": "11am to midnight, 7 days"
                  }
                ],
                "message": "Seats 120. Payroll is $640K and they've been open 12 years."
              }),
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.post(
                "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/answers",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
                json={
                  "answers": [
                    {
                      "key": "hours",
                      "value": "11am to midnight, 7 days"
                    }
                  ],
                  "message": "Seats 120. Payroll is $640K and they've been open 12 years."
                },
            )
            print(res.json())
      x-agentic-access:
        action: update
        consequence: Records answers on the file.
        human_in_the_loop: A broker confirms every answer; anything already on file is kept.
        reversible: Send a corrected answer for the same key; your broker sees both.
        confirm_with_user: true
  /submissions/{id}/flags:
    get:
      operationId: getUnderwriterFlags
      summary: What underwriters will ask about
      description: |-
        Things on this file underwriters usually question (payroll not split by class, whether subcontractors are insured, gaps or stale loss runs, large losses without detail), each with a plain question to answer now through the answers endpoint. Read-only, and drawn only from your own file.

        Any key (read or read-and-submit).
      tags:
        - Answers
      x-valjean-scope: read
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - read
      parameters:
        - name: id
          in: path
          required: true
          description: Submission id.
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UnderwriterFlags"
              example:
                submission_id: 6f2a1c9e-4b3d-4e8a-9c21-7d4e5f6a8b90
                flags:
                  - key: loss_runs_gap
                    severity: high
                    title: Loss runs cover 3 of the 5 years needed
                    detail: Loss runs on file cover 2023 to 2026. Markets want 5 years.
                    question: Can you send currently valued loss runs for the two earlier years, or confirm the insured had no coverage then?
                note: Underwriters will likely ask about these. Answering them now (POST /submissions/{id}/answers) usually saves a round trip.
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X GET "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/flags" \
              -H "Authorization: Bearer $VALJEAN_KEY"
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/flags", {
              method: "GET",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, },
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.get(
                "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/flags",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
            )
            print(res.json())
      x-agentic-access:
        action: read
        consequence: Reads only; changes nothing.
        human_in_the_loop: None needed.
        reversible: Nothing to undo.
        confirm_with_user: false
  /submissions/{id}/documents:
    get:
      operationId: listDocuments
      summary: Documents on a submission
      description: |-
        The file's documents, plus the carrier paper you may have: presented quotes, binders, policies and endorsements.

        Any key (read or read-and-submit).
      tags:
        - Documents
      x-valjean-scope: read
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - read
      parameters:
        - name: id
          in: path
          required: true
          description: Submission id.
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DocumentList"
              example:
                documents:
                  - id: 4186317b-232c-4da9-90a3-9f3b1fd9b7dc
                    source: file
                    kind: acord_125
                    file_name: ACORD 125.pdf
                    size_bytes: 182044
                    created_at: 2026-11-02T14:15:12Z
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X GET "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/documents" \
              -H "Authorization: Bearer $VALJEAN_KEY"
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/documents", {
              method: "GET",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, },
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.get(
                "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/documents",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
            )
            print(res.json())
      x-agentic-access:
        action: read
        consequence: Reads only; changes nothing.
        human_in_the_loop: None needed.
        reversible: Nothing to undo.
        confirm_with_user: false
    post:
      operationId: uploadDocument
      summary: Upload a document (up to 4 MB)
      description: |-
        multipart/form-data with "file" and an optional "doc_type". Valjean reads it with AI and updates what's needed. Over 4 MB: use an upload link.

        Needs a **read-and-submit** key and an agency with a signed broker agreement.
      tags:
        - Documents
      x-valjean-scope: submit
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - submit
      parameters:
        - name: id
          in: path
          required: true
          description: Submission id.
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                file:
                  type: string
                  contentMediaType: application/octet-stream
                doc_type:
                  type: string
                  pattern: ^[a-z0-9_]{2,40}$
      responses:
        "201":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DocumentCreated"
              example:
                document_id: 53a6ee6c-2017-4da0-83ab-e9703f6ff67e
                file_name: Loss runs 2021-2026.pdf
                doc_type: loss_runs
                note: Received. Valjean reads it and updates what's still needed in a minute or two.
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "415":
          $ref: "#/components/responses/UnsupportedType"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X POST "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/documents" \
              -H "Authorization: Bearer $VALJEAN_KEY" \
              -F "file=@loss-runs.pdf;type=application/pdf"
        - lang: javascript
          label: Node
          source: |-
            import fs from "node:fs";

            const form = new FormData();
            form.append("file", new Blob([await fs.promises.readFile("loss-runs.pdf")], { type: "application/pdf" }), "loss-runs.pdf");
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/documents", {
              method: "POST",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}` },
              body: form,
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.post(
                "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/documents",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
                files={"file": ("loss-runs.pdf", open("loss-runs.pdf", "rb"), "application/pdf")},
            )
            print(res.json())
      x-agentic-access:
        action: create
        consequence: Adds a document to the file.
        human_in_the_loop: A broker reviews it.
        reversible: "Not by API: ask your broker to remove it."
        confirm_with_user: true
  /submissions/{id}/documents/upload-url:
    post:
      operationId: getUploadLink
      summary: Get a link to upload a large file (up to 25 MB)
      description: |-
        A one-time link. PUT the file's bytes to upload_url with the given Content-Type, then call /documents/complete.

        Needs a **read-and-submit** key and an agency with a signed broker agreement.
      tags:
        - Documents
      x-valjean-scope: submit
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - submit
      parameters:
        - name: id
          in: path
          required: true
          description: Submission id.
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                file_name:
                  type: string
                  minLength: 1
                  maxLength: 200
                content_type:
                  type: string
                  minLength: 3
                  maxLength: 120
              required:
                - file_name
                - content_type
              additionalProperties: false
            example:
              file_name: SOV.xlsx
              content_type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UploadUrl"
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "415":
          $ref: "#/components/responses/UnsupportedType"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X POST "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/documents/upload-url" \
              -H "Authorization: Bearer $VALJEAN_KEY" \
              -H "Content-Type: application/json" \
              -d '{"file_name":"SOV.xlsx","content_type":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}'
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/documents/upload-url", {
              method: "POST",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, "Content-Type": "application/json" },
              body: JSON.stringify({
                "file_name": "SOV.xlsx",
                "content_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
              }),
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.post(
                "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/documents/upload-url",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
                json={
                  "file_name": "SOV.xlsx",
                  "content_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
                },
            )
            print(res.json())
      x-agentic-access:
        action: create
        consequence: Creates a one-time upload link; nothing is added until completeUpload.
        human_in_the_loop: None needed.
        reversible: Unused links expire.
        confirm_with_user: false
  /submissions/{id}/documents/complete:
    post:
      operationId: completeUpload
      summary: Record a large upload
      description: |-
        Call after PUTting the file to its upload link. Calling it twice returns the same document with duplicate: true.

        Needs a **read-and-submit** key and an agency with a signed broker agreement.
      tags:
        - Documents
      x-valjean-scope: submit
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - submit
      parameters:
        - name: id
          in: path
          required: true
          description: Submission id.
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                upload_id:
                  type: string
                  pattern: ^[0-9a-f-]{36}-[\w.\-]{1,120}$
                file_name:
                  type: string
                  minLength: 1
                  maxLength: 200
                content_type:
                  type: string
                  minLength: 3
                  maxLength: 120
                doc_type:
                  type: string
                  pattern: ^[a-z0-9_]{2,40}$
              required:
                - upload_id
                - file_name
                - content_type
              additionalProperties: false
            example:
              upload_id: ed46fd9f-afa9-4f32-b134-5fb1abddcbdc-SOV.xlsx
              file_name: SOV.xlsx
              content_type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
      responses:
        "201":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DocumentCreated"
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "415":
          $ref: "#/components/responses/UnsupportedType"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X POST "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/documents/complete" \
              -H "Authorization: Bearer $VALJEAN_KEY" \
              -H "Content-Type: application/json" \
              -d '{"upload_id":"ed46fd9f-afa9-4f32-b134-5fb1abddcbdc-SOV.xlsx","file_name":"SOV.xlsx","content_type":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}'
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/documents/complete", {
              method: "POST",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, "Content-Type": "application/json" },
              body: JSON.stringify({
                "upload_id": "ed46fd9f-afa9-4f32-b134-5fb1abddcbdc-SOV.xlsx",
                "file_name": "SOV.xlsx",
                "content_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
              }),
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.post(
                "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/documents/complete",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
                json={
                  "upload_id": "ed46fd9f-afa9-4f32-b134-5fb1abddcbdc-SOV.xlsx",
                  "file_name": "SOV.xlsx",
                  "content_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
                },
            )
            print(res.json())
      x-agentic-access:
        action: create
        consequence: Adds the uploaded document to the file.
        human_in_the_loop: A broker reviews it.
        reversible: "Not by API: ask your broker to remove it."
        confirm_with_user: true
  /submissions/{id}/documents/{docId}:
    get:
      operationId: getDocumentLink
      summary: Download a document
      description: |-
        A download link good for 5 minutes. Add ?redirect=1 to be redirected to the file instead. The link is a credential: don't log or share it.

        Any key (read or read-and-submit).
      tags:
        - Documents
      x-valjean-scope: read
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - read
      parameters:
        - name: id
          in: path
          required: true
          description: Submission id.
          schema:
            type: string
            format: uuid
        - name: docId
          in: path
          required: true
          description: Document id from the documents list.
          schema:
            type: string
            format: uuid
        - name: redirect
          in: query
          required: false
          description: 1 to be redirected (302) to the file instead of getting JSON.
          schema:
            type: string
            enum:
              - "1"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DocumentLink"
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "302":
          description: "With ?redirect=1: redirect to the file."
          headers:
            Location:
              schema:
                type: string
                format: uri
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X GET "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/documents/DOCUMENT_ID" \
              -H "Authorization: Bearer $VALJEAN_KEY"
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/documents/DOCUMENT_ID", {
              method: "GET",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, },
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.get(
                "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/documents/DOCUMENT_ID",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
            )
            print(res.json())
      x-agentic-access:
        action: read
        consequence: Reads only; changes nothing.
        human_in_the_loop: None needed.
        reversible: Nothing to undo.
        confirm_with_user: false
  /submissions/{id}/quotes:
    get:
      operationId: listQuotes
      summary: Quotes your broker has presented
      description: |-
        Only quotes a broker has presented, never ones still being worked. Totals include fees; surplus lines taxes are added at binding.

        Any key (read or read-and-submit).
      tags:
        - Quotes
      x-valjean-scope: read
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - read
      parameters:
        - name: id
          in: path
          required: true
          description: Submission id.
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QuoteList"
              example:
                submission_id: 6f2a1c9e-4b3d-4e8a-9c21-7d4e5f6a8b90
                quotes:
                  - id: 6f1d2c3b-8a9e-4b7c-9d10-1e2f3a4b5c6d
                    carrier: Magnolia Underwriting Managers
                    lines:
                      - GL
                      - LL
                    premium: 17075
                    fees: 250
                    total: 17325
                    limits: "GL: $1,000,000 per occurrence / $2,000,000 aggregate"
                    deductibles: "GL: $2,500 BI/PD"
                    exclusions: null
                    subjectivities: Signed application; currently valued loss runs
                    expires_on: 2026-12-02
                note: "To bind, reply to your Valjean broker: binding needs a signed request and the subjectivities met."
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X GET "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/quotes" \
              -H "Authorization: Bearer $VALJEAN_KEY"
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/quotes", {
              method: "GET",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, },
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.get(
                "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/quotes",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
            )
            print(res.json())
      x-agentic-access:
        action: read
        consequence: Reads only; changes nothing.
        human_in_the_loop: None needed.
        reversible: Nothing to undo.
        confirm_with_user: false
  /submissions/{id}/quotes/{quoteId}/bind-request:
    post:
      operationId: requestBind
      summary: Ask to bind a quote
      description: |-
        Your written instruction to bind a presented quote. It never binds by itself: your broker confirms with the market and sends the binder, and you get bind.status_changed events.

        Needs a **read-and-submit** key and an agency with a signed broker agreement.
      tags:
        - Bind
      x-valjean-scope: submit
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - submit
      parameters:
        - name: id
          in: path
          required: true
          description: Submission id.
          schema:
            type: string
            format: uuid
        - name: quoteId
          in: path
          required: true
          description: A presented quote's id.
          schema:
            type: string
            format: uuid
        - name: dry_run
          in: query
          required: false
          description: 1 to check the request and see what would happen, without saving or sending anything.
          schema:
            type: string
            enum:
              - "1"
              - "true"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                instruction:
                  type: string
                  minLength: 10
                  maxLength: 2000
                requested_effective_date:
                  type: string
                  format: date
                contact_name:
                  type: string
                  minLength: 2
                  maxLength: 120
                contact_email:
                  type: string
                  format: email
                  pattern: ^(?:[A-Za-z0-9_'+\-]+\.)*[A-Za-z0-9_'+\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$
                accept_subjectivities:
                  type: boolean
                  const: true
              required:
                - instruction
                - contact_name
                - accept_subjectivities
              additionalProperties: false
            example:
              instruction: Please bind as quoted, effective 12/15/2026
              contact_name: Dana Whitfield
              contact_email: dana@agency.example
              accept_subjectivities: true
      responses:
        "200":
          description: "With ?dry_run=1: every check passed and nothing was saved or sent."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DryRun"
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "201":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BindRequestCreated"
              example:
                bind_request_id: 9c8b7a6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d
                status: preparing
                status_text: "Received: your broker is preparing it"
                quote:
                  id: 6f1d2c3b-8a9e-4b7c-9d10-1e2f3a4b5c6d
                  carrier: Magnolia Underwriting Managers
                  premium: 17075
                subjectivities: Signed application; currently valued loss runs
                note: Not bound yet. Your broker confirms it with the market and sends the binder; you'll get bind.status_changed events.
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X POST "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/quotes/QUOTE_ID/bind-request" \
              -H "Authorization: Bearer $VALJEAN_KEY" \
              -H "Content-Type: application/json" \
              -d '{"instruction":"Please bind as quoted, effective 12/15/2026","contact_name":"Dana Whitfield","contact_email":"dana@agency.example","accept_subjectivities":true}'
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/quotes/QUOTE_ID/bind-request", {
              method: "POST",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, "Content-Type": "application/json" },
              body: JSON.stringify({
                "instruction": "Please bind as quoted, effective 12/15/2026",
                "contact_name": "Dana Whitfield",
                "contact_email": "dana@agency.example",
                "accept_subjectivities": true
              }),
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.post(
                "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/quotes/QUOTE_ID/bind-request",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
                json={
                  "instruction": "Please bind as quoted, effective 12/15/2026",
                  "contact_name": "Dana Whitfield",
                  "contact_email": "dana@agency.example",
                  "accept_subjectivities": True
                },
            )
            print(res.json())
      x-agentic-access:
        action: create
        consequence: The agent's written instruction to bind a presented quote. It does not bind.
        human_in_the_loop: Your broker confirms with the market and sends the binder.
        reversible: withdrawBindRequest while it's still being prepared; after that, call your broker.
        confirm_with_user: true
        dry_run: dry_run=1
  /submissions/{id}/bind-requests:
    get:
      operationId: listBindRequests
      summary: Bind progress
      description: |-
        Bind requests on a submission and where each stands.

        Any key (read or read-and-submit).
      tags:
        - Bind
      x-valjean-scope: read
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - read
      parameters:
        - name: id
          in: path
          required: true
          description: Submission id.
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BindRequestList"
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X GET "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/bind-requests" \
              -H "Authorization: Bearer $VALJEAN_KEY"
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/bind-requests", {
              method: "GET",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, },
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.get(
                "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/bind-requests",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
            )
            print(res.json())
      x-agentic-access:
        action: read
        consequence: Reads only; changes nothing.
        human_in_the_loop: None needed.
        reversible: Nothing to undo.
        confirm_with_user: false
  /submissions/{id}/bind-requests/{bindRequestId}:
    delete:
      operationId: withdrawBindRequest
      summary: Withdraw a bind request
      description: |-
        Withdraws your bind request while your broker is still preparing it (status preparing), before anything goes to the market. Once it's been sent to the market (requested), this returns 409 not_withdrawable: call your broker. Withdrawing twice is fine and returns the same result.

        Needs a **read-and-submit** key and an agency with a signed broker agreement.
      tags:
        - Bind
      x-valjean-scope: submit
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - submit
      parameters:
        - name: id
          in: path
          required: true
          description: Submission id.
          schema:
            type: string
            format: uuid
        - name: bindRequestId
          in: path
          required: true
          description: The bind request's id (from the bind request or bind progress).
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BindWithdrawn"
              example:
                bind_request_id: 9c8b7a6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d
                status: cancelled
                status_text: Cancelled
                note: Withdrawn. Nothing was sent to the market.
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X DELETE "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/bind-requests/{bindRequestId}" \
              -H "Authorization: Bearer $VALJEAN_KEY"
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/bind-requests/{bindRequestId}", {
              method: "DELETE",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, },
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.delete(
                "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/bind-requests/{bindRequestId}",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
            )
            print(res.json())
      x-agentic-access:
        action: delete
        consequence: Withdraws a bind request before it reaches the market.
        human_in_the_loop: None needed.
        reversible: Ask to bind again.
        confirm_with_user: true
  /submissions/{id}/instant-quotes:
    post:
      operationId: askInstantQuotes
      summary: Ask connected markets for instant quotes
      description: |-
        Asks markets connected to Valjean by API that fit the file. Depending on Valjean's setting, a broker approves the ask first (status: pending) or it runs at once (status: done). Either way the file must pass Valjean's send rules, and a price reaches you only once it's presented.

        Needs a **read-and-submit** key and an agency with a signed broker agreement.
      tags:
        - Instant quotes
      x-valjean-scope: submit
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - submit
      parameters:
        - name: id
          in: path
          required: true
          description: Submission id.
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                note:
                  type: string
                  maxLength: 2000
              additionalProperties: false
            example:
              note: Hoping for a quick number
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InstantQuotesResult"
              example:
                status: pending
                status_text: Sent to your broker to approve.
                request_id: 7ae4f5ab-5ef3-41c6-a449-138394ead28c
                markets_that_fit: 2
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X POST "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/instant-quotes" \
              -H "Authorization: Bearer $VALJEAN_KEY" \
              -H "Content-Type: application/json" \
              -d '{"note":"Hoping for a quick number"}'
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/instant-quotes", {
              method: "POST",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, "Content-Type": "application/json" },
              body: JSON.stringify({
                "note": "Hoping for a quick number"
              }),
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.post(
                "https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/instant-quotes",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
                json={
                  "note": "Hoping for a quick number"
                },
            )
            print(res.json())
      x-agentic-access:
        action: create
        consequence: Asks connected markets for instant quotes on the file.
        human_in_the_loop: Depending on Valjean's setting, a broker approves the ask first; prices reach you only once presented.
        reversible: Not by API.
        confirm_with_user: true
  /events:
    get:
      operationId: listEvents
      summary: What changed on your files
      description: |-
        Every change on your agency's files, oldest first. Pass after (the next_after from last time) for only newer events; with neither after nor since you get the last 7 days. Events are kept 90 days. type filters, comma-separated, and accepts a prefix like bind.*.

        Any key (read or read-and-submit).
      tags:
        - Events
      x-valjean-scope: read
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - read
      parameters:
        - name: after
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
        - name: since
          in: query
          required: false
          schema:
            type: string
            format: date-time
        - name: type
          in: query
          required: false
          schema:
            type: string
            pattern: ^[a-z_.*,]+$
        - name: limit
          in: query
          required: false
          schema:
            default: 50
            type: integer
            minimum: 1
            maximum: 200
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventList"
              example:
                events:
                  - id: 41
                    created_at: 2026-11-02T14:20:00Z
                    type: submission.status_changed
                    submission_id: 6f2a1c9e-4b3d-4e8a-9c21-7d4e5f6a8b90
                    data:
                      status: marketing
                      previous: needs_info
                      status_text: With insurance markets for review
                next_after: 41
                has_more: false
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X GET "https://api.valjeanspecialty.com/api/agent/v1/events" \
              -H "Authorization: Bearer $VALJEAN_KEY"
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/events", {
              method: "GET",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, },
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.get(
                "https://api.valjeanspecialty.com/api/agent/v1/events",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
            )
            print(res.json())
      x-agentic-access:
        action: read
        consequence: Reads only; changes nothing.
        human_in_the_loop: None needed.
        reversible: Nothing to undo.
        confirm_with_user: false
  /webhooks:
    get:
      operationId: listWebhooks
      summary: Your webhooks
      description: |-
        Your webhooks, the event types you can subscribe to, and the signature header name.

        Any key (read or read-and-submit).
      tags:
        - Webhooks
      x-valjean-scope: read
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - read
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookList"
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X GET "https://api.valjeanspecialty.com/api/agent/v1/webhooks" \
              -H "Authorization: Bearer $VALJEAN_KEY"
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/webhooks", {
              method: "GET",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, },
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.get(
                "https://api.valjeanspecialty.com/api/agent/v1/webhooks",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
            )
            print(res.json())
      x-agentic-access:
        action: read
        consequence: Reads only; changes nothing.
        human_in_the_loop: None needed.
        reversible: Nothing to undo.
        confirm_with_user: false
      x-todo:
        - Read-only keys can create, delete and test webhooks (they only ever carry your own agency's events). Confirm that's intended.
    post:
      operationId: createWebhook
      summary: Add a webhook
      description: |-
        Public https addresses only, up to 5 per agency. The signing secret is returned once. An empty events list means every event; a prefix like bind.* works too.

        Any key (read or read-and-submit).
      tags:
        - Webhooks
      x-valjean-scope: read
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - read
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                url:
                  type: string
                  maxLength: 500
                  format: uri
                events:
                  default: []
                  maxItems: 20
                  type: array
                  items:
                    type: string
                description:
                  type: string
                  maxLength: 200
              required:
                - url
              additionalProperties: false
            example:
              url: https://ams.agency.example/hooks/valjean
              events:
                - quote.presented
                - bind.*
      responses:
        "201":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookCreated"
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X POST "https://api.valjeanspecialty.com/api/agent/v1/webhooks" \
              -H "Authorization: Bearer $VALJEAN_KEY" \
              -H "Content-Type: application/json" \
              -d '{"url":"https://ams.agency.example/hooks/valjean","events":["quote.presented","bind.*"]}'
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/webhooks", {
              method: "POST",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, "Content-Type": "application/json" },
              body: JSON.stringify({
                "url": "https://ams.agency.example/hooks/valjean",
                "events": [
                  "quote.presented",
                  "bind.*"
                ]
              }),
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.post(
                "https://api.valjeanspecialty.com/api/agent/v1/webhooks",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
                json={
                  "url": "https://ams.agency.example/hooks/valjean",
                  "events": [
                    "quote.presented",
                    "bind.*"
                  ]
                },
            )
            print(res.json())
      x-agentic-access:
        action: create
        consequence: Starts sending your agency's events to a URL.
        human_in_the_loop: None needed.
        reversible: deleteWebhook.
        confirm_with_user: false
      x-todo:
        - Read-only keys can create webhooks; see listWebhooks.
  /webhooks/{id}:
    delete:
      operationId: deleteWebhook
      summary: Remove a webhook
      description: |-
        Stops deliveries to that address at once.

        Any key (read or read-and-submit).
      tags:
        - Webhooks
      x-valjean-scope: read
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - read
      parameters:
        - name: id
          in: path
          required: true
          description: Webhook id.
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookDeleted"
              example:
                deleted: true
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X DELETE "https://api.valjeanspecialty.com/api/agent/v1/webhooks/WEBHOOK_ID" \
              -H "Authorization: Bearer $VALJEAN_KEY"
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/webhooks/WEBHOOK_ID", {
              method: "DELETE",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, },
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.delete(
                "https://api.valjeanspecialty.com/api/agent/v1/webhooks/WEBHOOK_ID",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
            )
            print(res.json())
      x-agentic-access:
        action: delete
        consequence: Stops sending events to that URL.
        human_in_the_loop: None needed.
        reversible: createWebhook again (a new signing secret).
        confirm_with_user: false
  /webhooks/{id}/test:
    post:
      operationId: testWebhook
      summary: Send a test event
      description: |-
        Sends a signed webhook.ping straight away and reports what your endpoint returned.

        Any key (read or read-and-submit).
      tags:
        - Webhooks
      x-valjean-scope: read
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - read
      parameters:
        - name: id
          in: path
          required: true
          description: Webhook id.
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookTest"
              example:
                delivered: true
                status: 200
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X POST "https://api.valjeanspecialty.com/api/agent/v1/webhooks/WEBHOOK_ID/test" \
              -H "Authorization: Bearer $VALJEAN_KEY"
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/webhooks/WEBHOOK_ID/test", {
              method: "POST",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, },
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.post(
                "https://api.valjeanspecialty.com/api/agent/v1/webhooks/WEBHOOK_ID/test",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
            )
            print(res.json())
      x-agentic-access:
        action: create
        consequence: Sends one test event to the webhook.
        human_in_the_loop: None needed.
        reversible: Nothing to undo.
        confirm_with_user: false
  /policies:
    get:
      operationId: listPolicies
      summary: Policies placed through Valjean
      description: |-
        Your agency's policies, newest effective date first. status takes bound, issued, cancelled, expired or renewed, comma-separated.

        Any key (read or read-and-submit).
      tags:
        - Policies
      x-valjean-scope: read
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - read
      parameters:
        - name: status
          in: query
          required: false
          schema:
            type: string
            pattern: ^[a-z,]+$
        - name: limit
          in: query
          required: false
          schema:
            default: 50
            type: integer
            minimum: 1
            maximum: 100
        - name: offset
          in: query
          required: false
          schema:
            default: 0
            type: integer
            minimum: 0
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PolicyList"
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X GET "https://api.valjeanspecialty.com/api/agent/v1/policies" \
              -H "Authorization: Bearer $VALJEAN_KEY"
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/policies", {
              method: "GET",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, },
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.get(
                "https://api.valjeanspecialty.com/api/agent/v1/policies",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
            )
            print(res.json())
      x-agentic-access:
        action: read
        consequence: Reads only; changes nothing.
        human_in_the_loop: None needed.
        reversible: Nothing to undo.
        confirm_with_user: false
  /policies/{id}:
    get:
      operationId: getPolicy
      summary: One policy, with invoices and documents
      description: |-
        The policy, the invoices Valjean has sent for it, and its binder, policy and endorsement documents.

        Any key (read or read-and-submit).
      tags:
        - Policies
      x-valjean-scope: read
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - read
      parameters:
        - name: id
          in: path
          required: true
          description: Policy id.
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PolicyDetail"
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X GET "https://api.valjeanspecialty.com/api/agent/v1/policies/POLICY_ID" \
              -H "Authorization: Bearer $VALJEAN_KEY"
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/policies/POLICY_ID", {
              method: "GET",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, },
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.get(
                "https://api.valjeanspecialty.com/api/agent/v1/policies/POLICY_ID",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
            )
            print(res.json())
      x-agentic-access:
        action: read
        consequence: Reads only; changes nothing.
        human_in_the_loop: None needed.
        reversible: Nothing to undo.
        confirm_with_user: false
  /invoices:
    get:
      operationId: listInvoices
      summary: Invoices sent to your agency
      description: |-
        Invoices Valjean has sent, newest first. Drafts never appear.

        Any key (read or read-and-submit).
      tags:
        - Invoices
      x-valjean-scope: read
      security:
        - bearerAuth: []
        - apiKeyHeader: []
        - oauth2:
            - read
      parameters:
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum:
              - sent
              - paid
              - overdue
        - name: limit
          in: query
          required: false
          schema:
            default: 50
            type: integer
            minimum: 1
            maximum: 100
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InvoiceList"
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X GET "https://api.valjeanspecialty.com/api/agent/v1/invoices" \
              -H "Authorization: Bearer $VALJEAN_KEY"
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/agent/v1/invoices", {
              method: "GET",
              headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, },
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.get(
                "https://api.valjeanspecialty.com/api/agent/v1/invoices",
                headers={"Authorization": f"Bearer {os.environ['VALJEAN_KEY']}"},
            )
            print(res.json())
      x-agentic-access:
        action: read
        consequence: Reads only; changes nothing.
        human_in_the_loop: None needed.
        reversible: Nothing to undo.
        confirm_with_user: false
  /api/appetite:
    post:
      operationId: publicCheckAppetite
      summary: Public appetite check (no key)
      description: |-
        The same appetite answer without a key, for websites. Rate-limited to 30 requests a minute per IP; answers cache at the edge for 5 minutes. GET with the same fields as query parameters (lines comma-separated) also works.

        No key needed.
      tags:
        - Public
      x-valjean-scope: public
      servers:
        - url: https://api.valjeanspecialty.com
          description: Public endpoints (no key)
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                lines:
                  minItems: 1
                  maxItems: 6
                  type: array
                  items:
                    type: string
                    minLength: 1
                    maxLength: 60
                state:
                  type: string
                  minLength: 2
                  maxLength: 40
                naics:
                  type: string
                  pattern: ^[0-9]{2,6}$
                description:
                  type: string
                  maxLength: 300
                target_premium:
                  type: number
                  minimum: 0
                  maximum: 1000000000
                tiv:
                  type: number
                  minimum: 0
                  maximum: 100000000000
              required:
                - lines
                - state
              additionalProperties: false
            example:
              lines:
                - General Liability
              state: South Carolina
              description: restaurant with a bar
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicAppetiteAnswer"
        "400":
          $ref: "#/components/responses/BadRequest"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
      x-codeSamples:
        - lang: shell
          label: curl
          source: |-
            curl -X POST "https://api.valjeanspecialty.com/api/appetite" \
              -H "Content-Type: application/json" \
              -d '{"lines":["General Liability"],"state":"South Carolina","description":"restaurant with a bar"}'
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/appetite", {
              method: "POST",
              headers: { "Content-Type": "application/json" },
              body: JSON.stringify({
                "lines": [
                  "General Liability"
                ],
                "state": "South Carolina",
                "description": "restaurant with a bar"
              }),
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.post(
                "https://api.valjeanspecialty.com/api/appetite",
                json={
                  "lines": [
                    "General Liability"
                  ],
                  "state": "South Carolina",
                  "description": "restaurant with a bar"
                },
            )
            print(res.json())
      x-agentic-access:
        action: read
        consequence: Reads only; changes nothing.
        human_in_the_loop: None needed.
        reversible: Nothing to undo.
        confirm_with_user: false
    get:
      operationId: publicCheckAppetiteGet
      summary: Public appetite check (no key), as a GET
      description: |-
        Same as the POST form, with fields as query parameters; cacheable. lines is comma-separated.

        No key needed.
      tags:
        - Public
      x-valjean-scope: public
      servers:
        - url: https://api.valjeanspecialty.com
          description: Public endpoints (no key)
      security: []
      parameters:
        - name: lines
          in: query
          required: true
          schema:
            minItems: 1
            maxItems: 6
            type: array
            items:
              type: string
              minLength: 1
              maxLength: 60
          style: form
          explode: false
          description: Comma-separated.
        - name: state
          in: query
          required: true
          schema:
            type: string
            minLength: 2
            maxLength: 40
        - name: naics
          in: query
          required: false
          schema:
            type: string
            pattern: ^[0-9]{2,6}$
        - name: description
          in: query
          required: false
          schema:
            type: string
            maxLength: 300
        - name: target_premium
          in: query
          required: false
          schema:
            type: number
            minimum: 0
            maximum: 1000000000
        - name: tiv
          in: query
          required: false
          schema:
            type: number
            minimum: 0
            maximum: 100000000000
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicAppetiteAnswer"
        "400":
          $ref: "#/components/responses/BadRequest"
        "429":
          $ref: "#/components/responses/RateLimited"
      x-codeSamples:
        - lang: shell
          label: curl
          source: curl -X GET "https://api.valjeanspecialty.com/api/appetite"
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/appetite", {
              method: "GET",
              headers: { },
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.get(
                "https://api.valjeanspecialty.com/api/appetite",
            )
            print(res.json())
      x-agentic-access:
        action: read
        consequence: Reads only; changes nothing.
        human_in_the_loop: None needed.
        reversible: Nothing to undo.
        confirm_with_user: false
  /api/guides:
    get:
      operationId: listGuides
      summary: Class guides (no key)
      description: |-
        The classes Valjean publishes guidance for. Cached for an hour.

        No key needed.
      tags:
        - Public
      x-valjean-scope: public
      servers:
        - url: https://api.valjeanspecialty.com
          description: Public endpoints (no key)
      security: []
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GuideList"
        "429":
          $ref: "#/components/responses/RateLimited"
      x-codeSamples:
        - lang: shell
          label: curl
          source: curl -X GET "https://api.valjeanspecialty.com/api/guides"
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/guides", {
              method: "GET",
              headers: { },
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.get(
                "https://api.valjeanspecialty.com/api/guides",
            )
            print(res.json())
      x-agentic-access:
        action: read
        consequence: Reads only; changes nothing.
        human_in_the_loop: None needed.
        reversible: Nothing to undo.
        confirm_with_user: false
  /api/guides/{key}:
    get:
      operationId: getGuide
      summary: One class guide (no key)
      description: |-
        What Valjean needs for the class, with answer types, whether each item is confirmed with an underwriter, and a live appetite read for SC and GA.

        No key needed.
      tags:
        - Public
      x-valjean-scope: public
      servers:
        - url: https://api.valjeanspecialty.com
          description: Public endpoints (no key)
      security: []
      parameters:
        - name: key
          in: path
          required: true
          description: Guide key, e.g. gl_hospitality.
          schema:
            type: string
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Guide"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
      x-codeSamples:
        - lang: shell
          label: curl
          source: curl -X GET "https://api.valjeanspecialty.com/api/guides/gl_hospitality"
        - lang: javascript
          label: Node
          source: |-
            const res = await fetch("https://api.valjeanspecialty.com/api/guides/gl_hospitality", {
              method: "GET",
              headers: { },
            });
            console.log(await res.json());
        - lang: python
          label: Python
          source: |-
            import os, requests

            res = requests.get(
                "https://api.valjeanspecialty.com/api/guides/gl_hospitality",
            )
            print(res.json())
      x-agentic-access:
        action: read
        consequence: Reads only; changes nothing.
        human_in_the_loop: None needed.
        reversible: Nothing to undo.
        confirm_with_user: false
webhooks:
  submission.received:
    post:
      operationId: webhook_submission_received
      summary: A submission was created for your agency.
      description: |-
        A submission was created for your agency.

        POSTed to each of your webhooks subscribed to it. Reply 2xx within 8 seconds. Failures retry after 1, 5, 30, 120, 360, 720 and 1440 minutes (8 attempts, about 45 hours); a webhook is turned off after 50 failures in a row. Deliveries go out within about a minute. Use the event id to de-duplicate: a retry after a timeout can deliver the same event twice.
      tags:
        - Webhooks
      parameters:
        - name: Valjean-Signature
          in: header
          required: true
          description: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with your webhook's signing secret>.
          schema:
            type: string
            example: t=1793600000,v1=5f2b…
        - name: Valjean-Event-Id
          in: header
          required: true
          schema:
            type: string
        - name: Valjean-Event-Type
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Event_submission_received"
            example:
              id: 41
              type: submission.received
              created_at: 2026-11-02T14:20:00Z
              agency: Palmetto Coast Insurance Group
              submission_id: 6f2a1c9e-4b3d-4e8a-9c21-7d4e5f6a8b90
              data:
                status: new
                status_text: "Received: we're reviewing it"
      responses:
        "200":
          description: Received. Any 2xx works.
  submission.status_changed:
    post:
      operationId: webhook_submission_status_changed
      summary: A submission moved to a new stage.
      description: |-
        A submission moved to a new stage.

        POSTed to each of your webhooks subscribed to it. Reply 2xx within 8 seconds. Failures retry after 1, 5, 30, 120, 360, 720 and 1440 minutes (8 attempts, about 45 hours); a webhook is turned off after 50 failures in a row. Deliveries go out within about a minute. Use the event id to de-duplicate: a retry after a timeout can deliver the same event twice.
      tags:
        - Webhooks
      parameters:
        - name: Valjean-Signature
          in: header
          required: true
          description: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with your webhook's signing secret>.
          schema:
            type: string
            example: t=1793600000,v1=5f2b…
        - name: Valjean-Event-Id
          in: header
          required: true
          schema:
            type: string
        - name: Valjean-Event-Type
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Event_submission_status_changed"
            example:
              id: 41
              type: submission.status_changed
              created_at: 2026-11-02T14:20:00Z
              agency: Palmetto Coast Insurance Group
              submission_id: 6f2a1c9e-4b3d-4e8a-9c21-7d4e5f6a8b90
              data:
                status: marketing
                previous: needs_info
                status_text: With insurance markets for review
      responses:
        "200":
          description: Received. Any 2xx works.
  needs.requested:
    post:
      operationId: webhook_needs_requested
      summary: Valjean asked you for more information.
      description: |-
        Valjean asked you for more information.

        POSTed to each of your webhooks subscribed to it. Reply 2xx within 8 seconds. Failures retry after 1, 5, 30, 120, 360, 720 and 1440 minutes (8 attempts, about 45 hours); a webhook is turned off after 50 failures in a row. Deliveries go out within about a minute. Use the event id to de-duplicate: a retry after a timeout can deliver the same event twice.
      tags:
        - Webhooks
      parameters:
        - name: Valjean-Signature
          in: header
          required: true
          description: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with your webhook's signing secret>.
          schema:
            type: string
            example: t=1793600000,v1=5f2b…
        - name: Valjean-Event-Id
          in: header
          required: true
          schema:
            type: string
        - name: Valjean-Event-Type
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Event_needs_requested"
            example:
              id: 41
              type: needs.requested
              created_at: 2026-11-02T14:20:00Z
              agency: Palmetto Coast Insurance Group
              submission_id: 6f2a1c9e-4b3d-4e8a-9c21-7d4e5f6a8b90
              data:
                info_request_id: 0b9a6c1e-3f42-4f0e-9d51-2a7f5c8e1d34
                due_on: 2026-11-09
      responses:
        "200":
          description: Received. Any 2xx works.
  markets.updated:
    post:
      operationId: webhook_markets_updated
      summary: "A market answered: anonymous counts, never names."
      description: |-
        A market answered: anonymous counts, never names.

        POSTed to each of your webhooks subscribed to it. Reply 2xx within 8 seconds. Failures retry after 1, 5, 30, 120, 360, 720 and 1440 minutes (8 attempts, about 45 hours); a webhook is turned off after 50 failures in a row. Deliveries go out within about a minute. Use the event id to de-duplicate: a retry after a timeout can deliver the same event twice.
      tags:
        - Webhooks
      parameters:
        - name: Valjean-Signature
          in: header
          required: true
          description: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with your webhook's signing secret>.
          schema:
            type: string
            example: t=1793600000,v1=5f2b…
        - name: Valjean-Event-Id
          in: header
          required: true
          schema:
            type: string
        - name: Valjean-Event-Type
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Event_markets_updated"
            example:
              id: 41
              type: markets.updated
              created_at: 2026-11-02T14:20:00Z
              agency: Palmetto Coast Insurance Group
              submission_id: 6f2a1c9e-4b3d-4e8a-9c21-7d4e5f6a8b90
              data:
                reviewing: 1
                declined: 1
                quoted: 1
                no_response: 0
                total: 3
      responses:
        "200":
          description: Received. Any 2xx works.
  quote.presented:
    post:
      operationId: webhook_quote_presented
      summary: Your broker presented a quote.
      description: |-
        Your broker presented a quote.

        POSTed to each of your webhooks subscribed to it. Reply 2xx within 8 seconds. Failures retry after 1, 5, 30, 120, 360, 720 and 1440 minutes (8 attempts, about 45 hours); a webhook is turned off after 50 failures in a row. Deliveries go out within about a minute. Use the event id to de-duplicate: a retry after a timeout can deliver the same event twice.
      tags:
        - Webhooks
      parameters:
        - name: Valjean-Signature
          in: header
          required: true
          description: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with your webhook's signing secret>.
          schema:
            type: string
            example: t=1793600000,v1=5f2b…
        - name: Valjean-Event-Id
          in: header
          required: true
          schema:
            type: string
        - name: Valjean-Event-Type
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Event_quote_presented"
            example:
              id: 41
              type: quote.presented
              created_at: 2026-11-02T14:20:00Z
              agency: Palmetto Coast Insurance Group
              submission_id: 6f2a1c9e-4b3d-4e8a-9c21-7d4e5f6a8b90
              data:
                quote_id: 6f1d2c3b-8a9e-4b7c-9d10-1e2f3a4b5c6d
                carrier: Magnolia Underwriting Managers
                lines:
                  - GL
                  - LL
                premium: 17075
                expires_on: 2026-12-02
      responses:
        "200":
          description: Received. Any 2xx works.
  bind.status_changed:
    post:
      operationId: webhook_bind_status_changed
      summary: A bind request moved forward.
      description: |-
        A bind request moved forward.

        POSTed to each of your webhooks subscribed to it. Reply 2xx within 8 seconds. Failures retry after 1, 5, 30, 120, 360, 720 and 1440 minutes (8 attempts, about 45 hours); a webhook is turned off after 50 failures in a row. Deliveries go out within about a minute. Use the event id to de-duplicate: a retry after a timeout can deliver the same event twice.
      tags:
        - Webhooks
      parameters:
        - name: Valjean-Signature
          in: header
          required: true
          description: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with your webhook's signing secret>.
          schema:
            type: string
            example: t=1793600000,v1=5f2b…
        - name: Valjean-Event-Id
          in: header
          required: true
          schema:
            type: string
        - name: Valjean-Event-Type
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Event_bind_status_changed"
            example:
              id: 41
              type: bind.status_changed
              created_at: 2026-11-02T14:20:00Z
              agency: Palmetto Coast Insurance Group
              submission_id: 6f2a1c9e-4b3d-4e8a-9c21-7d4e5f6a8b90
              data:
                bind_request_id: 9c8b7a6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d
                quote_id: 6f1d2c3b-8a9e-4b7c-9d10-1e2f3a4b5c6d
                status: preparing
                status_text: "Received: your broker is preparing it"
      responses:
        "200":
          description: Received. Any 2xx works.
  policy.bound:
    post:
      operationId: webhook_policy_bound
      summary: A policy was bound.
      description: |-
        A policy was bound.

        POSTed to each of your webhooks subscribed to it. Reply 2xx within 8 seconds. Failures retry after 1, 5, 30, 120, 360, 720 and 1440 minutes (8 attempts, about 45 hours); a webhook is turned off after 50 failures in a row. Deliveries go out within about a minute. Use the event id to de-duplicate: a retry after a timeout can deliver the same event twice.
      tags:
        - Webhooks
      parameters:
        - name: Valjean-Signature
          in: header
          required: true
          description: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with your webhook's signing secret>.
          schema:
            type: string
            example: t=1793600000,v1=5f2b…
        - name: Valjean-Event-Id
          in: header
          required: true
          schema:
            type: string
        - name: Valjean-Event-Type
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Event_policy_bound"
            example:
              id: 41
              type: policy.bound
              created_at: 2026-11-02T14:20:00Z
              agency: Palmetto Coast Insurance Group
              submission_id: 6f2a1c9e-4b3d-4e8a-9c21-7d4e5f6a8b90
              data:
                policy_id: 1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d
                policy_number: MUM-GL-2026-0412
                carrier: Magnolia Underwriting Managers
                effective_date: 2026-12-15
      responses:
        "200":
          description: Received. Any 2xx works.
  policy.status_changed:
    post:
      operationId: webhook_policy_status_changed
      summary: A policy changed status (issued, cancelled, expired, renewed).
      description: |-
        A policy changed status (issued, cancelled, expired, renewed).

        POSTed to each of your webhooks subscribed to it. Reply 2xx within 8 seconds. Failures retry after 1, 5, 30, 120, 360, 720 and 1440 minutes (8 attempts, about 45 hours); a webhook is turned off after 50 failures in a row. Deliveries go out within about a minute. Use the event id to de-duplicate: a retry after a timeout can deliver the same event twice.
      tags:
        - Webhooks
      parameters:
        - name: Valjean-Signature
          in: header
          required: true
          description: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with your webhook's signing secret>.
          schema:
            type: string
            example: t=1793600000,v1=5f2b…
        - name: Valjean-Event-Id
          in: header
          required: true
          schema:
            type: string
        - name: Valjean-Event-Type
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Event_policy_status_changed"
            example:
              id: 41
              type: policy.status_changed
              created_at: 2026-11-02T14:20:00Z
              agency: Palmetto Coast Insurance Group
              submission_id: 6f2a1c9e-4b3d-4e8a-9c21-7d4e5f6a8b90
              data:
                policy_id: 1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d
                status: issued
      responses:
        "200":
          description: Received. Any 2xx works.
  document.available:
    post:
      operationId: webhook_document_available
      summary: A binder, policy or endorsement is ready to download.
      description: |-
        A binder, policy or endorsement is ready to download.

        POSTed to each of your webhooks subscribed to it. Reply 2xx within 8 seconds. Failures retry after 1, 5, 30, 120, 360, 720 and 1440 minutes (8 attempts, about 45 hours); a webhook is turned off after 50 failures in a row. Deliveries go out within about a minute. Use the event id to de-duplicate: a retry after a timeout can deliver the same event twice.
      tags:
        - Webhooks
      parameters:
        - name: Valjean-Signature
          in: header
          required: true
          description: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with your webhook's signing secret>.
          schema:
            type: string
            example: t=1793600000,v1=5f2b…
        - name: Valjean-Event-Id
          in: header
          required: true
          schema:
            type: string
        - name: Valjean-Event-Type
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Event_document_available"
            example:
              id: 41
              type: document.available
              created_at: 2026-11-02T14:20:00Z
              agency: Palmetto Coast Insurance Group
              submission_id: 6f2a1c9e-4b3d-4e8a-9c21-7d4e5f6a8b90
              data:
                document_id: 2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e
                kind: binder
                file_name: Binder MUM-GL-2026-0412.pdf
      responses:
        "200":
          description: Received. Any 2xx works.
  invoice.sent:
    post:
      operationId: webhook_invoice_sent
      summary: An invoice was sent to your agency.
      description: |-
        An invoice was sent to your agency.

        POSTed to each of your webhooks subscribed to it. Reply 2xx within 8 seconds. Failures retry after 1, 5, 30, 120, 360, 720 and 1440 minutes (8 attempts, about 45 hours); a webhook is turned off after 50 failures in a row. Deliveries go out within about a minute. Use the event id to de-duplicate: a retry after a timeout can deliver the same event twice.
      tags:
        - Webhooks
      parameters:
        - name: Valjean-Signature
          in: header
          required: true
          description: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with your webhook's signing secret>.
          schema:
            type: string
            example: t=1793600000,v1=5f2b…
        - name: Valjean-Event-Id
          in: header
          required: true
          schema:
            type: string
        - name: Valjean-Event-Type
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Event_invoice_sent"
            example:
              id: 41
              type: invoice.sent
              created_at: 2026-11-02T14:20:00Z
              agency: Palmetto Coast Insurance Group
              submission_id: 6f2a1c9e-4b3d-4e8a-9c21-7d4e5f6a8b90
              data:
                invoice_id: 3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f
                invoice_number: 1042
                amount_due: 19212.5
                due_on: 2027-01-14
      responses:
        "200":
          description: Received. Any 2xx works.
  invoice.paid:
    post:
      operationId: webhook_invoice_paid
      summary: An invoice was marked paid.
      description: |-
        An invoice was marked paid.

        POSTed to each of your webhooks subscribed to it. Reply 2xx within 8 seconds. Failures retry after 1, 5, 30, 120, 360, 720 and 1440 minutes (8 attempts, about 45 hours); a webhook is turned off after 50 failures in a row. Deliveries go out within about a minute. Use the event id to de-duplicate: a retry after a timeout can deliver the same event twice.
      tags:
        - Webhooks
      parameters:
        - name: Valjean-Signature
          in: header
          required: true
          description: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with your webhook's signing secret>.
          schema:
            type: string
            example: t=1793600000,v1=5f2b…
        - name: Valjean-Event-Id
          in: header
          required: true
          schema:
            type: string
        - name: Valjean-Event-Type
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Event_invoice_paid"
            example:
              id: 41
              type: invoice.paid
              created_at: 2026-11-02T14:20:00Z
              agency: Palmetto Coast Insurance Group
              submission_id: 6f2a1c9e-4b3d-4e8a-9c21-7d4e5f6a8b90
              data:
                invoice_id: 3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f
                invoice_number: 1042
                amount_due: 19212.5
                due_on: 2027-01-14
      responses:
        "200":
          description: Received. Any 2xx works.
  invoice.overdue:
    post:
      operationId: webhook_invoice_overdue
      summary: An invoice is past due.
      description: |-
        An invoice is past due.

        POSTed to each of your webhooks subscribed to it. Reply 2xx within 8 seconds. Failures retry after 1, 5, 30, 120, 360, 720 and 1440 minutes (8 attempts, about 45 hours); a webhook is turned off after 50 failures in a row. Deliveries go out within about a minute. Use the event id to de-duplicate: a retry after a timeout can deliver the same event twice.
      tags:
        - Webhooks
      parameters:
        - name: Valjean-Signature
          in: header
          required: true
          description: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with your webhook's signing secret>.
          schema:
            type: string
            example: t=1793600000,v1=5f2b…
        - name: Valjean-Event-Id
          in: header
          required: true
          schema:
            type: string
        - name: Valjean-Event-Type
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Event_invoice_overdue"
            example:
              id: 41
              type: invoice.overdue
              created_at: 2026-11-02T14:20:00Z
              agency: Palmetto Coast Insurance Group
              submission_id: 6f2a1c9e-4b3d-4e8a-9c21-7d4e5f6a8b90
              data:
                invoice_id: 3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f
                invoice_number: 1042
                amount_due: 19212.5
                due_on: 2027-01-14
      responses:
        "200":
          description: Received. Any 2xx works.
  webhook.ping:
    post:
      operationId: webhook_webhook_ping
      summary: Sent by POST /webhooks/{id}/test only.
      description: |-
        Sent by POST /webhooks/{id}/test only.

        POSTed to each of your webhooks subscribed to it. Reply 2xx within 8 seconds. Failures retry after 1, 5, 30, 120, 360, 720 and 1440 minutes (8 attempts, about 45 hours); a webhook is turned off after 50 failures in a row. Deliveries go out within about a minute. Use the event id to de-duplicate: a retry after a timeout can deliver the same event twice.
      tags:
        - Webhooks
      parameters:
        - name: Valjean-Signature
          in: header
          required: true
          description: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with your webhook's signing secret>.
          schema:
            type: string
            example: t=1793600000,v1=5f2b…
        - name: Valjean-Event-Id
          in: header
          required: true
          schema:
            type: string
        - name: Valjean-Event-Type
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Event_webhook_ping"
            example:
              id: 41
              type: webhook.ping
              created_at: 2026-11-02T14:20:00Z
              agency: Palmetto Coast Insurance Group
              submission_id: null
              data:
                message: Test from Valjean Specialty
      responses:
        "200":
          description: Received. Any 2xx works.
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: vjk_live_… / vjk_demo_… API key, or an OAuth access token (vjat_…)
      description: "Send the key as `Authorization: Bearer vjk_…`. Keys come from your agency portal or your Valjean broker."
    apiKeyHeader:
      type: apiKey
      in: header
      name: X-Api-Key
      description: The same API key in an X-Api-Key header, for tools that can't set Authorization.
    oauth2:
      type: oauth2
      description: "For AI apps that connect by signing in. Authorization code flow with PKCE (S256 only), public clients (no secret), dynamic client registration at https://api.valjeanspecialty.com/oauth/register. Discovery: https://api.valjeanspecialty.com/.well-known/oauth-authorization-server. Access tokens last 1 hour; refresh tokens 30 days and rotate on use. Tokens stop when the API key or portal link that approved them is revoked."
      flows:
        authorizationCode:
          authorizationUrl: https://api.valjeanspecialty.com/oauth/authorize
          tokenUrl: https://api.valjeanspecialty.com/oauth/token
          refreshUrl: https://api.valjeanspecialty.com/oauth/token
          scopes:
            read: Appetite, requirements, status, quotes, documents, policies
            submit: Also send files, answers, documents, bind requests and instant-quote asks
  schemas:
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              description: Machine-readable code, e.g. invalid_request, read_only, not_found, closed, rate_limited.
            message:
              type: string
              description: Plain-English explanation you can show a person.
            issues:
              type: array
              items:
                type: string
              description: 'Validation problems, as "field: problem" (400 invalid_request only).'
          required:
            - code
            - message
      required:
        - error
    Me:
      type: object
      properties:
        agency:
          type: string
          description: Your agency's name.
        key:
          type: string
          description: The key's name (or the connected app's name for OAuth).
        scope:
          type: string
          description: What this key may do.
          enum:
            - read
            - submit
        can_submit:
          type: boolean
          description: True when the key can submit and the agency has a signed broker agreement.
        environment:
          type: string
          description: '"live", or "demo (fictional sample data)" for a demo key.'
      required:
        - agency
        - key
        - scope
        - can_submit
        - environment
    HowToSend:
      type: object
      properties:
        email:
          type: string
          description: Where to email the risk instead.
          format: email
        include:
          type: array
          items:
            type: string
          description: What to include in that email.
        note:
          type: string
      required:
        - email
        - include
        - note
      description: "Present whenever the fit isn't \"good\": a broker reviews every submission, including ones outside the appetite on file."
    AppetiteAnswer:
      type: object
      properties:
        fit:
          type: string
          description: '"unknown" means no appetite is on file for these lines yet: fall back to your own guidance.'
          enum:
            - good
            - possible
            - unlikely
            - unknown
        appetite_on_file:
          type: boolean
        headline:
          type: string
          description: One sentence you can show as is.
        market_count:
          type: integer
          description: Markets on file that could write it, capped at 5. Market names are never returned.
        market_count_label:
          type: string
          description: e.g. "2 markets", "5+ markets", "Not on file yet".
        lines:
          type: array
          items:
            type: object
            properties:
              line:
                type: string
                description: Line code.
              fit:
                type: string
                enum:
                  - good
                  - possible
                  - unlikely
            required:
              - line
              - fit
        notes:
          type: array
          items:
            type: string
        needs:
          type: array
          items:
            type: string
          description: What Valjean will need for this risk, in plain words.
        class_matched:
          type:
            - string
            - "null"
          description: The class Valjean matched the risk to, if any.
        state:
          type: string
          description: Two-letter state code.
        unknown_lines:
          type: array
          items:
            type: string
          description: Lines in the request Valjean didn't recognise.
        how_to_send:
          $ref: "#/components/schemas/HowToSend"
      required:
        - fit
        - appetite_on_file
        - headline
        - market_count
        - market_count_label
        - lines
        - notes
        - needs
        - class_matched
        - state
    PublicAppetiteAnswer:
      allOf:
        - $ref: "#/components/schemas/AppetiteAnswer"
        - type: object
          properties:
            disclaimer:
              type: string
          required:
            - disclaimer
    AnswerSpec:
      type: object
      properties:
        type:
          type: string
          description: How to answer. "file" means upload a document instead.
          enum:
            - text
            - number
            - money
            - percent
            - yes_no
            - choice
            - date
            - file
        options:
          type: array
          items:
            type: string
          description: 'For type "choice": the allowed answers.'
      required:
        - type
    RequirementItem:
      type: object
      properties:
        key:
          type: string
          description: Answer by this key later (PATCH /submissions/{id}/answers).
        label:
          type: string
        type:
          type: string
          description: "Playbook item type: document, field, question or photo."
          x-todo: Market-only asks report "document" here even when they are questions; see level vocabulary note.
        level:
          type: string
          description: How much it matters.
          enum:
            - required
            - sometimes
            - recommended
            - some markets
          x-todo: "Three vocabularies exist (requirements: required/sometimes/recommended/some markets; needs: required/recommended; class guides: required/sometimes/helpful). Confirm the intended set."
        help:
          type:
            - string
            - "null"
        answer:
          $ref: "#/components/schemas/AnswerSpec"
      required:
        - key
        - label
        - type
        - level
        - help
        - answer
    Requirements:
      type: object
      properties:
        class_matched:
          type:
            - string
            - "null"
        state:
          type: string
        lines:
          type: array
          items:
            type: string
        items:
          type: array
          items:
            $ref: "#/components/schemas/RequirementItem"
      required:
        - class_matched
        - state
        - lines
        - items
    Need:
      type: object
      properties:
        key:
          type: string
        label:
          type: string
        type:
          type: string
          description: document, field, question, photo, or "requested" (an item your broker asked for).
        level:
          type: string
          enum:
            - required
            - recommended
        how:
          type: string
          description: '"answer", "upload a document" or "answer or upload".'
        answer:
          $ref: "#/components/schemas/AnswerSpec"
      required:
        - key
        - label
        - type
        - level
        - how
        - answer
    MarketProgress:
      type: object
      properties:
        reviewing:
          type: integer
        declined:
          type: integer
        quoted:
          type: integer
        no_response:
          type: integer
        total:
          type: integer
          description: Markets the file has been sent to.
        presented:
          type: integer
          description: Quotes your broker has presented to you.
        text:
          type: string
          description: 'One plain sentence, e.g. "With 4 markets: 3 quoted, 1 declined."'
      required:
        - reviewing
        - declined
        - quoted
        - no_response
        - total
        - presented
        - text
      description: "Anonymous counts: market names appear only on presented quotes."
    Broker:
      type: object
      properties:
        name:
          type:
            - string
            - "null"
        email:
          type: string
          format: email
      required:
        - name
        - email
    SubmissionSummary:
      type: object
      properties:
        id:
          type: string
          format: uuid
        reference:
          type: string
          description: Valjean reference, e.g. VS-000624.
        insured:
          type: string
        lines:
          type: array
          items:
            type: string
          description: Line names, e.g. "General Liability".
        effective_date:
          type:
            - string
            - "null"
          format: date
        status:
          type: string
          description: Plain-English status you can show as is.
        stage:
          type: string
          enum:
            - new
            - needs_info
            - marketing
            - quoted
            - bound
            - declined
            - lost
            - closed
        received_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - reference
        - insured
        - lines
        - effective_date
        - status
        - stage
        - received_at
        - updated_at
    Submission:
      allOf:
        - $ref: "#/components/schemas/SubmissionSummary"
        - type: object
          properties:
            files:
              type: array
              items:
                type: string
              description: File names on the submission.
            needs:
              type: array
              items:
                $ref: "#/components/schemas/Need"
              description: What's still open, each with a key. Empty once the file is closed.
            quotes_ready:
              type: integer
              description: Quotes your broker has presented.
            markets:
              $ref: "#/components/schemas/MarketProgress"
            broker:
              anyOf:
                - $ref: "#/components/schemas/Broker"
                - type: "null"
          required:
            - files
            - needs
            - quotes_ready
            - markets
            - broker
    SubmissionCreated:
      allOf:
        - $ref: "#/components/schemas/Submission"
        - type: object
          properties:
            duplicate:
              type: boolean
              description: "True when client_reference (or the Idempotency-Key header) matched an existing file: that file is returned with 200 and nothing new is created."
            status_url:
              type: string
              description: A private status page you can share with the producer. Treat it like a password. Returned when the file is created; a duplicate doesn't make another link.
              format: uri
          required:
            - duplicate
    IntakeCreated:
      allOf:
        - $ref: "#/components/schemas/SubmissionCreated"
        - type: object
          properties:
            documents:
              type: array
              items:
                $ref: "#/components/schemas/DocumentCreated"
            note:
              type: string
          required:
            - documents
    SubmissionList:
      type: object
      properties:
        total:
          type: integer
        submissions:
          type: array
          items:
            $ref: "#/components/schemas/SubmissionSummary"
      required:
        - total
        - submissions
    AnswerResult:
      type: object
      properties:
        answered:
          type: array
          items:
            type: string
          description: Keys that were recorded.
        unmatched:
          type: array
          items:
            type: string
          description: Keys not asked on this file (still saved in the file note).
        rejected:
          type: array
          items:
            type: object
            properties:
              key:
                type: string
              reason:
                type: string
            required:
              - key
              - reason
          description: "Answers that didn't fit the expected type: not saved."
        upload_instead:
          type: array
          items:
            type: string
          description: Keys that need a document upload rather than text.
        fields_filled:
          type: array
          items:
            type: string
          description: Fields that were blank and are now filled.
        fields_kept:
          type: array
          items:
            type: string
          description: "Fields Valjean already had: kept, not overwritten."
        from_message:
          type: object
          properties:
            answers:
              type: array
              items:
                type: object
                properties:
                  key:
                    type: string
                  value:
                    type: string
                required:
                  - key
                  - value
              description: Answers read from message (keys you also sent directly are left to yours).
            fields:
              type: array
              items:
                type: string
              description: Fields read from message.
            not_understood:
              type: array
              items:
                type: string
              description: Sentences or values that couldn't be mapped. Send them by key, or reword.
          required:
            - answers
            - fields
            - not_understood
          description: "Only when message was sent: what Valjean understood from it."
        note:
          type: string
        needs:
          type: array
          items:
            $ref: "#/components/schemas/Need"
          description: What's still open after these answers.
      required:
        - answered
        - unmatched
        - rejected
        - upload_instead
        - fields_filled
        - fields_kept
        - note
        - needs
    UnderwriterFlags:
      type: object
      properties:
        submission_id:
          type: string
          format: uuid
        flags:
          type: array
          items:
            type: object
            properties:
              key:
                type: string
                description: What kind of question, e.g. lump_sum_payroll, subs_insurance_unanswered, uninsured_subs, exposure_unrealistic, loss_runs_gap, loss_runs_stale, large_loss_undocumented.
              severity:
                type: string
                description: "high: underwriters usually won't quote until it's answered."
                enum:
                  - high
                  - medium
              title:
                type: string
              detail:
                type: string
                description: Why it stands out, from the facts on your file.
              question:
                type: string
                description: The question to answer ahead of time.
            required:
              - key
              - severity
              - title
              - detail
              - question
        note:
          type: string
      required:
        - submission_id
        - flags
        - note
    DocumentCreated:
      type: object
      properties:
        document_id:
          type: string
          format: uuid
        file_name:
          type: string
        doc_type:
          type: string
          description: Detected or given document type, e.g. acord_125, loss_runs, sov, other.
        note:
          type: string
        duplicate:
          type: boolean
          description: Only on /documents/complete when the upload was already recorded.
      required:
        - document_id
        - file_name
        - doc_type
    UploadUrl:
      type: object
      properties:
        upload_id:
          type: string
        upload_url:
          type: string
          description: PUT the file's bytes here.
          format: uri
        method:
          type: string
          const: PUT
        headers:
          type: object
          properties:
            Content-Type:
              type: string
          required:
            - Content-Type
        max_bytes:
          type: integer
        expires_in_seconds:
          type: integer
        then:
          type: string
          description: What to call next.
      required:
        - upload_id
        - upload_url
        - method
        - headers
        - max_bytes
        - expires_in_seconds
        - then
    Document:
      type: object
      properties:
        id:
          type: string
          format: uuid
        source:
          type: string
          description: "\"file\": the submission's documents. \"carrier\": presented quotes, binders, policies, endorsements."
          enum:
            - file
            - carrier
        kind:
          type: string
          description: Document type, or the carrier document kind (quote, binder, policy, endorsement).
        file_name:
          type: string
        size_bytes:
          type:
            - integer
            - "null"
        created_at:
          type: string
          format: date-time
      required:
        - id
        - source
        - kind
        - file_name
        - size_bytes
        - created_at
    DocumentList:
      type: object
      properties:
        documents:
          type: array
          items:
            $ref: "#/components/schemas/Document"
      required:
        - documents
    DocumentLink:
      type: object
      properties:
        file_name:
          type: string
        kind:
          type: string
        url:
          type: string
          description: "Download link. It's a credential: don't log or share it."
          format: uri
        expires_in_seconds:
          type: integer
      required:
        - file_name
        - kind
        - url
        - expires_in_seconds
    Quote:
      type: object
      properties:
        id:
          type: string
          format: uuid
        carrier:
          type: string
        lines:
          type: array
          items:
            type: string
          description: Line codes.
        premium:
          type: number
          description: Dollars.
        fees:
          type: number
          description: Policy, inspection, broker and other fees, in dollars.
        total:
          type: number
          description: Premium plus fees, in dollars. Surplus lines taxes are added at binding.
        limits:
          type:
            - string
            - "null"
        deductibles:
          type:
            - string
            - "null"
        exclusions:
          type:
            - string
            - "null"
        subjectivities:
          type:
            - string
            - "null"
        expires_on:
          type:
            - string
            - "null"
          format: date
      required:
        - id
        - carrier
        - lines
        - premium
        - fees
        - total
        - limits
        - deductibles
        - exclusions
        - subjectivities
        - expires_on
    QuoteList:
      type: object
      properties:
        submission_id:
          type: string
          format: uuid
        quotes:
          type: array
          items:
            $ref: "#/components/schemas/Quote"
        note:
          type: string
      required:
        - submission_id
        - quotes
        - note
    BindRequestCreated:
      type: object
      properties:
        bind_request_id:
          type: string
          format: uuid
        status:
          type: string
          const: preparing
        status_text:
          type: string
        quote:
          type: object
          properties:
            id:
              type: string
              format: uuid
            carrier:
              type: string
            premium:
              type: number
          required:
            - id
            - carrier
            - premium
        subjectivities:
          type:
            - string
            - "null"
        note:
          type: string
      required:
        - bind_request_id
        - status
        - status_text
        - quote
        - subjectivities
        - note
    BindRequest:
      type: object
      properties:
        id:
          type: string
          format: uuid
        quote_id:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - preparing
            - requested
            - binder_received
            - bound
            - cancelled
        status_text:
          type: string
        created_at:
          type: string
          format: date-time
        requested_at:
          type:
            - string
            - "null"
          format: date-time
        binder_received_at:
          type:
            - string
            - "null"
          format: date-time
      required:
        - id
        - quote_id
        - status
        - status_text
        - created_at
        - requested_at
        - binder_received_at
    BindRequestList:
      type: object
      properties:
        bind_requests:
          type: array
          items:
            $ref: "#/components/schemas/BindRequest"
      required:
        - bind_requests
    BindWithdrawn:
      type: object
      properties:
        bind_request_id:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - cancelled
        status_text:
          type: string
        note:
          type: string
      required:
        - bind_request_id
        - status
        - status_text
        - note
    DryRun:
      type: object
      properties:
        dry_run:
          type: boolean
          const: true
        valid:
          type: boolean
          const: true
          description: The request passed every check. A request that fails a check returns the same error it would without dry_run.
        would:
          type: string
          description: What sending it for real would do.
          enum:
            - create
            - return_existing
            - request_bind
        note:
          type: string
      required:
        - dry_run
        - valid
        - would
        - note
      description: "With ?dry_run=1: checked, nothing saved or sent. Other fields describe what would happen (the file's insured and lines, an existing file's id, or the quote and its subjectivities)."
    InstantQuotesPending:
      type: object
      properties:
        status:
          type: string
          const: pending
        status_text:
          type: string
        request_id:
          type: string
          description: Absent when you'd already asked and that ask is still pending.
          format: uuid
        markets_that_fit:
          type: integer
      required:
        - status
        - status_text
        - markets_that_fit
      description: Valjean's setting is "a broker approves each ask".
    InstantQuotesDone:
      type: object
      properties:
        status:
          type: string
          const: done
        results:
          type: array
          items:
            type: object
            properties:
              outcome:
                type: string
                description: '"blocked" means a send rule stopped it (readiness, agency, approval rules).'
                enum:
                  - quoted
                  - referred
                  - declined
                  - error
                  - blocked
              quote:
                type: object
                properties:
                  quote_id:
                    type: string
                    format: uuid
                  carrier:
                    type: string
                  premium:
                    type: number
                required:
                  - quote_id
                  - carrier
                  - premium
                description: Only when the quote could be presented to you.
              note:
                type: string
              reason:
                type: string
            required:
              - outcome
        note:
          type: string
      required:
        - status
        - results
        - note
      description: Valjean's setting is "automatic".
    InstantQuotesResult:
      oneOf:
        - $ref: "#/components/schemas/InstantQuotesPending"
        - $ref: "#/components/schemas/InstantQuotesDone"
      discriminator:
        propertyName: status
        mapping:
          pending: "#/components/schemas/InstantQuotesPending"
          done: "#/components/schemas/InstantQuotesDone"
    Event:
      type: object
      properties:
        id:
          type: integer
          description: "Cursor: pass the last one as ?after= to get only newer events."
        created_at:
          type: string
          format: date-time
        type:
          type: string
        submission_id:
          type:
            - string
            - "null"
          format: uuid
        data:
          type: object
          description: Depends on type; see the webhooks section. Status events add status_text in plain words.
      required:
        - id
        - created_at
        - type
        - submission_id
        - data
    EventList:
      type: object
      properties:
        events:
          type: array
          items:
            $ref: "#/components/schemas/Event"
        next_after:
          type:
            - integer
            - "null"
        has_more:
          type: boolean
      required:
        - events
        - next_after
        - has_more
    Webhook:
      type: object
      properties:
        id:
          type: string
          format: uuid
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            type: string
          description: Empty means every event.
        description:
          type:
            - string
            - "null"
        active:
          type: boolean
        last_success_at:
          type:
            - string
            - "null"
          format: date-time
        last_failure_at:
          type:
            - string
            - "null"
          format: date-time
        last_error:
          type:
            - string
            - "null"
        created_at:
          type: string
          format: date-time
      required:
        - id
        - url
        - events
        - description
        - active
        - last_success_at
        - last_failure_at
        - last_error
        - created_at
    WebhookList:
      type: object
      properties:
        webhooks:
          type: array
          items:
            $ref: "#/components/schemas/Webhook"
        event_types:
          type: array
          items:
            type: string
        signature_header:
          type: string
      required:
        - webhooks
        - event_types
        - signature_header
    WebhookCreated:
      type: object
      properties:
        id:
          type: string
          format: uuid
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            type: string
          description: '["(all)"] when every event.'
        secret:
          type: string
          description: Signing secret, shown once. Verify the Valjean-Signature header with it.
        note:
          type: string
      required:
        - id
        - url
        - events
        - secret
        - note
    WebhookDeleted:
      type: object
      properties:
        deleted:
          type: boolean
          const: true
      required:
        - deleted
    WebhookTest:
      type: object
      properties:
        delivered:
          type: boolean
        status:
          type: integer
          description: HTTP status your endpoint returned.
        error:
          type: string
      required:
        - delivered
    Policy:
      type: object
      properties:
        id:
          type: string
          format: uuid
        submission_id:
          type: string
          format: uuid
        insured:
          type: string
        policy_number:
          type:
            - string
            - "null"
        carrier:
          type: string
        lines:
          type: array
          items:
            type: string
        effective_date:
          type: string
          format: date
        expiration_date:
          type: string
          format: date
        status:
          type: string
          enum:
            - bound
            - issued
            - cancelled
            - expired
            - renewed
        premium:
          type: number
        fees:
          type: number
        taxes:
          type: number
          description: Surplus lines tax and stamping fee.
        total:
          type: number
        agent_commission:
          type:
            - number
            - "null"
          description: Your agency's commission on the policy, in dollars.
        policy_document:
          type: boolean
          description: True when the policy document is on file.
      required:
        - id
        - submission_id
        - insured
        - policy_number
        - carrier
        - lines
        - effective_date
        - expiration_date
        - status
        - premium
        - fees
        - taxes
        - total
        - agent_commission
        - policy_document
    PolicyList:
      type: object
      properties:
        total:
          type: integer
        policies:
          type: array
          items:
            $ref: "#/components/schemas/Policy"
      required:
        - total
        - policies
    InvoiceSummary:
      type: object
      properties:
        id:
          type: string
          format: uuid
        invoice_number:
          type: integer
        issued_on:
          type:
            - string
            - "null"
          format: date
        due_on:
          type:
            - string
            - "null"
          format: date
        amount_due:
          type: number
        status:
          type: string
          enum:
            - sent
            - paid
            - overdue
        paid_on:
          type:
            - string
            - "null"
          format: date
      required:
        - id
        - invoice_number
        - issued_on
        - due_on
        - amount_due
        - status
        - paid_on
    PolicyDetail:
      allOf:
        - $ref: "#/components/schemas/Policy"
        - type: object
          properties:
            invoices:
              type: array
              items:
                $ref: "#/components/schemas/InvoiceSummary"
            documents:
              type: array
              items:
                $ref: "#/components/schemas/Document"
          required:
            - invoices
            - documents
    Invoice:
      allOf:
        - $ref: "#/components/schemas/InvoiceSummary"
        - type: object
          properties:
            policy_id:
              type: string
              format: uuid
            policy_number:
              type:
                - string
                - "null"
            insured:
              type:
                - string
                - "null"
          required:
            - policy_id
            - policy_number
            - insured
    InvoiceList:
      type: object
      properties:
        invoices:
          type: array
          items:
            $ref: "#/components/schemas/Invoice"
      required:
        - invoices
    GuideSummary:
      type: object
      properties:
        key:
          type: string
        name:
          type: string
        class:
          type: string
        line:
          type: string
        line_name:
          type: string
        naics_prefixes:
          type: array
          items:
            type: string
        keywords:
          type: array
          items:
            type: string
      required:
        - key
        - name
        - class
        - line
        - line_name
        - naics_prefixes
        - keywords
    GuideList:
      type: object
      properties:
        guides:
          type: array
          items:
            $ref: "#/components/schemas/GuideSummary"
      required:
        - guides
    Guide:
      allOf:
        - $ref: "#/components/schemas/GuideSummary"
        - type: object
          properties:
            updated_at:
              type: string
              format: date-time
            appetite:
              type: array
              items:
                type: object
                properties:
                  state:
                    type: string
                  fit:
                    type: string
                    enum:
                      - good
                      - possible
                      - unlikely
                      - unknown
                  headline:
                    type: string
                  market_count_label:
                    type: string
                required:
                  - state
                  - fit
                  - headline
                  - market_count_label
            needs:
              type: array
              items:
                type: object
                properties:
                  key:
                    type: string
                  label:
                    type: string
                  level:
                    type: string
                    enum:
                      - required
                      - sometimes
                      - helpful
                  kind:
                    type: string
                  answer:
                    $ref: "#/components/schemas/AnswerSpec"
                  help:
                    type:
                      - string
                      - "null"
                  confirmed:
                    type: boolean
                    description: "False: Valjean's typical list, not yet confirmed with an underwriter; say \"typically\"."
                required:
                  - key
                  - label
                  - level
                  - kind
                  - answer
                  - help
                  - confirmed
            disclaimer:
              type: string
          required:
            - updated_at
            - appetite
            - needs
            - disclaimer
    WebhookEnvelope:
      type: object
      properties:
        id:
          type: integer
          description: Event id (the same cursor as GET /events).
        type:
          type: string
        created_at:
          type: string
          format: date-time
        agency:
          type: string
        submission_id:
          type:
            - string
            - "null"
          format: uuid
        data:
          type: object
      required:
        - id
        - type
        - created_at
        - agency
        - submission_id
        - data
    Event_submission_received:
      allOf:
        - $ref: "#/components/schemas/WebhookEnvelope"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              const: submission.received
            data:
              type: object
              properties:
                status:
                  type: string
                status_text:
                  type: string
              required:
                - status
                - status_text
    Event_submission_status_changed:
      allOf:
        - $ref: "#/components/schemas/WebhookEnvelope"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              const: submission.status_changed
            data:
              type: object
              properties:
                status:
                  type: string
                previous:
                  type: string
                status_text:
                  type: string
                  description: The new status in plain words.
              required:
                - status
                - previous
                - status_text
    Event_needs_requested:
      allOf:
        - $ref: "#/components/schemas/WebhookEnvelope"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              const: needs.requested
            data:
              type: object
              properties:
                info_request_id:
                  type: string
                  format: uuid
                due_on:
                  type:
                    - string
                    - "null"
                  format: date
              required:
                - info_request_id
                - due_on
    Event_markets_updated:
      allOf:
        - $ref: "#/components/schemas/WebhookEnvelope"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              const: markets.updated
            data:
              type: object
              properties:
                reviewing:
                  type: integer
                declined:
                  type: integer
                quoted:
                  type: integer
                no_response:
                  type: integer
                total:
                  type: integer
              required:
                - reviewing
                - declined
                - quoted
                - no_response
                - total
    Event_quote_presented:
      allOf:
        - $ref: "#/components/schemas/WebhookEnvelope"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              const: quote.presented
            data:
              type: object
              properties:
                quote_id:
                  type: string
                  format: uuid
                carrier:
                  type: string
                lines:
                  type: array
                  items:
                    type: string
                premium:
                  type: number
                expires_on:
                  type:
                    - string
                    - "null"
                  format: date
              required:
                - quote_id
                - carrier
                - lines
                - premium
                - expires_on
    Event_bind_status_changed:
      allOf:
        - $ref: "#/components/schemas/WebhookEnvelope"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              const: bind.status_changed
            data:
              type: object
              properties:
                bind_request_id:
                  type: string
                  format: uuid
                quote_id:
                  type: string
                  format: uuid
                status:
                  type: string
                  enum:
                    - preparing
                    - requested
                    - binder_received
                    - bound
                    - cancelled
                status_text:
                  type: string
              required:
                - bind_request_id
                - quote_id
                - status
                - status_text
    Event_policy_bound:
      allOf:
        - $ref: "#/components/schemas/WebhookEnvelope"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              const: policy.bound
            data:
              type: object
              properties:
                policy_id:
                  type: string
                  format: uuid
                policy_number:
                  type:
                    - string
                    - "null"
                carrier:
                  type: string
                effective_date:
                  type: string
                  format: date
              required:
                - policy_id
                - policy_number
                - carrier
                - effective_date
    Event_policy_status_changed:
      allOf:
        - $ref: "#/components/schemas/WebhookEnvelope"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              const: policy.status_changed
            data:
              type: object
              properties:
                policy_id:
                  type: string
                  format: uuid
                status:
                  type: string
              required:
                - policy_id
                - status
    Event_document_available:
      allOf:
        - $ref: "#/components/schemas/WebhookEnvelope"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              const: document.available
            data:
              type: object
              properties:
                document_id:
                  type: string
                  format: uuid
                kind:
                  type: string
                  enum:
                    - binder
                    - policy
                    - endorsement
                file_name:
                  type: string
              required:
                - document_id
                - kind
                - file_name
    Event_invoice_sent:
      allOf:
        - $ref: "#/components/schemas/WebhookEnvelope"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              const: invoice.sent
            data:
              type: object
              properties:
                invoice_id:
                  type: string
                  format: uuid
                invoice_number:
                  type: integer
                amount_due:
                  type: number
                due_on:
                  type:
                    - string
                    - "null"
                  format: date
              required:
                - invoice_id
                - invoice_number
                - amount_due
                - due_on
    Event_invoice_paid:
      allOf:
        - $ref: "#/components/schemas/WebhookEnvelope"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              const: invoice.paid
            data:
              type: object
              properties:
                invoice_id:
                  type: string
                  format: uuid
                invoice_number:
                  type: integer
                amount_due:
                  type: number
                due_on:
                  type:
                    - string
                    - "null"
                  format: date
              required:
                - invoice_id
                - invoice_number
                - amount_due
                - due_on
    Event_invoice_overdue:
      allOf:
        - $ref: "#/components/schemas/WebhookEnvelope"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              const: invoice.overdue
            data:
              type: object
              properties:
                invoice_id:
                  type: string
                  format: uuid
                invoice_number:
                  type: integer
                amount_due:
                  type: number
                due_on:
                  type:
                    - string
                    - "null"
                  format: date
              required:
                - invoice_id
                - invoice_number
                - amount_due
                - due_on
    Event_webhook_ping:
      allOf:
        - $ref: "#/components/schemas/WebhookEnvelope"
        - type: object
          required:
            - type
            - data
          properties:
            type:
              const: webhook.ping
            data:
              type: object
              properties:
                message:
                  type: string
              required:
                - message
  headers:
    RateLimitPolicy:
      description: "The rate limit for this key, as an IETF RateLimit-Policy: 120 requests (q) per 60-second window (w), counted across all of Valjean's servers. Over it, you get 429 with Retry-After."
      schema:
        type: string
        example: '"per-key";q=120;w=60'
  responses:
    BadRequest:
      description: Fix the request and send it again (invalid_request, invalid_json).
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: invalid_request
              message: Check the request.
              issues:
                - "insured.state: Too small: expected string to have >=2 characters"
    Unauthorized:
      description: "Key or token missing, wrong, expired or revoked (unauthorized). Don't retry: get a new key or sign in again."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: unauthorized
              message: This API key isn't valid. Ask your Valjean broker for a new one.
    Forbidden:
      description: "Not allowed: read_only (a read key tried to submit), agency_not_active (no signed broker agreement), not_offered (instant quotes are off), forbidden (API access off for the agency). Don't retry."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: read_only
              message: This key can read only. Ask your Valjean broker for a key that can submit.
    NotFound:
      description: Not found, or not your agency's (not_found). The API never says which.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: not_found
              message: Submission not found.
    Conflict:
      description: "The file's state doesn't allow it: closed, not_open, quote_expired, already_requested, not_withdrawable (the bind request already went to the market: call your broker), no_instant_market, limit, or ai_off (plain-sentence answers aren't switched on; send answers by key). Don't retry the same request."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: closed
              message: This submission is closed.
    TooLarge:
      description: "Body or file too large (too_large): JSON bodies up to 64 KB, direct uploads 4 MB, upload links 25 MB."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: too_large
              message: Files over 4 MB need an upload link.
    UnsupportedType:
      description: "File type not accepted (unsupported_type): PDF, images, Word, Excel, CSV or text."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: unsupported_type
              message: Send PDF, image, Word, Excel, CSV or text files.
    RateLimited:
      description: "Too many requests (rate_limited): 120 a minute per key, 30 a minute per IP on public endpoints, counted across all of Valjean's servers. Wait Retry-After seconds."
      headers:
        Retry-After:
          description: Seconds to wait.
          schema:
            type: integer
            example: 60
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: rate_limited
              message: Too many requests. Try again in a minute.
    ServerError:
      description: Something went wrong on Valjean's side (server_error). Safe to retry later; creates are idempotent with client_reference or an Idempotency-Key header.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: server_error
              message: Something went wrong. Try again shortly.
    Unavailable:
      description: "A service Valjean relies on didn't answer (ai_unavailable: the message couldn't be read just now). Retry shortly, or send answers by key."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: ai_unavailable
              message: Couldn't read the message just now. Try again, or send answers by key.
x-event-types:
  - submission.received
  - submission.status_changed
  - needs.requested
  - markets.updated
  - quote.presented
  - bind.status_changed
  - policy.bound
  - policy.status_changed
  - document.available
  - invoice.sent
  - invoice.paid
  - invoice.overdue
