{"openapi":"3.1.0","info":{"title":"POST /api/v1/credit/scorecard-versions/{version_id}/validate","version":"1.0.0","description":"Validate a scorecard version"},"servers":[{"url":"https://api.ondayzero.com","description":"Production"}],"paths":{"/api/v1/credit/scorecard-versions/{version_id}/validate":{"post":{"tags":["credit"],"summary":"Validate a scorecard version","description":"Checks every criterion and stores the report on the version: each key has a measurer (a required one must), units agree with the measure, comparators and conditions are well-formed, weights are 0 or more. A draft with no errors becomes `validated`; warnings do not block. `validator_report.messages` addresses each finding to its row and field. It stores the report and can move the draft, so it needs the same permission as any write: a viewer, or a `credit:read` token, is refused with 403.","operationId":"validate_scorecard_version","parameters":[{"name":"version_id","in":"path","required":true,"schema":{"type":"string","title":"Version Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-business-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Business-Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope_ScorecardVersionResponse_"}}}},"400":{"description":"Bad Request - Invalid input","content":{"application/json":{"example":{"error":"validation_error","message":"Validation failed: due_date: Input should be a valid date","errors":{"due_date":"Input should be a valid date"},"code":"GEN_002","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Missing or expired token, an MCP OAuth token used against the REST API, or no access to this business","content":{"application/json":{"example":{"error":"unauthorized","message":"Authentication required","code":"AUTH_001","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Insufficient permissions","content":{"application/json":{"example":{"error":"forbidden","message":"You don't have permission to access this resource","code":"AUTH_002","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - Resource does not exist","content":{"application/json":{"example":{"error":"not_found","message":"Resource not found","code":"NOT_FOUND_001","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":"validation_error","message":"Validation failed: amount: Input should be a valid integer","errors":{"amount":"Input should be a valid integer"},"code":"GEN_002","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"}}}}},"x-fastapi-operation-id":"validate_scorecard_version_api_v1_credit_scorecard_versions__version_id__validate_post"}}},"components":{"schemas":{"GradeBand":{"properties":{"min_score_bps":{"type":"integer","title":"Min Score Bps","description":"Lowest score, in basis points, in the band."},"label":{"type":"string","title":"Label"}},"type":"object","required":["min_score_bps","label"],"title":"GradeBand"},"ManualCheckDefinition":{"properties":{"id":{"type":"string","title":"Id"},"label":{"type":"string","title":"Label"},"applies_when":{"anyOf":[{"$ref":"#/components/schemas/ScorecardCondition"},{"type":"null"}],"description":"Null means it applies to every application."},"evidence_required":{"type":"boolean","title":"Evidence Required","description":"An attachment must accompany the attestation.","default":false},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"policy_reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Policy Reference"}},"type":"object","required":["id","label"],"title":"ManualCheckDefinition","description":"A policy item a person reviews because no data can measure it."},"ScorecardApproval":{"properties":{"approver_name":{"type":"string","title":"Approver Name","description":"Who approved, on the partner's side."},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"attachment_s3_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Attachment S3 Key","description":"The document behind the approval, if uploaded."},"recorded_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recorded By","description":"The signed-in operator who recorded it."},"recorded_by_user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recorded By User Id"},"at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"At"}},"type":"object","required":["approver_name"],"title":"ScorecardApproval","description":"One recorded sign-off."},"ScorecardCondition":{"properties":{"when":{"type":"string","title":"When","description":"`always`, `never`, or a fact about the application: `requested_limit` (whole currency units) or `requested_term_days`."},"op":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Op","description":"gt, gte, lt, lte, eq, ne or between. Absent for always/never."},"value":{"anyOf":[{"type":"number"},{"items":{"type":"number"},"type":"array"},{"type":"null"}],"title":"Value","description":"What the fact is compared with; [low, high] for `between`."}},"type":"object","required":["when"],"title":"ScorecardCondition","description":"When a rule is required, or applies at all."},"ScorecardCriterion":{"properties":{"id":{"type":"string","title":"Id","description":"Stable within the scorecard; survives edits."},"key":{"type":"string","title":"Key","description":"The measure, from `GET /credit/underwriting/measurers`."},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"How the rule reads."},"comparator":{"type":"string","title":"Comparator","description":"gte, gt, lte, lt, eq, ne or between."},"unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Unit","description":"currency, ratio, percent, months, days or count."},"threshold":{"$ref":"#/components/schemas/ScorecardThreshold"},"weight":{"type":"integer","title":"Weight","description":"Points the rule is worth. 0 or more.","default":1},"required":{"$ref":"#/components/schemas/ScorecardCondition","description":"When failing this rule is reported in `required_failed`."},"applies_when":{"anyOf":[{"$ref":"#/components/schemas/ScorecardCondition"},{"type":"null"}],"description":"When the rule is scored at all. Null means always."},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"policy_reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Policy Reference","description":"The policy clause behind the rule, e.g. \"§10\"."},"segments":{"additionalProperties":true,"type":"object","title":"Segments","description":"Per-segment threshold overrides, keyed by segment."}},"type":"object","required":["id","key","comparator","threshold","required"],"title":"ScorecardCriterion","description":"One measured rule."},"ScorecardThreshold":{"properties":{"basis":{"type":"string","title":"Basis","description":"`absolute`: `value` is the threshold. `requested_limit`: the threshold is `value` × the application's requested limit."},"value":{"anyOf":[{"type":"number"},{"items":{"type":"number"},"type":"array"},{"type":"null"}],"title":"Value","description":"A number, or [low, high] for the `between` comparator. Null only on a draft row that is not finished."}},"type":"object","required":["basis"],"title":"ScorecardThreshold","description":"What a measured figure is compared against."},"ScorecardValidateResponse":{"properties":{"valid":{"type":"boolean","title":"Valid"},"errors":{"items":{"type":"string"},"type":"array","title":"Errors"},"warnings":{"items":{"type":"string"},"type":"array","title":"Warnings"},"messages":{"items":{"$ref":"#/components/schemas/ScorecardValidationMessage"},"type":"array","title":"Messages","description":"Every finding with the row and field it concerns."},"criteria_count":{"type":"integer","title":"Criteria Count","default":0},"manual_check_count":{"type":"integer","title":"Manual Check Count","default":0},"measured":{"items":{"type":"string"},"type":"array","title":"Measured"},"unmeasured":{"items":{"type":"string"},"type":"array","title":"Unmeasured"},"required_unmeasured":{"items":{"type":"string"},"type":"array","title":"Required Unmeasured","description":"Required criteria DayZero cannot measure: an assessment can never pass them."},"context_fields_required":{"items":{"type":"string"},"type":"array","title":"Context Fields Required","description":"Facts an assessment must supply for every rule to be scored, e.g. `requested_limit`."}},"type":"object","required":["valid"],"title":"ScorecardValidateResponse","description":"What the validator found. Errors block approval; warnings do not."},"ScorecardValidationMessage":{"properties":{"severity":{"type":"string","enum":["error","warning","info"],"title":"Severity"},"code":{"type":"string","title":"Code","description":"Stable identifier for the kind of finding."},"message":{"type":"string","title":"Message"},"section":{"type":"string","title":"Section","description":"criteria, manual_checks, grade_bands or scoring.","default":"criteria"},"index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Index","description":"Position of the row within its section."},"item_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Item Id","description":"The row's id, if it has one."},"field":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Field","description":"The field at fault, if known."}},"type":"object","required":["severity","code","message"],"title":"ScorecardValidationMessage","description":"One finding, addressed to the row and field it is about."},"ScorecardVersionResponse":{"properties":{"id":{"type":"string","title":"Id"},"scorecard_id":{"type":"string","title":"Scorecard Id"},"version_number":{"type":"integer","title":"Version Number"},"status":{"type":"string","enum":["draft","validated","approved","active","retired"],"title":"Status"},"origin":{"type":"string","title":"Origin","description":"editor, migration (the program's criteria before versions), program_patch (set through PATCH /credit/programs), seed, or sandbox_copy (copied from the live business's active version onto its paired sandbox)."},"is_editable":{"type":"boolean","title":"Is Editable","description":"True while a draft; a version never changes once it is not.","default":false},"criteria_count":{"type":"integer","title":"Criteria Count","default":0},"manual_check_count":{"type":"integer","title":"Manual Check Count","default":0},"change_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Change Note"},"copied_from_version_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Copied From Version Id"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By","description":"Author, as recorded."},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"validated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Validated At"},"valid":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Valid","description":"Result of the last validation. Null if never validated."},"approvals_recorded":{"type":"integer","title":"Approvals Recorded","default":0},"approvals_required":{"type":"integer","title":"Approvals Required","default":1},"approver_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Approver Name"},"approved_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Approved By","description":"The operator who recorded the final approval."},"approved_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Approved At"},"approval_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Approval Note"},"approval_attachment_s3_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Approval Attachment S3 Key"},"activated_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Activated By"},"activated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Activated At"},"retired_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Retired At"},"program_id":{"type":"string","title":"Program Id"},"scorecard_name":{"type":"string","title":"Scorecard Name"},"criteria":{"items":{"$ref":"#/components/schemas/ScorecardCriterion"},"type":"array","title":"Criteria"},"manual_checks":{"items":{"$ref":"#/components/schemas/ManualCheckDefinition"},"type":"array","title":"Manual Checks"},"grade_bands":{"anyOf":[{"items":{"$ref":"#/components/schemas/GradeBand"},"type":"array"},{"type":"null"}],"title":"Grade Bands","description":"Null when the scorecard does not grade."},"scoring":{"$ref":"#/components/schemas/ScoringPolicy"},"validator_report":{"anyOf":[{"$ref":"#/components/schemas/ScorecardValidateResponse"},{"type":"null"}],"description":"Stored from the last validation."},"approvals":{"items":{"$ref":"#/components/schemas/ScorecardApproval"},"type":"array","title":"Approvals"}},"type":"object","required":["id","scorecard_id","version_number","status","origin","program_id","scorecard_name"],"title":"ScorecardVersionResponse","description":"A version and everything it defines."},"ScoringPolicy":{"properties":{"method":{"type":"string","title":"Method","description":"How `score_bps` is computed. One method exists today.","default":"weighted_pass_ratio"},"unmeasured_policy":{"type":"string","title":"Unmeasured Policy","description":"`exclude_from_denominator`: unmeasured criteria are left out of the score. `count_as_zero`: their weight counts as not earned.","default":"exclude_from_denominator"}},"type":"object","title":"ScoringPolicy"},"SuccessEnvelope_ScorecardVersionResponse_":{"properties":{"success":{"type":"boolean","title":"Success","default":true},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"},"data":{"anyOf":[{"$ref":"#/components/schemas/ScorecardVersionResponse"},{"type":"null"}]}},"additionalProperties":true,"type":"object","title":"SuccessEnvelope[ScorecardVersionResponse]"},"ErrorResponse":{"title":"ErrorResponse","type":"object","description":"Envelope returned by every non-2xx response. Branch on `code` (stable) rather than `message` (human-readable, may change).","required":["error","message","code"],"properties":{"error":{"type":"string","description":"Error category: `validation_error`, `unauthorized`, `forbidden`, `not_found`, `conflict`, `rate_limited`, `server_error`, or `service_unavailable`."},"message":{"type":"string","description":"Human-readable explanation, safe to show to end users."},"code":{"type":"string","description":"Stable machine-readable code in `CATEGORY_NNN` form (e.g. `NOT_FOUND_006`, `AUTH_010`, `GEN_002`)."},"request_id":{"type":"string","description":"Correlation id for support requests. Echoes the `x-request-id` request header when one was supplied."},"errors":{"type":"object","additionalProperties":{"type":"string"},"description":"Field-level validation messages keyed by field name. Present on 400/422 validation failures only."}}}},"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Token","description":"API token authentication. Format: `Bearer dz_...`"}}},"security":[{"BearerAuth":[]}]}