{
  "service": "Aiden DocLens",
  "api_version": "v1",
  "authentication": "Authorization: Bearer <app key>",
  "idempotency": "Idempotency-Key on create, upload and submit. Reuse with different input returns 409.",
  "limits": {
    "files_per_dossier": 20,
    "file_mib": 25,
    "dossier_mib": 100,
    "pdf_pages": 50,
    "dossier_pages": 100,
    "page_values_are_defaults": true
  },
  "quality": "Technical success does not imply quality approval.",
  "routes": {
    "/v1/dossiers": {
      "post": {
        "summary": "Create dossier",
        "request": "JSON: external_reference (optional)",
        "idempotency_key": true
      }
    },
    "/v1/dossiers/{id}": {
      "get": {
        "summary": "Get own dossier, documents and jobs"
      }
    },
    "/v1/dossiers/{id}/documents": {
      "post": {
        "summary": "Upload one file",
        "request": "multipart/form-data: file",
        "idempotency_key": true
      }
    },
    "/v1/dossiers/{id}/jobs": {
      "post": {
        "summary": "Seal dossier and queue job",
        "request": "JSON: use_gateway (default false)",
        "idempotency_key": true,
        "success_status": 202
      }
    },
    "/v1/jobs/{id}": {
      "get": {
        "summary": "Poll status and actual progress counters"
      }
    },
    "/v1/jobs/{id}/result": {
      "get": {
        "summary": "Get result; 409 while unavailable"
      }
    },
    "/v1/jobs/{id}/artifacts/{artifactId}": {
      "get": {
        "summary": "Download authorized derived file"
      }
    },
    "/v1/jobs/{id}/retry": {
      "post": {
        "summary": "Retry recoverable attempt; keep saved inputs",
        "idempotency_key": true,
        "success_status": 202
      }
    }
  },
  "contract_version": "1.1.0",
  "recovery": {
    "location": "recovery on job status and result",
    "outcome": "Independent of legacy status: complete/partial/failed/interrupted, or queued/running. Failure never means not an order.",
    "retry": "POST /v1/jobs/{id}/retry with fresh Idempotency-Key only if recovery.retry.allowed. Retry same HTTP request with same key after network failures.",
    "successor": "Same dossier/documents/gateway mode, new job ID, immutable history; one explicit successor per parent. Different key returns 409 with existing_job_id.",
    "interrupted": "Manual review before replay; old remote work may still be running.",
    "input_errors": "Corrected inputs require a new dossier; sealed originals remain immutable.",
    "quality": "Technical completeness is separate from quality review.",
    "transport": "TLS/proxy errors may not contain this JSON contract. Poll the known job, or replay an unacknowledged POST with the SAME idempotency key.",
    "limits": "Two global Qwen requests; one heavy extraction. Retrying processes ALL inputs again and can incur additional token use."
  },
  "processing_limits": {
    "defaults": {
      "max_pdf_pages": 50,
      "max_dossier_pages": 100,
      "max_qwen_requests": 1000
    },
    "current": "GET /service/limits",
    "management": "GET/PUT /admin/api/settings; admin bearer required",
    "job_snapshot": "processing_limits is frozen at job creation, including queued jobs. Retry creates a new snapshot. Idempotent replay retains the original snapshot.",
    "concurrency": "gateway_max_inflight remains live, shared across all jobs; default 2.",
    "validation": "PDF 1–500, dossier 1–2000, requests 1–10000; PDF limit cannot exceed dossier limit. Page limits are checked during processing.",
    "legacy": "Existing jobs migrate to previous defaults; old stored results may omit processing_limits."
  }
}
