{
  "openapi": "3.1.0",
  "info": {
    "title": "Job13 API",
    "version": "1.0.0",
    "summary": "OSHA recordability determinations under 29 CFR Part 1904, with citations.",
    "description": "Job13 decides whether a workplace injury is OSHA recordable under 29 CFR 1904 — quoting the exact provision behind every answer — then keeps the 300 Log, 300A summary and ITA export that follow from it.\n\n**Authentication.** Every endpoint takes `Authorization: Bearer <key>`. The public sandbox key `lb_test_sandbox` requires no signup and is throttled to roughly 25 requests per 24 hours per IP. That counter lives in each server instance's memory, so the effective ceiling scales with concurrency and resets on redeploy — treat it as a courtesy limit, not a quota you can rely on.\n\n**Disclaimer.** Job13 is an administrative classification aid, not legal advice. It quotes 29 CFR Part 1904 as published by the eCFR and never asserts a conclusion the regulation does not support; where the rule requires judgement it returns needs_review rather than guessing. Recordkeeping duties remain the employer's.",
    "contact": {
      "name": "Job13 support",
      "email": "hello@job13.com",
      "url": "https://www.job13.com/docs/api"
    },
    "license": {
      "name": "Regulation text: US Government work, public domain",
      "url": "https://www.ecfr.gov/current/title-29/part-1904"
    }
  },
  "servers": [
    {
      "url": "https://www.job13.com/api/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Determination",
      "description": "Decide a case."
    },
    {
      "name": "Rates",
      "description": "300A incidence rates."
    },
    {
      "name": "Reference",
      "description": "The regulation and its vocabularies."
    }
  ],
  "paths": {
    "/": {
      "get": {
        "operationId": "getDiscovery",
        "summary": "Discovery — every endpoint, the treatment vocabulary and the rule edition",
        "description": "The first call an agent should make. Returns the endpoint list, the current 29 CFR Part 1904 edition and retrieval date, and where to find the enumerated vocabularies, so a caller can bootstrap without hard-coding anything from these docs.",
        "tags": [
          "Reference"
        ],
        "responses": {
          "200": {
            "description": "Service description and endpoint index.",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds at which the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/recordability": {
      "post": {
        "operationId": "checkRecordability",
        "summary": "Decide whether a case is OSHA recordable",
        "description": "Runs the full 29 CFR Part 1904 determination and returns the verdict with a verbatim citation on every decisive step. Returns `needs_review` with HTTP 200 when the rule requires a fact you have not established.",
        "tags": [
          "Determination"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Opaque caller-generated key. Replaying the same key with the same request returns the byte-identical original response plus `Idempotency-Replayed: true`. On POST, reusing a key with a DIFFERENT body is a 409 — two different mutations under one key is a client bug. On GET there is no side effect to deduplicate, so reusing a key for a different query simply answers the new query and re-arms the key."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IncidentInput"
              },
              "examples": {
                "sutures": {
                  "summary": "Sutures — recordable",
                  "value": {
                    "occurredInWorkEnvironment": "yes",
                    "isNewCase": "yes",
                    "treatments": [
                      "sutures_staples_or_surgical_glue"
                    ]
                  }
                },
                "butterfly": {
                  "summary": "Butterfly bandage only — not recordable",
                  "value": {
                    "occurredInWorkEnvironment": "yes",
                    "isNewCase": "yes",
                    "resultedInDeath": "no",
                    "daysAwayFromWork": "no",
                    "restrictedWorkOrTransfer": "no",
                    "lossOfConsciousness": "no",
                    "significantDiagnosis": "no",
                    "treatments": [
                      "wound_coverings_butterfly_steristrips"
                    ]
                  }
                },
                "unknownTreatment": {
                  "summary": "Unclassifiable treatment — needs_review",
                  "value": {
                    "occurredInWorkEnvironment": "yes",
                    "isNewCase": "yes",
                    "resultedInDeath": "no",
                    "daysAwayFromWork": "no",
                    "restrictedWorkOrTransfer": "no",
                    "lossOfConsciousness": "no",
                    "significantDiagnosis": "no",
                    "treatments": [
                      "other_unlisted"
                    ]
                  }
                },
                "hearingLoss": {
                  "summary": "Hearing loss — the two-prong 1904.10 test",
                  "value": {
                    "occurredInWorkEnvironment": "yes",
                    "isNewCase": "yes",
                    "specificCase": "hearing_loss",
                    "treatments": [
                      "none"
                    ],
                    "hearingLoss": {
                      "standardThresholdShift": "yes",
                      "totalHearingLevelAtLeast25dB": "no"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The determination, including needs_review outcomes.",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds at which the window resets."
              },
              "Idempotency-Replayed": {
                "schema": {
                  "type": "boolean"
                },
                "description": "`true` when this body came from the replay cache rather than a fresh computation. Present on every response to a request that carried an Idempotency-Key."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecordabilityResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/rates": {
      "post": {
        "operationId": "computeRates",
        "summary": "Compute TRIR, DART and DAFW",
        "description": "Incidence rates on the standard 200,000-hour base. With `hoursWorked` of 0 the rates are null, never 0, and `note` explains why.",
        "tags": [
          "Rates"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Opaque caller-generated key. Replaying the same key with the same request returns the byte-identical original response plus `Idempotency-Replayed: true`. On POST, reusing a key with a DIFFERENT body is a 409 — two different mutations under one key is a client bug. On GET there is no side effect to deduplicate, so reusing a key for a different query simply answers the new query and re-arms the key."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RateRequest"
              },
              "examples": {
                "typical": {
                  "summary": "A year with hours recorded",
                  "value": {
                    "hoursWorked": 400000,
                    "totalRecordableCases": 6,
                    "casesWithDaysAway": 2,
                    "casesWithRestrictionOrTransfer": 1
                  }
                },
                "noHours": {
                  "summary": "No hours — rates are null, not zero",
                  "value": {
                    "hoursWorked": 0,
                    "totalRecordableCases": 3
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The computed rates.",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds at which the window resets."
              },
              "Idempotency-Replayed": {
                "schema": {
                  "type": "boolean"
                },
                "description": "`true` when this body came from the replay cache rather than a fresh computation. Present on every response to a request that carried an Idempotency-Key."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/rule/{section}": {
      "get": {
        "operationId": "getRuleSection",
        "summary": "Read one section of 29 CFR Part 1904 verbatim",
        "tags": [
          "Reference"
        ],
        "parameters": [
          {
            "name": "section",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "1904.0",
                "1904.1",
                "1904.2",
                "1904.3",
                "1904.4",
                "1904.5",
                "1904.6",
                "1904.7",
                "1904.8",
                "1904.9",
                "1904.10",
                "1904.11",
                "1904.13",
                "1904.29",
                "1904.30",
                "1904.31",
                "1904.32",
                "1904.33",
                "1904.34",
                "1904.35",
                "1904.36",
                "1904.37",
                "1904.38",
                "1904.39",
                "1904.40",
                "1904.41",
                "1904.42",
                "1904.43",
                "1904.44",
                "1904.45",
                "1904.46"
              ]
            },
            "example": "1904.7",
            "description": "Section id. A 404 returns every valid id in `known_keys`."
          }
        ],
        "responses": {
          "200": {
            "description": "The section, with its paragraphs and permanent eCFR link.",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds at which the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/first-aid-list": {
      "get": {
        "operationId": "listFirstAid",
        "summary": "The exhaustive 14-item first-aid list, verbatim",
        "description": "1904.7(b)(5)(ii)(A)-(N). The list is EXHAUSTIVE — anything not on it is medical treatment beyond first aid.",
        "tags": [
          "Reference"
        ],
        "responses": {
          "200": {
            "description": "The 14 items with their paragraph references.",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds at which the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/exemptions": {
      "get": {
        "operationId": "checkExemption",
        "summary": "1904.2 partial-exemption status for a NAICS code",
        "tags": [
          "Reference"
        ],
        "parameters": [
          {
            "name": "naics",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^\\d{2,6}$"
            },
            "example": "4412",
            "description": "Omit to get the full Appendix A list instead of a determination."
          }
        ],
        "responses": {
          "200": {
            "description": "Exemption status, or the full Appendix A list.",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds at which the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/interpretations": {
      "get": {
        "operationId": "searchInterpretations",
        "summary": "Search OSHA's Letters of Interpretation for Part 1904",
        "description": "OSHA's own published answers to specific recordkeeping questions, verbatim and searchable. Use this rather than recalling how OSHA has ruled on a fact pattern: every result carries the letter's date, the sections it cites and a permanent osha.gov link. Bodies are omitted from results — fetch a letter by slug for the full text.",
        "tags": [
          "Interpretations"
        ],
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "paraffin wax",
            "description": "Free text matched against titles and bodies; title matches rank highest."
          },
          {
            "name": "section",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "example": "1904.7",
            "description": "Restrict to letters citing this section of Part 1904."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20
            },
            "description": "Maximum letters to return."
          }
        ],
        "responses": {
          "200": {
            "description": "Matching letters with excerpts and source URLs, plus corpus provenance. A zero-result response says so explicitly rather than implying OSHA has never addressed the topic.",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds at which the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/interpretations/{slug}": {
      "get": {
        "operationId": "getInterpretation",
        "summary": "One Letter of Interpretation, verbatim",
        "description": "The full text of one letter as OSHA published it — not a summary, because a paraphrase of a regulatory interpretation is not something anyone should cite.",
        "tags": [
          "Interpretations"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "2024-10-22",
            "description": "OSHA's own date-based path segment. Obtain it from the search endpoint rather than constructing it."
          }
        ],
        "responses": {
          "200": {
            "description": "The letter, with OSHA's standing caveat and its source URL.",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds at which the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/treatments": {
      "get": {
        "operationId": "listTreatments",
        "summary": "The treatment vocabulary accepted by /recordability",
        "description": "Free-text treatments are rejected on purpose. Call this to discover every valid code, its classification and the rationale in the rule's own terms.",
        "tags": [
          "Reference"
        ],
        "responses": {
          "200": {
            "description": "Every treatment code with kind and rationale.",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests allowed per window."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Requests left."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix seconds at which the window resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Send `Authorization: Bearer lb_test_sandbox` to use the public sandbox key. No signup. Best-effort limit of roughly 25 requests per 24 hours per IP — the counter is per server instance, so it is a courtesy throttle, not a guarantee."
      }
    },
    "schemas": {
      "Citation": {
        "type": "object",
        "description": "A verbatim quotation of the regulation. Never paraphrased.",
        "required": [
          "section",
          "quote",
          "url"
        ],
        "properties": {
          "section": {
            "type": "string",
            "examples": [
              "1904.7"
            ]
          },
          "paragraph": {
            "type": "string",
            "examples": [
              "1904.7(b)(5)"
            ]
          },
          "quote": {
            "type": "string",
            "description": "Verbatim eCFR text."
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "DecisionStep": {
        "type": "object",
        "description": "One auditable step of the determination, in evaluation order.",
        "required": [
          "key",
          "question",
          "answer",
          "decisive",
          "citations"
        ],
        "properties": {
          "key": {
            "type": "string",
            "examples": [
              "general_criteria"
            ]
          },
          "question": {
            "type": "string"
          },
          "answer": {
            "type": "string"
          },
          "decisive": {
            "type": "boolean",
            "description": "True when this step decided the outcome on its own."
          },
          "citations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Citation"
            }
          }
        }
      },
      "IncidentInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "occurredInWorkEnvironment",
          "isNewCase"
        ],
        "properties": {
          "description": {
            "type": "string",
            "description": "Free-text label for the case. Carried through for the audit trail only."
          },
          "occurredInWorkEnvironment": {
            "type": "string",
            "enum": [
              "yes",
              "no",
              "unknown"
            ],
            "description": "1904.5(a): did an event or exposure in the work environment cause or contribute to the injury or illness? Use \"unknown\" when the fact is genuinely not established yet — Job13 returns needs_review rather than assuming \"no\"."
          },
          "exceptions": {
            "type": "array",
            "description": "Roman numerals of any 1904.5(b)(2) work-relatedness exceptions the employer asserts. Any one of them defeats work-relatedness and makes the case not recordable.",
            "items": {
              "type": "string",
              "enum": [
                "i",
                "ii",
                "iii",
                "iv",
                "v",
                "vi",
                "vii",
                "viii",
                "ix"
              ]
            }
          },
          "isNewCase": {
            "type": "string",
            "enum": [
              "yes",
              "no",
              "unknown"
            ],
            "description": "1904.6: is this a new case, as opposed to the continuation of a previously recorded injury or illness? Use \"unknown\" when the fact is genuinely not established yet — Job13 returns needs_review rather than assuming \"no\"."
          },
          "resultedInDeath": {
            "type": "string",
            "enum": [
              "yes",
              "no",
              "unknown"
            ],
            "description": "1904.7(a): did the case result in death? Use \"unknown\" when the fact is genuinely not established yet — Job13 returns needs_review rather than assuming \"no\"."
          },
          "daysAwayFromWork": {
            "type": "string",
            "enum": [
              "yes",
              "no",
              "unknown"
            ],
            "description": "1904.7(a): did the case result in one or more days away from work? Use \"unknown\" when the fact is genuinely not established yet — Job13 returns needs_review rather than assuming \"no\"."
          },
          "restrictedWorkOrTransfer": {
            "type": "string",
            "enum": [
              "yes",
              "no",
              "unknown"
            ],
            "description": "1904.7(a): did the case result in restricted work or transfer to another job? Use \"unknown\" when the fact is genuinely not established yet — Job13 returns needs_review rather than assuming \"no\"."
          },
          "lossOfConsciousness": {
            "type": "string",
            "enum": [
              "yes",
              "no",
              "unknown"
            ],
            "description": "1904.7(a): did the case involve loss of consciousness? Use \"unknown\" when the fact is genuinely not established yet — Job13 returns needs_review rather than assuming \"no\"."
          },
          "significantDiagnosis": {
            "type": "string",
            "enum": [
              "yes",
              "no",
              "unknown"
            ],
            "description": "1904.7(b)(7): was a significant injury or illness (e.g. cancer, chronic irreversible disease, fractured or cracked bone, punctured eardrum) diagnosed by a physician or other licensed health care professional? Use \"unknown\" when the fact is genuinely not established yet — Job13 returns needs_review rather than assuming \"no\"."
          },
          "treatments": {
            "type": "array",
            "description": "Treatments actually provided, from the fixed vocabulary. The first-aid list at 1904.7(b)(5)(ii) is EXHAUSTIVE: anything not on it is medical treatment and makes the case recordable. Free text is rejected — use other_unlisted if nothing matches, which forces needs_review. Defaults to [\"none\"].",
            "items": {
              "type": "string",
              "enum": [
                "nonprescription_medication_nonprescription_strength",
                "tetanus_immunization",
                "cleaning_flushing_soaking_surface_wounds",
                "wound_coverings_butterfly_steristrips",
                "hot_or_cold_therapy",
                "non_rigid_support",
                "temporary_immobilization_for_transport",
                "drilling_nail_or_draining_blister",
                "eye_patches",
                "eye_foreign_body_irrigation_or_swab",
                "splinter_removal_simple_means",
                "finger_guards",
                "massage",
                "drinking_fluids_for_heat_stress",
                "observation_or_counseling_only",
                "diagnostic_procedure_only",
                "prescription_medication",
                "nonprescription_medication_prescription_strength",
                "sutures_staples_or_surgical_glue",
                "rigid_immobilization_cast_or_splint",
                "physical_therapy_or_chiropractic",
                "immunization_other_than_tetanus",
                "wound_closure_other_than_butterfly",
                "removal_of_foreign_body_from_eye_by_other_means",
                "surgical_debridement",
                "oxygen_or_iv_administered",
                "other_unlisted",
                "none"
              ]
            }
          },
          "specificCase": {
            "type": "string",
            "enum": [
              "needlestick_or_sharps",
              "tuberculosis",
              "hearing_loss",
              "medical_removal",
              "musculoskeletal_disorder"
            ],
            "description": "Specific-case handling under 1904.8-1904.12. Needlestick, TB and medical removal are recordable on their own terms; hearing loss uses the two-part 1904.10 test; an MSD is recorded only if it meets the ordinary 1904.7 criteria."
          },
          "hearingLoss": {
            "type": "object",
            "additionalProperties": false,
            "description": "Required when specificCase is hearing_loss. Both prongs must be yes to record.",
            "properties": {
              "standardThresholdShift": {
                "type": "string",
                "enum": [
                  "yes",
                  "no",
                  "unknown"
                ],
                "description": "A Standard Threshold Shift: an average shift of 10 dB or more at 2000, 3000 and 4000 Hz in one ear. Use \"unknown\" when the fact is genuinely not established yet — Job13 returns needs_review rather than assuming \"no\"."
              },
              "totalHearingLevelAtLeast25dB": {
                "type": "string",
                "enum": [
                  "yes",
                  "no",
                  "unknown"
                ],
                "description": "Total hearing level of 25 dB or more above audiometric zero, averaged at the same frequencies, in the SAME ear as the shift. Use \"unknown\" when the fact is genuinely not established yet — Job13 returns needs_review rather than assuming \"no\"."
              }
            }
          },
          "employer": {
            "type": "object",
            "additionalProperties": false,
            "description": "Optional context for the 1904.1 / 1904.2 partial exemptions.",
            "properties": {
              "peakEmployeesLastYear": {
                "type": "number",
                "description": "Peak employment across the whole company at any time last calendar year. 10 or fewer means partially exempt under 1904.1."
              },
              "naics": {
                "type": "string",
                "description": "Establishment NAICS code, for the 1904.2 Appendix A industry exemption."
              }
            }
          }
        }
      },
      "RecordabilityResult": {
        "type": "object",
        "required": [
          "verdict",
          "summary",
          "criteriaMet",
          "steps",
          "citations",
          "unresolved",
          "caveats"
        ],
        "properties": {
          "verdict": {
            "type": "string",
            "enum": [
              "recordable",
              "not_recordable",
              "needs_review"
            ],
            "description": "needs_review is a first-class outcome returned with HTTP 200, not an error: the rule requires a fact that has not been established. Branch on this field, never on the status code."
          },
          "summary": {
            "type": "string"
          },
          "criteriaMet": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Which 1904.7(a) criteria the case meets."
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DecisionStep"
            }
          },
          "citations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Citation"
            }
          },
          "unresolved": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Non-empty whenever verdict is needs_review. What you must establish."
          },
          "caveats": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "exemptionNote": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set when 1904.1 / 1904.2 partial exemption may apply."
          },
          "input_echo": {
            "type": "object",
            "description": "The parsed IncidentInput the engine actually ran on, echoed so a caller can confirm what reached the engine rather than what it meant to send. Often the largest field in the payload.",
            "additionalProperties": true
          },
          "rule_edition": {
            "type": "string",
            "examples": [
              "2026-01-01"
            ]
          },
          "rule_retrieved": {
            "type": "string",
            "description": "Date the Part 1904 corpus was pulled from the eCFR.",
            "examples": [
              "2026-07-19"
            ]
          },
          "rule_source": {
            "type": "string",
            "format": "uri",
            "description": "eCFR URL the corpus was parsed from."
          },
          "disclaimer": {
            "type": "string",
            "examples": [
              "Job13 is an administrative classification aid, not legal advice. It quotes 29 CFR Part 1904 as published by the eCFR and never asserts a conclusion the regulation does not support; where the rule requires judgement it returns needs_review rather than guessing. Recordkeeping duties remain the employer's."
            ]
          }
        }
      },
      "RateRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "hoursWorked"
        ],
        "properties": {
          "hoursWorked": {
            "type": "number",
            "minimum": 0,
            "description": "Total hours actually worked by all employees during the year. Pass 0 if unknown — the rates come back null with an explanation, never a flattering 0."
          },
          "totalRecordableCases": {
            "type": "number",
            "minimum": 0,
            "description": "Total recordable cases."
          },
          "casesWithDaysAway": {
            "type": "number",
            "minimum": 0,
            "description": "Cases with days away from work."
          },
          "casesWithRestrictionOrTransfer": {
            "type": "number",
            "minimum": 0,
            "description": "Cases with job transfer or restriction but no days away."
          }
        }
      },
      "RateResult": {
        "type": "object",
        "required": [
          "trir",
          "dart",
          "dafw",
          "note"
        ],
        "properties": {
          "trir": {
            "type": [
              "number",
              "null"
            ],
            "description": "Total Recordable Incident Rate per 200,000 hours. NULL — not 0 — when hoursWorked is 0, because 0 would read as a perfect safety record."
          },
          "dart": {
            "type": [
              "number",
              "null"
            ],
            "description": "Days Away, Restricted or Transferred rate."
          },
          "dafw": {
            "type": [
              "number",
              "null"
            ],
            "description": "Days-away-from-work-only rate."
          },
          "basis_hours": {
            "type": "number",
            "examples": [
              200000
            ]
          },
          "note": {
            "type": "string",
            "description": "Explains a null result in words as well as in type."
          },
          "formulas": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "citations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Citation"
            }
          },
          "inputs": {
            "type": "object",
            "description": "The rate inputs as parsed, echoed back.",
            "additionalProperties": true
          },
          "rule_edition": {
            "type": "string",
            "examples": [
              "2026-01-01"
            ]
          },
          "rule_retrieved": {
            "type": "string",
            "description": "Date the Part 1904 corpus was pulled from the eCFR.",
            "examples": [
              "2026-07-19"
            ]
          },
          "rule_source": {
            "type": "string",
            "format": "uri",
            "description": "eCFR URL the corpus was parsed from."
          },
          "disclaimer": {
            "type": "string",
            "examples": [
              "Job13 is an administrative classification aid, not legal advice. It quotes 29 CFR Part 1904 as published by the eCFR and never asserts a conclusion the regulation does not support; where the rule requires judgement it returns needs_review rather than guessing. Recordkeeping duties remain the employer's."
            ]
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "docs_url"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "invalid_request",
                  "unauthorized",
                  "rate_limited",
                  "not_found",
                  "method_not_allowed",
                  "idempotency_key_reuse",
                  "internal_error"
                ]
              },
              "message": {
                "type": "string"
              },
              "docs_url": {
                "type": "string",
                "format": "uri"
              },
              "field": {
                "type": "string",
                "description": "Dotted path of the offending field."
              },
              "known_keys": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Every value the field accepts. Present whenever the field is an enum, so a caller can self-correct without human intervention."
              }
            }
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing, malformed or unrecognised bearer key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "InvalidRequest": {
        "description": "Validation failed. When the offending field is an enum the body carries `known_keys`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "No such resource. Carries `known_keys` with the valid identifiers.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Sandbox key limit reached for this IP. See `Retry-After`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  },
  "externalDocs": {
    "description": "Full documentation",
    "url": "https://www.job13.com/docs/api"
  }
}