{"openapi":"3.0.3","info":{"title":"RSVTP Ingestion API","version":"1.0.0","description":"Ingest vehicle, customer and payment records from third-party systems. Records are normalized and stored in MySQL. `transactionRef` is unique and idempotent, so retries are safe. Supported services: Vehicle License (VL001), Hackney Permit (HP002). `serviceName` and `serviceCode` must be sent as a matching pair.","contact":{"name":"RSVTP API support","email":"support@example.com"}},"servers":[{"url":"http://localhost:8086","description":"Current environment"}],"tags":[{"name":"Transactions","description":"Submit and retrieve vehicle payment records."},{"name":"Health","description":"Service and database health checks."}],"paths":{"/api/v1/transactions":{"post":{"tags":["Transactions"],"summary":"Submit a vehicle, customer and payment record","operationId":"createTransaction","description":"Accepts one flat JSON payload containing vehicle, customer and payment information.\n\n**Supported services**\n- `Vehicle License` → `VL001`\n- `Hackney Permit` → `HP002`\n`serviceName` and `serviceCode` must be sent as a matching pair. Unsupported or mismatched pairs return `422 VALIDATION_FAILED`.\n\n**Matching rules**\n- Vehicles are matched by `chassisNumber`, then `plateNumber`.\n- Owners are matched by `phoneNumber`, then `emailAddress`.\n- Existing vehicle and owner details are updated with the supplied values.\n- If the chassis and plate belong to different vehicles, or the phone and email belong to different owners, the record is still stored against the primary match and flagged internally for review. It is not rejected.\n\n**Idempotency**\n- `transactionRef` is unique. Retrying with the same reference and the same normalized data returns `200` with `duplicate: true`.\n- Reusing a reference with different data returns `409 TRANSACTION_REF_CONFLICT`.\n\nThe POST response is intentionally minimal: a success receipt with the transaction reference and duplicate indicator. Internal review flags are not exposed. Use `GET /api/v1/transactions/{transactionRef}` to retrieve the full normalized record.\n\nSend the API key in the `x-api-key` header. A Bearer token is also accepted.","security":[{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionPayload"},"example":{"plateNumber":"RIV-482-AA","vehicleMake":"Toyota","vehicleModel":"Corolla","chassisNumber":"JTDBR32E720123456","engineNumber":"1ZZ-FE-789012","vehicleColour":"Silver","vehicleType":"Saloon","vehicleCategory":"Private","fullName":"Chinedu Okafor","phoneNumber":"+2348031234567","address":"12 Aba Road, Port Harcourt, Rivers State","emailAddress":"chinedu.okafor@example.com","amountPaid":15000.5,"transactionRef":"RVS-2026-000190","transactionDate":"2026-11-04T10:15:00.000Z","serviceName":"Vehicle License","serviceCode":"VL001"}}}},"responses":{"200":{"description":"Idempotent replay. The transaction already exists with identical data.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestResponse"},"example":{"success":true,"message":"Record already received (idempotent replay).","data":{"transactionRef":"RVS-2026-000190","duplicate":true},"meta":{"requestId":"8f14e45f-ea0f-4c0c-9b5e-9b0a1f4f0f1a","timestamp":"2026-11-04T10:15:03.120Z"}}}}},"201":{"description":"Record received and stored successfully. The response is a minimal success receipt and does not expose internal review flags.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestResponse"},"example":{"success":true,"message":"Record received and stored successfully.","data":{"transactionRef":"RVS-2026-000190","duplicate":false},"meta":{"requestId":"8f14e45f-ea0f-4c0c-9b5e-9b0a1f4f0f1a","timestamp":"2026-11-04T10:15:03.120Z"}}}}},"400":{"description":"Malformed JSON or query parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_FAILED","message":"The payload failed validation.","details":[{"field":"plateNumber","message":"plateNumber is required","code":"too_small"}]},"meta":{"requestId":"8f14e45f-ea0f-4c0c-9b5e-9b0a1f4f0f1a","timestamp":"2026-11-04T10:15:03.120Z"}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_FAILED","message":"The payload failed validation.","details":[{"field":"plateNumber","message":"plateNumber is required","code":"too_small"}]},"meta":{"requestId":"8f14e45f-ea0f-4c0c-9b5e-9b0a1f4f0f1a","timestamp":"2026-11-04T10:15:03.120Z"}}}}},"409":{"description":"A transaction with this reference already exists with different data (`TRANSACTION_REF_CONFLICT`). Identifier mismatches are stored and flagged internally instead of rejected.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_FAILED","message":"The payload failed validation.","details":[{"field":"plateNumber","message":"plateNumber is required","code":"too_small"}]},"meta":{"requestId":"8f14e45f-ea0f-4c0c-9b5e-9b0a1f4f0f1a","timestamp":"2026-11-04T10:15:03.120Z"}}}}},"413":{"description":"Request body is larger than 1 MB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_FAILED","message":"The payload failed validation.","details":[{"field":"plateNumber","message":"plateNumber is required","code":"too_small"}]},"meta":{"requestId":"8f14e45f-ea0f-4c0c-9b5e-9b0a1f4f0f1a","timestamp":"2026-11-04T10:15:03.120Z"}}}}},"415":{"description":"Content-Type is not application/json.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_FAILED","message":"The payload failed validation.","details":[{"field":"plateNumber","message":"plateNumber is required","code":"too_small"}]},"meta":{"requestId":"8f14e45f-ea0f-4c0c-9b5e-9b0a1f4f0f1a","timestamp":"2026-11-04T10:15:03.120Z"}}}}},"422":{"description":"Payload validation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_FAILED","message":"The payload failed validation.","details":[{"field":"plateNumber","message":"plateNumber is required","code":"too_small"}]},"meta":{"requestId":"8f14e45f-ea0f-4c0c-9b5e-9b0a1f4f0f1a","timestamp":"2026-11-04T10:15:03.120Z"}}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_FAILED","message":"The payload failed validation.","details":[{"field":"plateNumber","message":"plateNumber is required","code":"too_small"}]},"meta":{"requestId":"8f14e45f-ea0f-4c0c-9b5e-9b0a1f4f0f1a","timestamp":"2026-11-04T10:15:03.120Z"}}}}}}},"get":{"tags":["Transactions"],"summary":"List transactions","operationId":"listTransactions","description":"Returns a paginated list of transactions. Search matches transaction reference, service, plate number, chassis number, vehicle make/model, owner name, phone and email.","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},{"name":"pageSize","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"search","in":"query","description":"Free-text search.","schema":{"type":"string"}},{"name":"serviceCode","in":"query","description":"Filter by service code.","schema":{"type":"string","enum":["VL001","HP002"],"example":"VL001"}},{"name":"from","in":"query","description":"Inclusive lower bound for transactionDate.","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","description":"Inclusive upper bound for transactionDate.","schema":{"type":"string","format":"date-time"}},{"name":"flagged","in":"query","description":"Filter by review state. `true` returns only records stored with identifier mismatch flags; `false` returns clean records.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Paginated transaction list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedTransactionsResponse"}}}},"400":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_FAILED","message":"The payload failed validation.","details":[{"field":"plateNumber","message":"plateNumber is required","code":"too_small"}]},"meta":{"requestId":"8f14e45f-ea0f-4c0c-9b5e-9b0a1f4f0f1a","timestamp":"2026-11-04T10:15:03.120Z"}}}}},"401":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_FAILED","message":"The payload failed validation.","details":[{"field":"plateNumber","message":"plateNumber is required","code":"too_small"}]},"meta":{"requestId":"8f14e45f-ea0f-4c0c-9b5e-9b0a1f4f0f1a","timestamp":"2026-11-04T10:15:03.120Z"}}}}},"500":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_FAILED","message":"The payload failed validation.","details":[{"field":"plateNumber","message":"plateNumber is required","code":"too_small"}]},"meta":{"requestId":"8f14e45f-ea0f-4c0c-9b5e-9b0a1f4f0f1a","timestamp":"2026-11-04T10:15:03.120Z"}}}}}}}},"/api/v1/transactions/{transactionRef}":{"get":{"tags":["Transactions"],"summary":"Get a transaction by reference","operationId":"getTransaction","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"transactionRef","in":"path","required":true,"schema":{"type":"string","example":"RVS-2026-000184"}}],"responses":{"200":{"description":"Transaction found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionResponse"},"example":{"success":true,"data":{"id":184,"transactionRef":"RVS-2026-000184","amountPaid":"15000.50","transactionDate":"2026-11-04T10:15:00.000Z","serviceName":"Vehicle License","serviceCode":"VL001","createdAt":"2026-11-04T10:15:03.120Z","updatedAt":"2026-11-04T10:15:03.120Z","flagged":false,"flagReasons":[],"vehicle":{"id":57,"plateNumber":"RIV-482-AA","vehicleMake":"Toyota","vehicleModel":"Corolla","chassisNumber":"JTDBR32E720123456","engineNumber":"1ZZ-FE-789012","vehicleColour":"Silver","vehicleType":"Saloon","vehicleCategory":"Private"},"owner":{"id":41,"fullName":"Chinedu Okafor","phoneNumber":"+2348031234567","emailAddress":"chinedu.okafor@example.com","address":"12 Aba Road, Port Harcourt, Rivers State"}},"meta":{"requestId":"8f14e45f-ea0f-4c0c-9b5e-9b0a1f4f0f1a","timestamp":"2026-11-04T10:15:03.120Z"}}}}},"401":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_FAILED","message":"The payload failed validation.","details":[{"field":"plateNumber","message":"plateNumber is required","code":"too_small"}]},"meta":{"requestId":"8f14e45f-ea0f-4c0c-9b5e-9b0a1f4f0f1a","timestamp":"2026-11-04T10:15:03.120Z"}}}}},"404":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_FAILED","message":"The payload failed validation.","details":[{"field":"plateNumber","message":"plateNumber is required","code":"too_small"}]},"meta":{"requestId":"8f14e45f-ea0f-4c0c-9b5e-9b0a1f4f0f1a","timestamp":"2026-11-04T10:15:03.120Z"}}}}},"500":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_FAILED","message":"The payload failed validation.","details":[{"field":"plateNumber","message":"plateNumber is required","code":"too_small"}]},"meta":{"requestId":"8f14e45f-ea0f-4c0c-9b5e-9b0a1f4f0f1a","timestamp":"2026-11-04T10:15:03.120Z"}}}}}}}},"/api/v1/health":{"get":{"tags":["Health"],"summary":"Health check","operationId":"healthCheck","description":"Public endpoint that verifies the application and database are reachable.","responses":{"200":{"description":"Service is healthy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"},"example":{"status":"ok","database":"up","latencyMs":3,"uptimeSeconds":8421,"timestamp":"2026-11-04T10:15:03.120Z"}}}},"503":{"description":"Database is unreachable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"},"example":{"status":"degraded","database":"down","latencyMs":2001,"timestamp":"2026-11-04T10:15:03.120Z"}}}}}}}},"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"x-api-key","description":"API key issued to the integrating system. Alternatively send `Authorization: Bearer <key>`."}},"schemas":{"TransactionPayload":{"type":"object","description":"Flat payload containing vehicle, customer and payment information. Unknown fields are ignored.","required":["plateNumber","vehicleMake","vehicleModel","chassisNumber","engineNumber","fullName","phoneNumber","amountPaid","transactionRef","transactionDate","serviceName","serviceCode"],"properties":{"plateNumber":{"type":"string","maxLength":20,"example":"RIV-482-AA","description":"Vehicle plate number. Normalized to uppercase."},"vehicleMake":{"type":"string","maxLength":80,"example":"Toyota"},"vehicleModel":{"type":"string","maxLength":80,"example":"Corolla"},"chassisNumber":{"type":"string","maxLength":60,"example":"JTDBR32E720123456","description":"Vehicle chassis/VIN. Normalized to uppercase; unique per vehicle."},"engineNumber":{"type":"string","maxLength":60,"example":"1ZZ-FE-789012","description":"Engine number. Normalized to uppercase with whitespace removed."},"vehicleColour":{"type":"string","maxLength":40,"example":"Silver"},"vehicleType":{"type":"string","maxLength":60,"example":"Saloon"},"vehicleCategory":{"type":"string","maxLength":60,"example":"Private"},"fullName":{"type":"string","maxLength":150,"example":"Chinedu Okafor"},"phoneNumber":{"type":"string","maxLength":30,"example":"+2348031234567","description":"Owner phone number. Spaces, dashes, dots and parentheses are removed; `00` prefixes are converted to `+`."},"address":{"type":"string","maxLength":1000,"example":"12 Aba Road, Port Harcourt, Rivers State"},"emailAddress":{"type":"string","format":"email","maxLength":190,"example":"chinedu.okafor@example.com"},"amountPaid":{"oneOf":[{"type":"number"},{"type":"string"}],"example":15000.5,"description":"Amount paid, greater than 0 and at most 9,999,999,999.99. Stored as DECIMAL(12,2)."},"transactionRef":{"type":"string","maxLength":100,"example":"RVS-2026-000184","description":"Unique transaction reference. Used for idempotency; retries with the same reference and data are safe."},"transactionDate":{"type":"string","format":"date-time","example":"2026-11-04T10:15:00.000Z","description":"ISO 8601 date-time. Unix timestamps in milliseconds are also accepted."},"serviceName":{"type":"string","enum":["Vehicle License","Hackney Permit"],"example":"Vehicle License","description":"Service name. Must match `serviceCode`: Vehicle License → VL001, Hackney Permit → HP002."},"serviceCode":{"type":"string","enum":["VL001","HP002"],"example":"VL001","description":"Service code. Normalized to uppercase. Must match `serviceName`: Vehicle License → VL001, Hackney Permit → HP002."}}},"Vehicle":{"type":"object","properties":{"id":{"type":"integer","example":57},"plateNumber":{"type":"string","example":"RIV-482-AA"},"vehicleMake":{"type":"string","example":"Toyota"},"vehicleModel":{"type":"string","example":"Corolla"},"chassisNumber":{"type":"string","example":"JTDBR32E720123456"},"engineNumber":{"type":"string","example":"1ZZ-FE-789012"},"vehicleColour":{"type":"string","nullable":true,"example":"Silver"},"vehicleType":{"type":"string","nullable":true,"example":"Saloon"},"vehicleCategory":{"type":"string","nullable":true,"example":"Private"}},"required":["id","plateNumber","vehicleMake","vehicleModel","chassisNumber","engineNumber","vehicleColour","vehicleType","vehicleCategory"]},"Owner":{"type":"object","nullable":true,"properties":{"id":{"type":"integer","example":41},"fullName":{"type":"string","example":"Chinedu Okafor"},"phoneNumber":{"type":"string","example":"+2348031234567"},"emailAddress":{"type":"string","nullable":true,"format":"email","example":"chinedu.okafor@example.com"},"address":{"type":"string","nullable":true,"example":"12 Aba Road, Port Harcourt, Rivers State"}},"required":["id","fullName","phoneNumber","emailAddress","address"]},"Transaction":{"type":"object","properties":{"id":{"type":"integer","example":184},"transactionRef":{"type":"string","example":"RVS-2026-000184"},"amountPaid":{"type":"string","description":"Decimal amount serialized as a string to avoid float rounding.","example":"15000.50"},"transactionDate":{"type":"string","format":"date-time"},"serviceName":{"type":"string","enum":["Vehicle License","Hackney Permit"],"example":"Vehicle License"},"serviceCode":{"type":"string","enum":["VL001","HP002"],"example":"VL001"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"flagged":{"type":"boolean","description":"True when the record was stored but its vehicle or owner identifiers pointed at different existing records."},"flagReasons":{"type":"array","description":"Human-readable conflict reasons. Empty when `flagged` is false.","items":{"type":"string"},"example":["VEHICLE_IDENTIFIER_MISMATCH: chassisNumber \"JTDBR32E720123456\" belongs to vehicle #57, while plateNumber \"RIV-999-ZZ\" belongs to vehicle #62."]},"vehicle":{"type":"object","properties":{"id":{"type":"integer","example":57},"plateNumber":{"type":"string","example":"RIV-482-AA"},"vehicleMake":{"type":"string","example":"Toyota"},"vehicleModel":{"type":"string","example":"Corolla"},"chassisNumber":{"type":"string","example":"JTDBR32E720123456"},"engineNumber":{"type":"string","example":"1ZZ-FE-789012"},"vehicleColour":{"type":"string","nullable":true,"example":"Silver"},"vehicleType":{"type":"string","nullable":true,"example":"Saloon"},"vehicleCategory":{"type":"string","nullable":true,"example":"Private"}},"required":["id","plateNumber","vehicleMake","vehicleModel","chassisNumber","engineNumber","vehicleColour","vehicleType","vehicleCategory"]},"owner":{"type":"object","nullable":true,"properties":{"id":{"type":"integer","example":41},"fullName":{"type":"string","example":"Chinedu Okafor"},"phoneNumber":{"type":"string","example":"+2348031234567"},"emailAddress":{"type":"string","nullable":true,"format":"email","example":"chinedu.okafor@example.com"},"address":{"type":"string","nullable":true,"example":"12 Aba Road, Port Harcourt, Rivers State"}},"required":["id","fullName","phoneNumber","emailAddress","address"]}},"required":["id","transactionRef","amountPaid","transactionDate","serviceName","serviceCode","createdAt","updatedAt","flagged","flagReasons","vehicle","owner"]},"Meta":{"type":"object","properties":{"requestId":{"type":"string","format":"uuid"},"timestamp":{"type":"string","format":"date-time"}},"required":["requestId","timestamp"]},"IngestResult":{"type":"object","description":"Minimal receipt returned by the ingestion endpoint. Internal review flags are not exposed. Fetch the transaction by reference for the full normalized record.","properties":{"transactionRef":{"type":"string","example":"RVS-2026-000190"},"duplicate":{"type":"boolean","description":"True when this response is an idempotent replay of an existing transaction."}},"required":["transactionRef","duplicate"]},"IngestResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string"},"data":{"$ref":"#/components/schemas/IngestResult"},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["success","data","meta"]},"TransactionResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string"},"data":{"$ref":"#/components/schemas/Transaction"},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["success","data","meta"]},"Pagination":{"type":"object","properties":{"page":{"type":"integer","example":1},"pageSize":{"type":"integer","example":20},"total":{"type":"integer","example":184},"totalPages":{"type":"integer","example":10},"hasNextPage":{"type":"boolean","example":true},"hasPreviousPage":{"type":"boolean","example":false}}},"PaginatedTransactionsResponse":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","properties":{"transactions":{"type":"array","items":{"$ref":"#/components/schemas/Transaction"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}},"meta":{"$ref":"#/components/schemas/Meta"}}},"ErrorResponse":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"object","properties":{"code":{"type":"string","example":"VALIDATION_FAILED"},"message":{"type":"string"},"details":{"description":"Field-level validation errors or additional conflict context.","oneOf":[{"type":"array","items":{"type":"object"}},{"type":"object"}]}},"required":["code","message"]},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["success","error","meta"]},"HealthResponse":{"type":"object","properties":{"status":{"type":"string","enum":["ok","degraded"]},"database":{"type":"string","enum":["up","down"]},"latencyMs":{"type":"integer"},"uptimeSeconds":{"type":"integer"},"timestamp":{"type":"string","format":"date-time"}}}}}}