openapi: 3.1.0 info: title: RohaVerify API version: 1.0.0 description: | Register, verify, list, and revoke issuer-backed SHA-256 document fingerprints. Browser flows hash files locally. The optional /api/hash endpoint receives file bytes transiently and returns only their digest. When enabled, /api/malware/scan explicitly uploads and streams a file to ClamAV, persists only hash/verdict metadata, and returns a deterministic malware risk score. A clean verdict is not a guarantee that a file is safe. servers: - url: https://rohaverify.com - url: https://staging.rohaverify.com security: - bearerAuth: [] paths: /api/health: get: summary: Process liveness security: [] responses: "200": description: Process is serving HTTP. /api/ready: get: summary: Datastore readiness security: [] responses: "200": description: DynamoDB is reachable. "503": description: Datastore is unavailable. /api/version: get: summary: Deployed build identity security: [] responses: "200": description: Version and build metadata. /api/auth/me: get: summary: Authentication mode and current identity security: - {} - bearerAuth: [] responses: "200": description: Authentication configuration or active identity. "401": $ref: "#/components/responses/Unauthorized" /api/register: post: summary: Register one document fingerprint requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/RegisterRequest" responses: "201": description: Fingerprint registered. content: application/json: schema: $ref: "#/components/schemas/RegisterResponse" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "409": description: The fingerprint is already registered. /api/register/batch: post: summary: Register up to 100 fingerprints requestBody: required: true content: application/json: schema: type: object required: [documents] properties: documents: type: array minItems: 1 maxItems: 100 items: $ref: "#/components/schemas/RegisterRequest" responses: "200": description: Per-item registration results and summary. "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" /api/register/{signature}/revoke: post: summary: Revoke a fingerprint owned by the authenticated issuer parameters: - $ref: "#/components/parameters/Signature" requestBody: required: true content: application/json: schema: type: object additionalProperties: false properties: issuer: type: string maxLength: 320 description: Required in API-key mode; derived from SSO/JWT otherwise. reason: type: string maxLength: 280 responses: "200": description: Credential revoked. "401": $ref: "#/components/responses/Unauthorized" "403": description: Authenticated issuer does not own this credential. "404": description: Fingerprint not found. /api/verify: get: summary: Verify a fingerprint from a QR link security: [] parameters: - name: sig in: query required: true schema: $ref: "#/components/schemas/Signature" - name: iss in: query description: Expected issuer pin. schema: type: string maxLength: 320 responses: "200": description: Verification result; unknown and revoked records return authentic=false. content: application/json: schema: $ref: "#/components/schemas/VerifyResponse" post: summary: Verify a fingerprint security: [] requestBody: required: true content: application/json: schema: type: object additionalProperties: false required: [signature] properties: signature: $ref: "#/components/schemas/Signature" expectedIssuer: type: string maxLength: 320 responses: "200": description: Verification result. content: application/json: schema: $ref: "#/components/schemas/VerifyResponse" "400": $ref: "#/components/responses/BadRequest" /api/issuer/documents: get: summary: List credentials owned by the authenticated issuer parameters: - name: issuer in: query description: Required in API-key mode; ignored for identity-based auth. schema: type: string maxLength: 320 - name: limit in: query schema: type: integer minimum: 1 maximum: 100 default: 50 - name: cursor in: query schema: type: string responses: "200": description: Paginated issuer-owned credential records. "401": $ref: "#/components/responses/Unauthorized" /api/hash: post: summary: Compute SHA-256 on the server description: | This optional compatibility endpoint uploads file bytes to the server transiently. Use browser-side hashing when the file must remain on the user's device. security: [] requestBody: required: true content: multipart/form-data: schema: type: object required: [file] properties: file: type: string format: binary responses: "200": description: SHA-256 digest; file content is not persisted. "400": $ref: "#/components/responses/BadRequest" "413": description: Upload exceeds MAX_UPLOAD_MB. /api/malware/status: get: summary: Optional ClamAV availability security: [] responses: "200": description: Whether scanning is enabled and the daemon is available. content: application/json: schema: type: object required: [enabled, available] properties: enabled: type: boolean available: type: boolean engine: type: string engineVersion: type: string /api/malware/scan: post: summary: Stream a file to the optional ClamAV scanner description: | This explicit opt-in uploads file bytes and streams them to a private clamd service with INSTREAM. The original file is not retained or persisted. SHA-256 and verdict metadata may be retained for reputation lookup. security: [] requestBody: required: true content: multipart/form-data: schema: type: object required: [file] properties: file: type: string format: binary responses: "200": description: Completed ClamAV verdict and deterministic malware risk score. content: application/json: schema: $ref: "#/components/schemas/MalwareScanResponse" "400": $ref: "#/components/responses/BadRequest" "413": description: File exceeds MAX_MALWARE_SCAN_MB or clamd StreamMaxLength. "503": description: Scanning is disabled, unavailable, or timed out. This never means clean. /api/malware/reputation: get: summary: Read the latest hash-only malware reputation security: [] parameters: - name: signature in: query required: true schema: $ref: "#/components/schemas/Signature" responses: "200": description: Latest retained ClamAV observation for the SHA-256. content: application/json: schema: $ref: "#/components/schemas/MalwareReputation" "400": $ref: "#/components/responses/BadRequest" "404": description: No retained malware observation exists for this SHA-256. /api/billing/plans: get: summary: Public plan catalog and checkout mode security: [] responses: "200": description: Plan catalog and currentMode. /api/billing/status: get: summary: Authenticated issuer plan and usage parameters: - $ref: "#/components/parameters/BillingIssuer" responses: "200": description: Current plan, state, quota, and monthly usage. "401": $ref: "#/components/responses/Unauthorized" /api/billing/checkout: post: summary: Start checkout for an enabled self-service plan parameters: - $ref: "#/components/parameters/BillingIssuer" requestBody: required: false content: application/json: schema: type: object additionalProperties: false properties: planId: type: string enum: [starter, growth, scale, enterprise] priceId: type: string maxLength: 200 responses: "200": description: Checkout URL or sales-contact instruction. "401": $ref: "#/components/responses/Unauthorized" "503": description: Online billing is disabled or not configured. components: securitySchemes: bearerAuth: type: http scheme: bearer description: Provisioned API key, Cognito JWT, or SSO session as configured. parameters: Signature: name: signature in: path required: true schema: $ref: "#/components/schemas/Signature" BillingIssuer: name: X-Billing-Issuer in: header description: Required for API-key billing calls; identity auth derives it. schema: type: string maxLength: 320 responses: BadRequest: description: Invalid request. content: application/json: schema: $ref: "#/components/schemas/Error" Unauthorized: description: Missing or invalid issuer credential. content: application/json: schema: $ref: "#/components/schemas/Error" schemas: Signature: type: string pattern: "^[a-fA-F0-9]{64}$" description: SHA-256 digest encoded as 64 hexadecimal characters. Error: type: object required: [error] properties: error: type: string RegisterRequest: type: object additionalProperties: false required: [signature, issuer] properties: signature: $ref: "#/components/schemas/Signature" issuer: type: string maxLength: 320 documentName: type: string maxLength: 200 documentType: type: string maxLength: 100 issuedAt: type: string maxLength: 100 description: Issuer-provided date or timestamp. metadata: type: object maxProperties: 50 additionalProperties: type: string maxLength: 1000 Record: type: object required: [signature, issuer, status] properties: signature: $ref: "#/components/schemas/Signature" issuer: type: string documentName: type: string documentType: type: string issuedAt: type: string status: type: string enum: [active, revoked] metadata: type: object additionalProperties: type: string createdAt: type: string format: date-time revokedAt: type: string format: date-time revokedReason: type: string RegisterResponse: type: object properties: record: $ref: "#/components/schemas/Record" verificationUrl: type: string format: uri qrCodeDataUrl: type: string VerifyResponse: type: object required: [authentic] properties: authentic: type: boolean reason: type: string record: $ref: "#/components/schemas/Record" MalwareReputation: type: object required: - sha256 - verdict - flagged - malwareRiskScore - engine - lastScannedAt - observations - stale properties: sha256: $ref: "#/components/schemas/Signature" verdict: type: string enum: [clean, malicious] flagged: type: boolean malwareRiskScore: type: integer enum: [0, 100] description: Deterministic mapping of the ClamAV verdict, not a probability. threat: type: string engine: type: string const: ClamAV engineVersion: type: string lastScannedAt: type: string format: date-time observations: type: integer minimum: 1 stale: type: boolean MalwareScanResponse: type: object required: - sha256 - verdict - flagged - malwareRiskScore - engine - scannedAt - bytesScanned - reputationStored - disclaimer properties: sha256: $ref: "#/components/schemas/Signature" verdict: type: string enum: [clean, malicious] flagged: type: boolean malwareRiskScore: type: integer enum: [0, 100] threat: type: string engine: type: string const: ClamAV engineVersion: type: string scannedAt: type: string format: date-time bytesScanned: type: integer minimum: 0 reputation: $ref: "#/components/schemas/MalwareReputation" reputationStored: type: boolean disclaimer: type: string