{
    "openapi": "3.1.0",
    "info": {
        "title": "NoRekey API",
        "version": "1.0.0",
        "summary": "Convert PDF bank statements to CSV, Excel, OFX, QFX and JSON — every conversion verified against the statement's own balances.",
        "description": "Three endpoints: upload a statement, poll its status, download the result in any supported format. Authentication is a bearer API key (created under Settings → Team → API keys; keys are shown once and start with `nrk_`). API conversions draw from the same monthly page allowance as the web converter, on every plan including Free. Rate limit: 120 requests per minute per API key. Source PDFs are deleted the moment processing finishes; statement passwords are used once to open the file and never stored.",
        "termsOfService": "https://norekey.com/terms",
        "contact": {
            "name": "NoRekey support",
            "email": "support@norekey.com",
            "url": "https://norekey.com/help/using-the-api"
        }
    },
    "servers": [{ "url": "https://norekey.com" }],
    "security": [{ "apiKey": [] }],
    "paths": {
        "/api/v1/conversions": {
            "post": {
                "operationId": "createConversion",
                "summary": "Upload a statement for conversion",
                "description": "Accepts the statement either as a multipart file upload or as base64 inside a JSON body (for callers where multipart is awkward). PDFs only, up to 20 MB. Password-protected PDFs convert in one step when the password is supplied; scanned statements convert through OCR automatically and charge 3 pages of allowance per page (`pages_charged` reports the actual cost). Returns the conversion document immediately — poll it until `status` is `completed`.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "multipart/form-data": {
                            "schema": {
                                "type": "object",
                                "required": ["statement"],
                                "properties": {
                                    "statement": {
                                        "type": "string",
                                        "format": "binary",
                                        "description": "The statement PDF, up to 20 MB."
                                    },
                                    "password": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Password for a protected PDF. Used once to open the file, never stored."
                                    }
                                }
                            }
                        },
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": ["statement", "filename"],
                                "properties": {
                                    "statement": {
                                        "type": "string",
                                        "contentEncoding": "base64",
                                        "description": "The statement PDF, base64-encoded. Decoded size limit is the same 20 MB."
                                    },
                                    "filename": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Original filename, used for display and exports."
                                    },
                                    "password": {
                                        "type": "string",
                                        "maxLength": 255,
                                        "description": "Password for a protected PDF. Used once to open the file, never stored."
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Statement accepted; conversion queued or processing.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Conversion"
                                }
                            }
                        }
                    },
                    "401": { "$ref": "#/components/responses/Unauthenticated" },
                    "422": {
                        "description": "The statement was rejected. `code` identifies why — `password_required` (protected PDF, no password given), `quota_exceeded` (the statement would take the team over its monthly page allowance), `too_many_pages` (over the per-file page limit), or an `invalid_statement`/unreadable-PDF code with a human-readable `message`.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": { "$ref": "#/components/responses/RateLimited" }
                }
            }
        },
        "/api/v1/conversions/{conversion}": {
            "get": {
                "operationId": "getConversion",
                "summary": "Poll a conversion",
                "description": "Returns the conversion document. Poll until `status` is `completed` (or `failed`); `expected_seconds` is a live estimate of total processing time for a statement of this size. Completed conversions report the detected `bank` and `currency`, whether the extraction `balanced` against the statement's own opening and closing figures, and the formats available for export.",
                "parameters": [
                    { "$ref": "#/components/parameters/conversionId" }
                ],
                "responses": {
                    "200": {
                        "description": "The conversion document.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Conversion"
                                }
                            }
                        }
                    },
                    "401": { "$ref": "#/components/responses/Unauthenticated" },
                    "404": { "$ref": "#/components/responses/NotFound" },
                    "429": { "$ref": "#/components/responses/RateLimited" }
                }
            }
        },
        "/api/v1/conversions/{conversion}/export/{format}": {
            "get": {
                "operationId": "downloadExport",
                "summary": "Download a completed conversion",
                "description": "Streams the converted statement in the requested format as a file download. Available once `status` is `completed`, until the conversion expires on the team's retention schedule (expired conversions return 410).",
                "parameters": [
                    { "$ref": "#/components/parameters/conversionId" },
                    {
                        "name": "format",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "enum": ["csv", "xlsx", "ofx", "qfx", "json"]
                        },
                        "description": "The export format."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The converted statement as a file download.",
                        "content": {
                            "text/csv": { "schema": { "type": "string" } },
                            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
                                "schema": {
                                    "type": "string",
                                    "format": "binary"
                                }
                            },
                            "application/x-ofx": {
                                "schema": { "type": "string" }
                            },
                            "application/json": {
                                "schema": { "type": "object" }
                            }
                        }
                    },
                    "401": { "$ref": "#/components/responses/Unauthenticated" },
                    "404": {
                        "description": "Unknown conversion, a conversion belonging to another team, or an unknown export format."
                    },
                    "410": {
                        "description": "The conversion has expired on the team's retention schedule and its data has been deleted."
                    },
                    "429": { "$ref": "#/components/responses/RateLimited" }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "apiKey": {
                "type": "http",
                "scheme": "bearer",
                "description": "Team API key, sent as `Authorization: Bearer nrk_…`. Create keys under Settings → Team → API keys."
            }
        },
        "parameters": {
            "conversionId": {
                "name": "conversion",
                "in": "path",
                "required": true,
                "schema": { "type": "string" },
                "description": "The conversion id (ULID) returned when the statement was uploaded."
            }
        },
        "responses": {
            "Unauthenticated": {
                "description": "Missing, malformed or revoked API key."
            },
            "NotFound": {
                "description": "No such conversion visible to this API key — keys see exactly their own team's conversions."
            },
            "RateLimited": {
                "description": "Rate limit exceeded — 120 requests per minute per API key."
            }
        },
        "schemas": {
            "Conversion": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string",
                        "description": "ULID identifying the conversion."
                    },
                    "statement": {
                        "type": "string",
                        "description": "Original filename of the uploaded PDF."
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "pending",
                            "processing",
                            "completed",
                            "failed",
                            "expired"
                        ]
                    },
                    "pages": {
                        "type": ["integer", "null"],
                        "description": "Page count of the statement."
                    },
                    "scanned": {
                        "type": "boolean",
                        "description": "Whether the statement was detected as scanned and converted through OCR."
                    },
                    "pages_charged": {
                        "type": ["integer", "null"],
                        "description": "Pages drawn from the monthly allowance — 3× the page count for scanned statements."
                    },
                    "expected_seconds": {
                        "type": "integer",
                        "description": "Live estimate of total processing time for a statement of this size, in seconds."
                    },
                    "bank": {
                        "type": ["string", "null"],
                        "description": "Bank or issuer detected from the statement, once completed."
                    },
                    "currency": {
                        "type": ["string", "null"],
                        "description": "Currency detected from the statement, once completed."
                    },
                    "balanced": {
                        "type": ["boolean", "null"],
                        "description": "Whether the extracted transactions reconciled against the statement's own opening and closing balances. False means the conversion is flagged for review — inspect before trusting."
                    },
                    "opening_balance": {
                        "type": ["number", "string", "null"],
                        "description": "The statement's declared opening balance."
                    },
                    "closing_balance": {
                        "type": ["number", "string", "null"],
                        "description": "The statement's declared closing balance."
                    },
                    "error": {
                        "type": ["string", "null"],
                        "description": "Human-readable failure reason when status is failed."
                    },
                    "formats": {
                        "type": "array",
                        "items": { "type": "string" },
                        "description": "Export formats available from the export endpoint."
                    },
                    "created_at": {
                        "type": ["string", "null"],
                        "format": "date-time"
                    },
                    "completed_at": {
                        "type": ["string", "null"],
                        "format": "date-time"
                    }
                }
            },
            "Error": {
                "type": "object",
                "required": ["code", "message"],
                "properties": {
                    "code": {
                        "type": "string",
                        "description": "Machine-readable error identifier, e.g. password_required, quota_exceeded, too_many_pages, invalid_statement."
                    },
                    "message": {
                        "type": "string",
                        "description": "Human-readable explanation."
                    }
                }
            }
        }
    }
}
