{
  "openapi": "3.0.3",
  "info": {
    "title": "DocExtract API",
    "version": "1.0.0",
    "description": "Turn unstructured document text into clean, typed JSON. Send an invoice, resume, contract, meeting notes, or any document as plain text and receive structured data back. No OCR setup, no template configuration.\n\nThe default server and API-key security scheme in this specification describe direct-origin access. The Business request example documents the body format, not an entitlement obtainable with that direct key. For Business postprocessing, use the RapidAPI hostname and subscriber headers in the linked guide; generated clients and Try it tools need that marketplace routing/authentication instead.",
    "contact": {
      "name": "Ainception",
      "url": "https://ainception.ca"
    }
  },
  "servers": [
    {
      "url": "https://ainception.ca/api/docextract",
      "description": "Direct-origin API; its API key alone does not enable Business postprocessing"
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "operationId": "getHealth",
        "summary": "Health check",
        "description": "Returns service status. No authentication required.",
        "responses": {
          "200": {
            "description": "Service is up",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                },
                "example": {
                  "status": "ok",
                  "timestamp": "2026-09-07T23:51:08Z",
                  "service": "docextract"
                }
              }
            }
          }
        }
      }
    },
    "/extract": {
      "post": {
        "operationId": "extractDocument",
        "summary": "Extract structured data from document text",
        "description": "Send document text and a document_type. Returns a typed JSON object with the fields relevant to that document type. Absent fields are omitted. Business (RapidAPI Mega) requests can receive deterministic post-extraction correction and optional normalization. Eligibility requires trusted marketplace subscription information; a direct API key alone does not qualify. The schema describes the expected request/result, not an extraction-accuracy guarantee. A correction audit can contain a normalization rejection even when HTTP status is 200. See the Business guide.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExtractRequest"
              },
              "examples": {
                "invoice": {
                  "summary": "Invoice",
                  "value": {
                    "document_type": "invoice",
                    "content": "INVOICE #A-2291\nAcme Widgets Ltd\n55 Main St, Calgary AB\nDate: 2026-08-14  Due: 2026-09-13\n2x Gadget @ 40.00 = 80.00\n1x Install = 120.00\nSubtotal: 200.00\nGST 5%: 10.00\nTotal: 210.00 CAD\nTerms: Net 30"
                  }
                },
                "resume": {
                  "summary": "Resume",
                  "value": {
                    "document_type": "resume",
                    "content": "Jane Okafor - Senior Data Engineer\njane.okafor@example.com | +1-403-555-0142 | Calgary, AB\nSkills: Python, Spark, dbt, AWS, Airflow\nExperience:\n- Data Engineer, Nimbus Corp, 2021-2026: built the lakehouse, cut ETL cost 40%\nEducation: BSc Computer Science, University of Alberta, 2019"
                  }
                },
                "business": {
                  "summary": "Business normalization (requires eligible RapidAPI Mega route)",
                  "value": {
                    "document_type": "invoice",
                    "content": "INVOICE INV-1001\nVendor: ACME Ltd.\nInvoice date: 2026-09-18\nDue date: 2026-10-18\n2 Gadget at 100.00 each\nSubtotal: 200.00\nTax: 10.00\nTotal: 210.00 CAD",
                    "normalization": {
                      "date_format": {
                        "invoice_date": "US",
                        "due_date": "ISO"
                      },
                      "numeric_format": {
                        "total_amount": "CURRENCY",
                        "line_items[0].unit_price": "NUMBER"
                      },
                      "text_substitute": {
                        "vendor_name": {
                          "from": "Ltd.",
                          "to": "Limited"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Extraction succeeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtractResponse"
                },
                "example": {
                  "success": true,
                  "document_type": "invoice",
                  "engine": "groq",
                  "processing_ms": 1355,
                  "data": {
                    "vendor_name": "Acme Widgets Ltd",
                    "invoice_number": "A-2291",
                    "invoice_date": "2026-08-14",
                    "due_date": "2026-09-13",
                    "subtotal": 200.0,
                    "tax_amount": 10.0,
                    "total_amount": 210.0,
                    "currency": "CAD",
                    "line_items": [
                      {
                        "description": "Gadget",
                        "quantity": 2,
                        "unit_price": 40.0,
                        "total": 80.0
                      },
                      {
                        "description": "Install",
                        "quantity": 1,
                        "unit_price": 120.0,
                        "total": 120.0
                      }
                    ],
                    "payment_terms": "Net 30"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid content / JSON",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown path or method under /api/docextract (for example a mistyped /extract or a non-POST request). The extraction endpoint is POST /extract.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Unknown endpoint. Use POST /extract",
                  "code": 404
                }
              }
            }
          },
          "413": {
            "description": "Request body exceeds 64 KB or content field exceeds 16000 characters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (20 requests / 60s)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "Extraction timed out",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Upstream model error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "externalDocs": {
          "description": "Business (RapidAPI Mega) feature guide",
          "url": "https://ainception.ca/docextract/business/"
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      }
    },
    "schemas": {
      "Health": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "ok"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          },
          "service": {
            "type": "string",
            "example": "docextract"
          }
        }
      },
      "ExtractRequest": {
        "type": "object",
        "required": [
          "content"
        ],
        "properties": {
          "content": {
            "type": "string",
            "description": "Document text to extract from. Max 16000 characters; requests exceeding this limit receive HTTP 413.",
            "maxLength": 16000
          },
          "document_type": {
            "type": "string",
            "description": "Which extraction template to apply.",
            "enum": [
              "invoice",
              "resume",
              "meeting_notes",
              "contract",
              "general"
            ],
            "default": "general"
          },
          "normalization": {
            "$ref": "#/components/schemas/Normalization"
          }
        }
      },
      "ExtractResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "data": {
            "type": "object",
            "description": "Extracted fields. Shape depends on document_type; absent fields are omitted. This is the expected object shape. Model output is not schema-enforced: non-JSON output may be returned as raw_text with a warning, and unexpected JSON types can cause correction to be skipped.",
            "additionalProperties": true
          },
          "document_type": {
            "type": "string"
          },
          "engine": {
            "type": "string",
            "description": "Which model produced the result."
          },
          "processing_ms": {
            "type": "integer"
          },
          "warning": {
            "type": "string",
            "description": "Present only when model output was non-JSON."
          },
          "correction": {
            "$ref": "#/components/schemas/Correction"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": false
          },
          "error": {
            "type": "string"
          },
          "code": {
            "type": "integer"
          },
          "detail": {
            "type": "string",
            "description": "Human-readable explanation of the error, naming the offending field or limit."
          }
        }
      },
      "Normalization": {
        "type": "object",
        "additionalProperties": false,
        "description": "Optional Business-only post-extraction rules. Correction precedes normalization. Paths are relative to data (without the data. prefix), case-sensitive, at most 200 characters, and support dotted keys and zero-based array indexes, not wildcards. At most 20 rules total across sections and 2048 compact UTF-8 JSON bytes. An exact path may have only one operation. Missing paths and unsuitable values can be no-ops. An invalid object normally rejects all normalization with a normalization_config audit entry, not HTTP 400; nonobjects are ignored by the HTTP handler. Unexpected postprocessing errors preserve the original extraction.",
        "properties": {
          "date_format": {
            "$ref": "#/components/schemas/DateFormatRules"
          },
          "numeric_format": {
            "$ref": "#/components/schemas/NumericFormatRules"
          },
          "text_substitute": {
            "$ref": "#/components/schemas/TextSubstitutionRules"
          }
        }
      },
      "DateFormatRules": {
        "type": "object",
        "maxProperties": 20,
        "description": "Paths must end in date, invoice_date, due_date, effective_date, expiration_date, or next_meeting. Values must parse as ISO dates after OCR correction. ISO=YYYY-MM-DD; US=MM/DD/YYYY; EU=DD-MM-YYYY; UK=DD/MM/YYYY; YYYYMMDD=compact date.",
        "additionalProperties": {
          "type": "string",
          "enum": [
            "ISO",
            "US",
            "EU",
            "UK",
            "YYYYMMDD"
          ]
        }
      },
      "NumericFormatRules": {
        "type": "object",
        "maxProperties": 20,
        "description": "Paths must end in subtotal, tax_amount, total_amount, value, quantity, unit_price, or total. DECIMAL returns a decimal string; NUMBER a JSON number; CURRENCY a string with two fractional digits, without a currency symbol or exchange conversion. Validate precision and numeric range.",
        "additionalProperties": {
          "type": "string",
          "enum": [
            "DECIMAL",
            "NUMBER",
            "CURRENCY"
          ]
        }
      },
      "TextSubstitutionRules": {
        "type": "object",
        "maxProperties": 20,
        "description": "Exact paths to string values. Replace all literal, case-sensitive matches at each path. No regex or fuzzy matching. Control characters and certain prompt/code-like patterns are rejected.",
        "additionalProperties": {
          "$ref": "#/components/schemas/TextSubstitution"
        }
      },
      "TextSubstitution": {
        "type": "object",
        "required": [
          "from",
          "to"
        ],
        "additionalProperties": false,
        "properties": {
          "from": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "to": {
            "type": "string",
            "maxLength": 100
          }
        }
      },
      "Correction": {
        "type": "object",
        "required": [
          "applied",
          "method",
          "changes"
        ],
        "description": "Present after a completed eligible Business pass. May be absent when ineligible or when postprocessing is skipped after an exception. Not an accuracy or successful-normalization guarantee.",
        "properties": {
          "applied": {
            "type": "boolean",
            "description": "True when at least one change actually mutated data (before != after). Advisory-only entries (where before == after) do not set this true. A normalization_config rejection still sets this true. Note: changes can be nonempty while applied is false (advisory-only entries are present in changes but do not set applied); consumers should iterate changes checking the advisory flag rather than gating on applied."
          },
          "method": {
            "type": "string",
            "enum": [
              "deterministic_ocr_and_normalization"
            ]
          },
          "changes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CorrectionChange"
            },
            "description": "All OCR entries precede normalization entries. Rules that produce no suggestion at all create no entry; note that an advisory identifier entry IS created even though before == after, because it carries a suggested correction that was deliberately not applied."
          }
        }
      },
      "CorrectionChange": {
        "type": "object",
        "required": [
          "path",
          "before",
          "after",
          "method",
          "reason"
        ],
        "properties": {
          "path": {
            "type": "string",
            "description": "Path relative to data, or normalization_config for a rejected rule object."
          },
          "before": {
            "description": "Value before this operation; normalization sees the post-OCR value. A rejection records the supplied configuration."
          },
          "after": {
            "description": "Value after this operation; null for a normalization_config rejection.",
            "nullable": true
          },
          "method": {
            "type": "string",
            "enum": [
              "deterministic_ocr",
              "normalization"
            ]
          },
          "reason": {
            "type": "string"
          },
          "advisory": {
            "type": "boolean",
            "description": "Present only for identifier OCR advisory entries. When true, this entry is advisory-only: the original value was preserved (before == after) and suggested contains the proposed correction.",
            "default": false
          },
          "suggested": {
            "type": "string",
            "description": "Present only when advisory is true. The proposed correction that was considered but not applied."
          }
        }
      }
    }
  },
  "externalDocs": {
    "description": "Business (RapidAPI Mega) feature guide",
    "url": "https://ainception.ca/docextract/business/"
  }
}
