{
  "openapi": "3.0.3",
  "info": {
    "title": "PerfectParser Public API",
    "description": "Welcome to the PerfectParser Developer API documentation. You can use our API to programmatically create and configure Parsers, submit documents for bulk extraction, check extraction status, and retrieve structured results. We also support outbound webhooks to deliver parsed data directly to your server in real-time.",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://api.perfectparser.com",
      "description": "Production API Server"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "paths": {
    "/v1/parsers": {
      "get": {
        "summary": "List Parsers",
        "description": "Retrieve a list of all custom document parsers configured in your workspace.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": { "type": "integer", "default": 20 },
            "description": "Number of results per page (max 100)."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": { "type": "string" },
            "description": "Pagination cursor from previous response."
          }
        ],
        "responses": {
          "200": {
            "description": "A list of parsers.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "parsers": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Parser"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  },
                  "required": ["parsers", "pagination"]
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a Parser",
        "description": "Programmatically define a new custom document parser, detailing its name, description, result mode, and optionally predefined fields.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "The user-facing name of the parser.",
                    "example": "API Invoice Parser"
                  },
                  "description": {
                    "type": "string",
                    "description": "Optional details about what this parser processes.",
                    "example": "Extracts vendor, line items, and totals from invoices."
                  },
                  "result_mode": {
                    "type": "string",
                    "enum": ["one_per_file", "one_per_page"],
                    "default": "one_per_file",
                    "description": "How the results are structured. one_per_file groups extraction into a single object, one_per_page separates extraction per page."
                  },
                  "fields": {
                    "type": "array",
                    "description": "Predefined schema fields to configure parser manually.",
                    "items": {
                      "$ref": "#/components/schemas/FieldDefinition"
                    }
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          },
          "responses": {
            "201": {
              "description": "Parser created successfully.",
              "content": {
                "application/json": {
                  "schema": {
                    "$ref": "#/components/schemas/ParserWrapped"
                  }
                }
              }
            },
            "400": {
              "description": "Invalid request body parameters.",
              "content": {
                "application/json": {
                  "schema": { "$ref": "#/components/schemas/ErrorResponse" }
                }
              }
            },
            "401": {
              "description": "Invalid or missing API key.",
              "content": {
                "application/json": {
                  "schema": { "$ref": "#/components/schemas/ErrorResponse" }
                }
              }
            },
            "409": {
              "description": "Parser with this name already exists.",
              "content": {
                "application/json": {
                  "schema": { "$ref": "#/components/schemas/ErrorResponse" }
                }
              }
            }
          }
        }
      }
    },
    "/v1/parsers/{id}": {
      "get": {
        "summary": "Retrieve Parser Detail",
        "description": "Retrieve configuration details, result mode, and JSON Schema of a specific parser.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The unique parser ID.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Parser details retrieved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ParserWrapped"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "404": {
            "description": "Parser not found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update a Parser",
        "description": "Update specific fields of a parser configuration (e.g. name, description, result mode, or fields).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The unique parser ID.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "example": "Updated Parser Name"
                  },
                  "description": {
                    "type": "string",
                    "example": "Updated description."
                  },
                  "result_mode": {
                    "type": "string",
                    "enum": ["one_per_file", "one_per_page"]
                  },
                  "fields": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/FieldDefinition"
                    }
                  }
                }
              }
            }
          },
          "responses": {
            "200": {
              "description": "Parser updated successfully.",
              "content": {
                "application/json": {
                  "schema": {
                    "$ref": "#/components/schemas/ParserWrapped"
                  }
                }
              }
            },
            "400": {
              "description": "Invalid request parameters.",
              "content": {
                "application/json": {
                  "schema": { "$ref": "#/components/schemas/ErrorResponse" }
                }
              }
            },
            "401": {
              "description": "Invalid or missing API key.",
              "content": {
                "application/json": {
                  "schema": { "$ref": "#/components/schemas/ErrorResponse" }
                }
              }
            },
            "404": {
              "description": "Parser not found.",
              "content": {
                "application/json": {
                  "schema": { "$ref": "#/components/schemas/ErrorResponse" }
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a Parser",
        "description": "Permanently soft-delete a parser configuration from your account.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The unique parser ID.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Parser deleted successfully."
          },
          "401": {
            "description": "Invalid or missing API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "404": {
            "description": "Parser not found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/v1/parsers/{id}/detect-fields": {
      "post": {
        "summary": "Auto Detect Schema Fields",
        "description": "Triggers AI field detection from a sample document. Mode A: upload file via multipart/form-data. Mode B: reference existing sample_file_id via JSON. Charges 1 credit per page.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The unique parser ID.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": { "type": "string", "format": "binary", "description": "Sample PDF or image (Mode A)." },
                  "prompt": { "type": "string" },
                  "replace_existing_schema": { "type": "string", "enum": ["true", "false"] }
                },
                "required": ["file"]
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sample_file_id": {
                    "type": "string",
                    "description": "Existing sample file ID from a previous detect-fields call or GET parser.",
                    "example": "smp_123"
                  },
                  "prompt": { "type": "string" },
                  "replace_existing_schema": { "type": "boolean", "default": false }
                },
                "required": ["sample_file_id"]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Fields detected.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "parser_id": { "type": "string", "example": "prs_999" },
                    "sample_file_id": { "type": "string", "example": "smp_123" },
                    "schema": { "type": "object" },
                    "credits_used": { "type": "integer", "example": 3 }
                  },
                  "required": ["parser_id", "sample_file_id", "schema", "credits_used"]
                }
              }
            }
          },
          "400": {
            "description": "No sample file uploaded or schema already exists.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "401": {
            "description": "Invalid API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "402": {
            "description": "Insufficient credits.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "404": {
            "description": "Parser not found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/v1/extractions": {
      "get": {
        "summary": "List Extractions",
        "description": "Retrieve a list of extraction batches.",
        "parameters": [
          { "name": "parser_id", "in": "query", "schema": { "type": "string" } },
          { "name": "status", "in": "query", "schema": { "type": "string", "enum": ["queued", "pending", "processing", "completed", "failed", "partial"] } },
          { "name": "since", "in": "query", "schema": { "type": "string", "format": "date-time" }, "description": "Return extractions updated after this ISO 8601 timestamp." },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50 } },
          { "name": "cursor", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "List of extractions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "extractions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ExtractionListItem"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  },
                  "required": ["extractions", "pagination"]
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Submit Documents for Extraction",
        "description": "Queue one or more files for structured data extraction. Credits are deducted based on total page counts.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "parser_id": { "type": "string", "description": "Parser ID to process files." },
                  "files": {
                    "type": "array",
                    "items": { "type": "string", "format": "binary" },
                    "description": "List of files to extract."
                  }
                },
                "required": ["parser_id", "files"]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Submission accepted and queued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "extraction_id": { "type": "string", "example": "ext_123" },
                    "parser_id": { "type": "string", "example": "prs_999" },
                    "status": { "type": "string", "example": "queued" },
                    "file_count": { "type": "integer", "example": 2 },
                    "page_count": { "type": "integer", "example": 7 },
                    "credits_used": { "type": "integer", "example": 7 },
                    "credits_remaining": { "type": "integer", "example": 43 },
                    "documents": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "document_id": { "type": "string", "example": "doc_abc" },
                          "filename": { "type": "string", "example": "doc1.pdf" },
                          "page_count": { "type": "integer", "example": 3 },
                          "credits_used": { "type": "integer", "example": 3 },
                          "status": { "type": "string", "example": "queued" }
                        },
                        "required": ["document_id", "filename", "page_count", "credits_used", "status"]
                      }
                    }
                  },
                  "required": ["extraction_id", "parser_id", "status", "file_count", "page_count", "credits_used", "credits_remaining", "documents"]
                }
              }
            }
          },
          "400": {
            "description": "Unsupported file or parser not configured.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "401": {
            "description": "Invalid API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "402": {
            "description": "Insufficient credits.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/v1/extractions/{extraction_id}": {
      "get": {
        "summary": "Get Extraction Status",
        "description": "Get current status, total pages, and document summary details of a submitted extraction batch.",
        "parameters": [
          { "name": "extraction_id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Extraction status details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Extraction"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "404": {
            "description": "Extraction not found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/v1/extractions/{extraction_id}/results": {
      "get": {
        "summary": "Get Extraction Results",
        "description": "Retrieve full extracted JSON data for all documents completed in the extraction batch.",
        "parameters": [
          { "name": "extraction_id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Extraction results payload.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtractionResults"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "404": {
            "description": "Extraction not found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/v1/extractions/{extraction_id}/export": {
      "get": {
        "summary": "Export Extraction Batch Results",
        "description": "Download extraction results formatted as JSON, CSV, or Excel.",
        "parameters": [
          { "name": "extraction_id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "format", "in": "query", "required": true, "schema": { "type": "string", "enum": ["json", "csv", "xlsx"] } }
        ],
        "responses": {
          "200": {
            "description": "Formatted file download.",
            "content": {
              "application/octet-stream": {}
            }
          },
          "401": {
            "description": "Invalid API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "404": {
            "description": "Extraction not found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/v1/extractions/{extraction_id}/documents/{document_id}": {
      "get": {
        "summary": "Get Document",
        "description": "Retrieve full metadata and extracted data for a single document within an extraction batch. Both path IDs must match the stored parent-child relationship.",
        "parameters": [
          { "name": "extraction_id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "document_id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Document details with extracted data.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "document": {
                      "$ref": "#/components/schemas/DocumentDetail"
                    }
                  },
                  "required": ["document"]
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "404": {
            "description": "Extraction or document not found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/v1/extractions/{extraction_id}/documents/{document_id}/export": {
      "get": {
        "summary": "Export Document Results",
        "description": "Download the structured result data of a single document within an extraction batch in JSON, CSV, or Excel.",
        "parameters": [
          { "name": "extraction_id", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "document_id", "in": "path", "required": true, "schema": { "type": "string" } },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "enum": ["json", "csv", "xlsx"], "default": "json" }
          }
        ],
        "responses": {
          "200": {
            "description": "File download.",
            "content": {
              "application/octet-stream": {}
            }
          },
          "401": {
            "description": "Invalid API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "404": {
            "description": "Extraction or document not found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/v1/me": {
      "get": {
        "summary": "Get Account",
        "description": "Returns identity and credit balance for the authenticated API key.",
        "responses": {
          "200": {
            "description": "Account details.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user": {
                      "type": "object",
                      "properties": {
                        "user_id": { "type": "string" },
                        "email": { "type": "string" },
                        "name": { "type": "string", "nullable": true },
                        "credits_available": { "type": "integer" },
                        "key_name": { "type": "string", "nullable": true },
                        "key_expires_at": { "type": "string", "format": "date-time", "nullable": true },
                        "member_since": { "type": "string", "format": "date-time" }
                      },
                      "required": ["user_id", "email", "credits_available", "member_since"]
                    }
                  },
                  "required": ["user"]
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/v1/credits": {
      "get": {
        "summary": "Get Credits",
        "description": "Returns credit balance and active purchase summary.",
        "responses": {
          "200": {
            "description": "Credit balance.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "credits": {
                      "type": "object",
                      "properties": {
                        "available": { "type": "integer" },
                        "purchases": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "purchase_id": { "type": "string" },
                              "plan": { "type": "string" },
                              "credits": { "type": "integer" },
                              "remaining": { "type": "integer" },
                              "expires_at": { "type": "string", "format": "date-time", "nullable": true },
                              "purchased_at": { "type": "string", "format": "date-time" }
                            }
                          }
                        }
                      },
                      "required": ["available", "purchases"]
                    }
                  },
                  "required": ["credits"]
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks": {
      "get": {
        "summary": "List Webhooks",
        "description": "Retrieve list of active outbound webhook subscriptions with cursor pagination.",
        "parameters": [
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 20 } },
          { "name": "cursor", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "A list of webhooks.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhooks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Webhook"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  },
                  "required": ["webhooks", "pagination"]
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create Webhook Subscription",
        "description": "Subscribe to real-time events. Perfect for syncing directly to Zapier or no-code platforms.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": { "type": "string", "format": "uri", "description": "Callback HTTP POST URL." },
                  "events": {
                    "type": "array",
                    "items": { "type": "string", "enum": ["extraction.document_completed", "extraction.job_completed"] },
                    "description": "Events to subscribe to."
                  },
                  "secret": { "type": "string", "description": "Optional static token sent in X-Webhook-Secret header (min 8 chars)." }
                },
                "required": ["url", "events"]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook registered.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhook": {
                      "allOf": [
                        { "$ref": "#/components/schemas/Webhook" },
                        {
                          "type": "object",
                          "properties": {
                            "signing_secret": {
                              "type": "string",
                              "description": "HMAC signing secret — returned once on create."
                            }
                          },
                          "required": ["signing_secret"]
                        }
                      ]
                    }
                  },
                  "required": ["webhook"]
                }
              }
            }
          },
          "400": {
            "description": "Invalid payload parameters.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "401": {
            "description": "Invalid API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}": {
      "get": {
        "summary": "Get Webhook Detail",
        "description": "Retrieve configuration of a webhook subscription, including recent callback logs.",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Webhook detail with delivery attempts list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhook_id": { "type": "string" },
                    "url": { "type": "string" },
                    "events": { "type": "array", "items": { "type": "string" } },
                    "is_active": { "type": "boolean" },
                    "created_at": { "type": "string", "format": "date-time" },
                    "recent_deliveries": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "delivery_id": { "type": "string" },
                          "event": { "type": "string" },
                          "status": { "type": "string" },
                          "status_code": { "type": "integer", "nullable": true },
                          "retry_count": { "type": "integer" },
                          "attempted_at": { "type": "string", "format": "date-time" }
                        },
                        "required": ["delivery_id", "event", "status", "retry_count", "attempted_at"]
                      }
                    }
                  },
                  "required": ["webhook_id", "url", "events", "is_active", "created_at", "recent_deliveries"]
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "404": {
            "description": "Webhook subscription not found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update Webhook Subscription",
        "description": "Partially update URL, events, or active status.",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": { "type": "string", "format": "uri" },
                  "events": {
                    "type": "array",
                    "items": { "type": "string", "enum": ["extraction.document_completed", "extraction.job_completed"] }
                  },
                  "is_active": { "type": "boolean" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook updated.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "webhook": { "$ref": "#/components/schemas/Webhook" }
                  },
                  "required": ["webhook"]
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "404": {
            "description": "Webhook subscription not found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete Webhook Subscription",
        "description": "Disable/deactivate the webhook. No further callbacks will be triggered.",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "204": {
            "description": "Webhook disabled successfully."
          },
          "401": {
            "description": "Invalid API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "404": {
            "description": "Webhook subscription not found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}/test": {
      "post": {
        "summary": "Test Webhook Delivery",
        "description": "Sends a synthetic extraction.job_completed payload with test: true to verify endpoint configuration.",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Test delivery result.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "test_result": {
                      "type": "object",
                      "properties": {
                        "delivered": { "type": "boolean" },
                        "status_code": { "type": "integer", "nullable": true },
                        "response_time_ms": { "type": "integer", "nullable": true },
                        "error": { "type": "string", "nullable": true }
                      },
                      "required": ["delivered"]
                    }
                  },
                  "required": ["test_result"]
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "404": {
            "description": "Webhook not found.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Your API access key (e.g. `pp_live_...`)."
      }
    },
    "schemas": {
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": { "type": "string", "example": "PARSER_NOT_FOUND" },
              "message": { "type": "string", "example": "The requested parser does not exist." },
              "status": { "type": "integer", "example": 404 },
              "details": { "type": "object", "nullable": true }
            },
            "required": ["code", "message", "status"]
          }
        },
        "required": ["error"]
      },
      "FieldDefinition": {
        "type": "object",
        "properties": {
          "name": { "type": "string", "example": "vendor_name" },
          "type": { "type": "string", "enum": ["string", "number", "boolean", "date", "array", "object"], "example": "string" },
          "description": { "type": "string", "example": "The name of the vendor" },
          "required": { "type": "boolean", "example": false }
        },
        "required": ["name"]
      },
      "Parser": {
        "type": "object",
        "properties": {
          "parser_id": { "type": "string", "example": "prs_999" },
          "name": { "type": "string", "example": "Invoice Parser" },
          "description": { "type": "string", "nullable": true, "example": "Extracts fields from billing sheets" },
          "result_mode": { "type": "string", "example": "one_per_file" },
          "schema": { "type": "object", "nullable": true, "description": "Underlying JSON Schema" },
          "fields": { "type": "object", "nullable": true, "description": "JSON metadata map of fields" },
          "has_sample_file": { "type": "boolean", "example": true },
          "sample_file_id": { "type": "string", "nullable": true, "example": "smp_123" },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        },
        "required": ["parser_id", "name", "result_mode", "has_sample_file", "created_at", "updated_at"]
      },
      "ParserWrapped": {
        "type": "object",
        "properties": {
          "parser": {
            "$ref": "#/components/schemas/Parser"
          }
        },
        "required": ["parser"]
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "limit": { "type": "integer", "example": 20 },
          "has_next_page": { "type": "boolean", "example": false },
          "next_cursor": { "type": "string", "nullable": true, "example": "prs_xyz" }
        },
        "required": ["limit", "has_next_page", "next_cursor"]
      },
      "ExtractionListItem": {
        "type": "object",
        "properties": {
          "extraction_id": { "type": "string", "example": "ext_123" },
          "parser_id": { "type": "string", "example": "prs_999" },
          "parser_name": { "type": "string", "example": "Invoice Parser" },
          "status": { "type": "string", "example": "completed" },
          "file_count": { "type": "integer", "example": 3 },
          "page_count": { "type": "integer", "example": 10 },
          "credits_used": { "type": "integer", "example": 10 },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" },
          "completed_at": { "type": "string", "format": "date-time", "nullable": true },
          "document_summary": {
            "type": "object",
            "properties": {
              "total": { "type": "integer", "example": 3 },
              "completed": { "type": "integer", "example": 3 },
              "failed": { "type": "integer", "example": 0 },
              "processing": { "type": "integer", "example": 0 }
            },
            "required": ["total", "completed", "failed", "processing"]
          }
        },
        "required": [
          "extraction_id", "parser_id", "parser_name", "status",
          "file_count", "page_count", "credits_used", "created_at", "updated_at", "document_summary"
        ]
      },
      "ExtractionDetailDocument": {
        "type": "object",
        "properties": {
          "document_id": { "type": "string", "example": "doc_abc" },
          "filename": { "type": "string", "example": "invoice.pdf" },
          "status": { "type": "string", "example": "completed" },
          "page_count": { "type": "integer", "example": 3 },
          "credits_used": { "type": "integer", "example": 3 },
          "error_message": { "type": "string", "nullable": true, "example": null },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        },
        "required": ["document_id", "filename", "status", "page_count", "credits_used", "created_at", "updated_at"]
      },
      "Extraction": {
        "type": "object",
        "properties": {
          "extraction_id": { "type": "string", "example": "ext_123" },
          "parser_id": { "type": "string", "example": "prs_999" },
          "parser_name": { "type": "string", "example": "Invoice Parser" },
          "status": { "type": "string", "example": "completed" },
          "file_count": { "type": "integer", "example": 3 },
          "page_count": { "type": "integer", "example": 10 },
          "credits_used": { "type": "integer", "example": 10 },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" },
          "completed_at": { "type": "string", "format": "date-time", "nullable": true },
          "documents": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExtractionDetailDocument"
            }
          }
        },
        "required": [
          "extraction_id", "parser_id", "parser_name", "status",
          "file_count", "page_count", "credits_used", "created_at", "updated_at", "documents"
        ]
      },
      "ExtractionResultsDocument": {
        "type": "object",
        "properties": {
          "document_id": { "type": "string", "example": "doc_abc" },
          "filename": { "type": "string", "example": "invoice.pdf" },
          "status": { "type": "string", "example": "completed" },
          "page_count": { "type": "integer", "example": 3 },
          "credits_used": { "type": "integer", "example": 3 },
          "confidence": { "type": "number", "nullable": true, "example": 0.98 },
          "processing_time_ms": { "type": "integer", "nullable": true, "example": 3200 },
          "extracted_data": { "type": "object", "nullable": true },
          "error_message": { "type": "string", "nullable": true, "example": null },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        },
        "required": ["document_id", "filename", "status", "page_count", "credits_used", "created_at", "updated_at"]
      },
      "ExtractionResults": {
        "type": "object",
        "properties": {
          "extraction_id": { "type": "string", "example": "ext_123" },
          "parser_id": { "type": "string", "example": "prs_999" },
          "status": { "type": "string", "example": "completed" },
          "file_count": { "type": "integer", "example": 3 },
          "page_count": { "type": "integer", "example": 10 },
          "credits_used": { "type": "integer", "example": 10 },
          "created_at": { "type": "string", "format": "date-time" },
          "completed_at": { "type": "string", "format": "date-time", "nullable": true },
          "documents": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExtractionResultsDocument"
            }
          }
        },
        "required": ["extraction_id", "parser_id", "status", "file_count", "page_count", "credits_used", "created_at", "documents"]
      },
      "DocumentDetail": {
        "type": "object",
        "properties": {
          "document_id": { "type": "string", "example": "doc_abc" },
          "extraction_id": { "type": "string", "nullable": true, "example": "ext_123" },
          "parser_id": { "type": "string", "nullable": true, "example": "prs_999" },
          "parser_name": { "type": "string", "nullable": true, "example": "Invoice Parser" },
          "filename": { "type": "string", "example": "invoice.pdf" },
          "file_type": { "type": "string", "example": "application/pdf" },
          "file_size": { "type": "integer", "example": 204800 },
          "status": { "type": "string", "example": "completed" },
          "page_count": { "type": "integer", "example": 3 },
          "credits_used": { "type": "integer", "example": 3 },
          "confidence": { "type": "number", "nullable": true, "example": 0.98 },
          "processing_time_ms": { "type": "integer", "nullable": true, "example": 3200 },
          "extracted_data": { "type": "object", "nullable": true },
          "error_message": { "type": "string", "nullable": true },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        },
        "required": [
          "document_id", "extraction_id", "parser_id", "filename", "status", "page_count",
          "credits_used", "created_at", "updated_at"
        ]
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "webhook_id": { "type": "string", "example": "wh_123" },
          "url": { "type": "string", "example": "https://hooks.zapier.com/..." },
          "events": {
            "type": "array",
            "items": { "type": "string" }
          },
          "is_active": { "type": "boolean", "example": true },
          "created_at": { "type": "string", "format": "date-time" }
        },
        "required": ["webhook_id", "url", "events", "is_active", "created_at"]
      }
    }
  },
  "x-webhooks": {
    "extraction.document_completed": {
      "post": {
        "summary": "Document Completed Callback",
        "description": "Triggered when a single document inside an extraction batch reaches a terminal success or failed state.",
        "headers": {
          "X-PerfectParser-Signature": { "type": "string", "description": "HMAC-SHA256 signature calculated from the raw payload signed with the webhook secret." },
          "X-Webhook-Secret": { "type": "string", "description": "Static shared webhook secret token provided by the user." }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "event": { "type": "string", "example": "extraction.document_completed" },
                  "timestamp": { "type": "string", "format": "date-time" },
                  "data": { "$ref": "#/components/schemas/DocumentDetail" }
                },
                "required": ["event", "timestamp", "data"]
              }
            }
          }
        }
      }
    },
    "extraction.job_completed": {
      "post": {
        "summary": "Extraction Job Completed Callback",
        "description": "Triggered when all files inside an extraction batch have completed processing.",
        "headers": {
          "X-PerfectParser-Signature": { "type": "string", "description": "HMAC-SHA256 signature calculated from the raw payload signed with the webhook secret." },
          "X-Webhook-Secret": { "type": "string", "description": "Static shared webhook secret token provided by the user." }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "event": { "type": "string", "example": "extraction.job_completed" },
                  "timestamp": { "type": "string", "format": "date-time" },
                  "data": { "$ref": "#/components/schemas/Extraction" }
                },
                "required": ["event", "timestamp", "data"]
              }
            }
          }
        }
      }
    }
  }
}
