Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Valjean Specialty agent Documents 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: Documents
description: Upload and download documents.
paths:
/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-02 14:15:12+00:00
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\" \\\n -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\", {\n method: \"GET\",\n headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, },\n});\nconsole.log(await res.json());"
- lang: python
label: Python
source: "import os, requests\n\nres = requests.get(\n \"https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/documents\",\n headers={\"Authorization\": f\"Bearer {os.environ['VALJEAN_KEY']}\"},\n)\nprint(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\" \\\n -H \"Authorization: Bearer $VALJEAN_KEY\" \\\n -F \"file=@loss-runs.pdf;type=application/pdf\""
- lang: javascript
label: Node
source: "import fs from \"node:fs\";\n\nconst form = new FormData();\nform.append(\"file\", new Blob([await fs.promises.readFile(\"loss-runs.pdf\")], { type: \"application/pdf\" }), \"loss-runs.pdf\");\nconst res = await fetch(\"https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/documents\", {\n method: \"POST\",\n headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}` },\n body: form,\n});\nconsole.log(await res.json());"
- lang: python
label: Python
source: "import os, requests\n\nres = requests.post(\n \"https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/documents\",\n headers={\"Authorization\": f\"Bearer {os.environ['VALJEAN_KEY']}\"},\n files={\"file\": (\"loss-runs.pdf\", open(\"loss-runs.pdf\", \"rb\"), \"application/pdf\")},\n)\nprint(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\" \\\n -H \"Authorization: Bearer $VALJEAN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -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\", {\n method: \"POST\",\n headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n \"file_name\": \"SOV.xlsx\",\n \"content_type\": \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\"\n }),\n});\nconsole.log(await res.json());"
- lang: python
label: Python
source: "import os, requests\n\nres = requests.post(\n \"https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/documents/upload-url\",\n headers={\"Authorization\": f\"Bearer {os.environ['VALJEAN_KEY']}\"},\n json={\n \"file_name\": \"SOV.xlsx\",\n \"content_type\": \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\"\n },\n)\nprint(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\" \\\n -H \"Authorization: Bearer $VALJEAN_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -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\", {\n method: \"POST\",\n headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n \"upload_id\": \"ed46fd9f-afa9-4f32-b134-5fb1abddcbdc-SOV.xlsx\",\n \"file_name\": \"SOV.xlsx\",\n \"content_type\": \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\"\n }),\n});\nconsole.log(await res.json());"
- lang: python
label: Python
source: "import os, requests\n\nres = requests.post(\n \"https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/documents/complete\",\n headers={\"Authorization\": f\"Bearer {os.environ['VALJEAN_KEY']}\"},\n json={\n \"upload_id\": \"ed46fd9f-afa9-4f32-b134-5fb1abddcbdc-SOV.xlsx\",\n \"file_name\": \"SOV.xlsx\",\n \"content_type\": \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\"\n },\n)\nprint(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\" \\\n -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\", {\n method: \"GET\",\n headers: { Authorization: `Bearer ${process.env.VALJEAN_KEY}`, },\n});\nconsole.log(await res.json());"
- lang: python
label: Python
source: "import os, requests\n\nres = requests.get(\n \"https://api.valjeanspecialty.com/api/agent/v1/submissions/SUBMISSION_ID/documents/DOCUMENT_ID\",\n headers={\"Authorization\": f\"Bearer {os.environ['VALJEAN_KEY']}\"},\n)\nprint(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
components:
responses:
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.
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.
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.
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.
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'
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.
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.
schemas:
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
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
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
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
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
DocumentList:
type: object
properties:
documents:
type: array
items:
$ref: '#/components/schemas/Document'
required:
- documents
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'
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
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