{
  "openapi": "3.1.0",
  "info": {
    "title": "ShieldSphere Integration API",
    "version": "1.0.0",
    "description": "Read your ShieldSphere safety data and send records in from other systems.\n\n**Direction is fixed.** External systems may create records and read data. They can never update or delete — correcting a record happens in ShieldSphere, so there is no ambiguity about which system owns the truth.\n\n**Authentication.** Send your key as `Authorization: Bearer <key>` or `X-API-Key: <key>`. Keys are created in Settings → Developers by an organization owner. Requires the Integration API entitlement (enterprise).\n\n**Errors** all share one shape: `{ \"error\": { \"code\", \"message\", \"request_id\" } }`. Branch on `code`, never on `message`. Quote `request_id` in support requests — it identifies the exact request in our logs.\n\n**Rate limits** are per key: 120 reads/min and 60 writes/min by default, in separate buckets. Responses carry `X-RateLimit-Limit` and `X-RateLimit-Remaining`.\n\n**Idempotency.** Send an `Idempotency-Key` header on every write. A retry with the same key returns the original result instead of creating a second record. This matters most for incidents: OSHA case numbers are sequential, so a duplicate would consume a number in a federally-required log."
  },
  "servers": [
    {
      "url": "https://app.shieldsphere.ai",
      "description": "Production"
    }
  ],
  "security": [
    {
      "BearerAuth": []
    },
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Locations",
      "description": "Sites and company profiles"
    },
    {
      "name": "Employees"
    },
    {
      "name": "Incidents",
      "description": "OSHA recordable incidents"
    },
    {
      "name": "Assets"
    },
    {
      "name": "Chemicals",
      "description": "SDS chemical inventory"
    },
    {
      "name": "Observations",
      "description": "Submit a hazard photo for AI analysis"
    },
    {
      "name": "Scans",
      "description": "Hazard scan results"
    },
    {
      "name": "Jobs",
      "description": "Poll asynchronous work"
    }
  ],
  "paths": {
    "/api/v1/profiles": {
      "get": {
        "tags": [
          "Locations"
        ],
        "summary": "List locations",
        "description": "Call this first — most other endpoints need a `location_id`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/UpdatedSince"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of locations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Location"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/employees": {
      "get": {
        "tags": [
          "Employees"
        ],
        "summary": "List employees",
        "description": "Defaults to active employees. Terminated staff are excluded unless you ask for them.",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "inactive",
                "terminated",
                "all"
              ]
            },
            "description": "Defaults to `active`."
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/UpdatedSince"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of employees",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Employee"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          }
        }
      },
      "post": {
        "tags": [
          "Employees"
        ],
        "summary": "Create an employee",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "full_name"
                ],
                "properties": {
                  "full_name": {
                    "type": "string",
                    "maxLength": 255
                  },
                  "location_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid",
                    "description": "Optional — an employee can be org-level."
                  },
                  "badge_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Payroll or badge number. Not the API id."
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "email"
                  },
                  "department": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "job_title": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "hire_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/Created"
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "409": {
            "description": "An employee with that email already exists. Duplicates are flagged, never merged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/incidents": {
      "get": {
        "tags": [
          "Incidents"
        ],
        "summary": "List incidents",
        "description": "Recordable incidents only by default. Deleted records and submissions still awaiting review are never returned. Privacy cases have the employee name withheld, as required by 29 CFR 1904.29.",
        "parameters": [
          {
            "name": "report_type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "recordable",
                "near_miss",
                "all"
              ]
            },
            "description": "Defaults to `recordable`."
          },
          {
            "name": "year",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/UpdatedSince"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of incidents",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Incident"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Incidents"
        ],
        "summary": "Record an incident",
        "description": "Creates a recordable incident. Partial 301 data is accepted — 29 CFR 1904.29 allows seven days to complete the form, and the rest can be filled in in ShieldSphere.\n\n**Send an Idempotency-Key.** Case numbers are sequential in the OSHA 300 log, so a duplicate consumes a number that cannot be reclaimed.\n\nNear-miss reports, the 301 medical block, and day-count overrides are not accepted here — those belong to the in-app workflow.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "location_id",
                  "date_of_injury",
                  "description_of_injury",
                  "employee_name",
                  "classification"
                ],
                "properties": {
                  "location_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "date_of_injury": {
                    "type": "string",
                    "format": "date"
                  },
                  "description_of_injury": {
                    "type": "string"
                  },
                  "employee_name": {
                    "type": "string",
                    "description": "Required. For a privacy case, send the real name — we withhold it on output."
                  },
                  "classification": {
                    "type": "string",
                    "enum": [
                      "injury",
                      "skin_disorder",
                      "respiratory_condition",
                      "poisoning",
                      "hearing_loss",
                      "other_illness"
                    ],
                    "description": "Required. The OSHA 300 column M classification."
                  },
                  "employee_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid",
                    "description": "A ShieldSphere employee id, if the person is on the roster."
                  },
                  "job_title": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "where_event_occurred": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "injury_nature": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "body_parts": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "resulted_in_death": {
                    "type": "boolean",
                    "default": false
                  },
                  "days_away_flag": {
                    "type": "boolean",
                    "default": false,
                    "description": "Column H — whether days away occurred. The COUNT is num_days_away."
                  },
                  "job_transfer_or_restriction": {
                    "type": "boolean",
                    "default": false
                  },
                  "num_days_away": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "num_days_restricted": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "is_privacy_case": {
                    "type": "boolean",
                    "default": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "case_number": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        }
      }
    },
    "/api/v1/assets": {
      "get": {
        "tags": [
          "Assets"
        ],
        "summary": "List assets",
        "description": "Retired assets are excluded by default.",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "out_of_service",
                "retired",
                "all"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/UpdatedSince"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of assets",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Asset"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Assets"
        ],
        "summary": "Create an asset",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "location_id",
                  "name"
                ],
                "properties": {
                  "location_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string"
                  },
                  "asset_type": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "identifier": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Serial or tag number."
                  },
                  "manufacturer": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "model": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "location_label": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "check_interval_unit": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "days",
                      "weeks",
                      "months",
                      "years",
                      null
                    ]
                  },
                  "check_interval_value": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/Created"
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          }
        }
      }
    },
    "/api/v1/chemicals": {
      "get": {
        "tags": [
          "Chemicals"
        ],
        "summary": "List chemical inventory",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/UpdatedSince"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of chemicals",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Chemical"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Chemicals"
        ],
        "summary": "Add a chemical",
        "description": "Creates the inventory record. Attaching an SDS document is a separate in-app flow; a chemical without one is a normal state.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "location_id",
                  "chemical_name"
                ],
                "properties": {
                  "location_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "chemical_name": {
                    "type": "string"
                  },
                  "manufacturer": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "sub_location": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "quantity": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "received_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/Created"
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "422": {
            "description": "The SDS module is not enabled for that location.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/observations": {
      "post": {
        "tags": [
          "Observations"
        ],
        "summary": "Submit a hazard observation for analysis",
        "description": "Send an observation with a photo and we analyze it against OSHA standards.\n\nReturns **202** with a `job_id` — analysis takes longer than a request allows. Poll `GET /api/v1/jobs/{job_id}` until `status` is `succeeded` or `dead`, then read the findings from `result`.\n\nSend `source` and `external_id` and we deduplicate automatically, so replaying the same observation will not run (or bill) a second analysis.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "image_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Public https URL of the photo. We fetch it server-side and REDIRECTS ARE NOT FOLLOWED — the URL must return the image bytes on the first request, because a redirect can point somewhere the original URL did not. Max 20MB; JPEG, PNG, GIF, or WebP. Provide this or storage_path, not both."
                  },
                  "storage_path": {
                    "type": "string",
                    "description": "A path previously returned by this API."
                  },
                  "framework": {
                    "type": "string",
                    "enum": [
                      "general_industry",
                      "construction"
                    ],
                    "default": "general_industry"
                  },
                  "state": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Two-letter US state. Required unless the location is non-US."
                  },
                  "location_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid",
                    "description": "Which site the observation belongs to. Omit and the resulting scan is unassigned, which means it will not appear in any location's history."
                  },
                  "context": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 4000,
                    "description": "What the observer noted."
                  },
                  "source": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Your system's name, e.g. \"procore\"."
                  },
                  "external_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Your record id, for deduplication."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued for analysis",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "job_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "status": {
                          "type": "string"
                        },
                        "deduplicated": {
                          "type": "boolean"
                        },
                        "_links": {
                          "type": "object",
                          "properties": {
                            "self": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "200": {
            "description": "Already submitted — the same job is returned."
          },
          "400": {
            "$ref": "#/components/responses/ValidationFailed"
          }
        }
      }
    },
    "/api/v1/jobs/{jobId}": {
      "get": {
        "tags": [
          "Jobs"
        ],
        "summary": "Get job status",
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Job status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Job"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/scans": {
      "get": {
        "tags": [
          "Scans"
        ],
        "summary": "List hazard scans",
        "parameters": [
          {
            "name": "overall_risk_level",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "low",
                "medium",
                "high",
                "critical"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "$ref": "#/components/parameters/UpdatedSince"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of scans",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Scan"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/scans/{scanId}": {
      "get": {
        "tags": [
          "Scans"
        ],
        "summary": "Get a scan with its findings",
        "parameters": [
          {
            "name": "scanId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The scan, including violations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/Scan"
                        },
                        {
                          "type": "object",
                          "properties": {
                            "violations": {
                              "type": "array",
                              "items": {
                                "$ref": "#/components/schemas/Violation"
                              }
                            }
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/ingest": {
      "post": {
        "tags": [
          "Ingest"
        ],
        "summary": "Send your own rows, in your own format",
        "description": "Post the rows your system already produces - your column names, your vocabulary - and we translate them using a mapping we set up with you.\n\nThis is the alternative to transforming your export into our schema yourself. If you would rather send our shape directly, use the per-resource endpoints such as `POST /api/v1/inspections`.\n\n**Partial success.** One bad row does not cost the rest. Every row comes back in either `accepted` or `rejected`, addressed by its `index` in the array you sent and by its `client_key` where it has one, so you can re-send exactly what failed.\n\n**Nothing is guessed.** An unrecognized severity, an ambiguous date, or a site name that is not in the mapping rejects that row rather than being defaulted - a mis-mapped column that silently files every row as medium looks like success and corrupts your trend data. Send dates as `YYYY-MM-DD`: `03/04/2026` is March in the US and April elsewhere, so it is refused rather than assumed.\n\n**Safe to re-send.** Rows are deduplicated on a stable key, so a nightly sync that re-sends yesterday's rows will not create second copies. A row that already existed comes back in `accepted` with `deduplicated: true`.\n\nRequires the write scope for the module you are sending (for example `inspections:write`).",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "mapping_id",
                  "module",
                  "rows"
                ],
                "properties": {
                  "mapping_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The saved mapping to apply. We provide this when your integration is set up."
                  },
                  "module": {
                    "type": "string",
                    "enum": [
                      "inspections",
                      "incidents",
                      "employees",
                      "assets",
                      "chemicals"
                    ],
                    "description": "Which kind of record these rows become."
                  },
                  "rows": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 500,
                    "description": "Your rows, as JSON objects keyed by YOUR column names. Up to 500 per request; send more as several batches. For inspections, one row is one FINDING - rows sharing an inspection date and a site are grouped into a single inspection, which is how a running inspection log exports.",
                    "items": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  },
                  "source_app": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Your system's name, recorded on each record for provenance. Defaults to `integration_api`."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch processed. Check `accepted` and `rejected` - a 200 does not mean every row landed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IngestResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request, unknown module, or empty rows.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the write scope for this module.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such mapping for this organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "More than 500 rows.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The mapping cannot be used for this module - a required field is unmapped, or it points at a location that no longer exists.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Authorization: Bearer ss_live_..."
      },
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      }
    },
    "parameters": {
      "Limit": {
        "name": "limit",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 50
        }
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "Pass `next_cursor` from the previous page. Opaque — do not construct one."
      },
      "UpdatedSince": {
        "name": "updated_since",
        "in": "query",
        "schema": {
          "type": "string",
          "format": "date-time"
        },
        "description": "Only records changed at or after this time. Use for incremental sync."
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "schema": {
          "type": "string",
          "maxLength": 255
        },
        "description": "A unique string per logical write. Retrying with the same key returns the original result instead of creating a duplicate."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "validation_failed",
                  "invalid_request",
                  "unauthenticated",
                  "forbidden",
                  "insufficient_scope",
                  "entitlement_required",
                  "generation_paused",
                  "not_found",
                  "conflict",
                  "payload_too_large",
                  "unprocessable",
                  "rate_limited",
                  "internal_error",
                  "upstream_unavailable"
                ]
              },
              "message": {
                "type": "string"
              },
              "details": {},
              "request_id": {
                "type": "string",
                "format": "uuid"
              }
            },
            "required": [
              "code",
              "message",
              "request_id"
            ]
          }
        }
      },
      "Location": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "company_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "industry": {
            "type": [
              "string",
              "null"
            ]
          },
          "employee_band": {
            "type": [
              "string",
              "null"
            ]
          },
          "type": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ]
          },
          "state": {
            "type": [
              "string",
              "null"
            ]
          },
          "city": {
            "type": [
              "string",
              "null"
            ]
          },
          "location_group": {
            "type": [
              "string",
              "null"
            ]
          },
          "location_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_corporate": {
            "type": "boolean",
            "description": "A corporate umbrella. Cannot accept records — use an operational site."
          },
          "parent_profile_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Employee": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "location_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "full_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "badge_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Payroll or badge number. Not unique."
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "department": {
            "type": [
              "string",
              "null"
            ]
          },
          "job_title": {
            "type": [
              "string",
              "null"
            ]
          },
          "hire_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "status": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Incident": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "location_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "case_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "The OSHA 300 log case number. Sequential per location per year, so it is NOT unique across your organization — two locations both have a 2026-001. Use `id` as the identifier and treat case_number as a display label."
          },
          "report_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "employee_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "\"Privacy Case\" when withheld under 29 CFR 1904.29."
          },
          "employee_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "job_title": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_privacy_case": {
            "type": "boolean"
          },
          "date_of_injury": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "where_event_occurred": {
            "type": [
              "string",
              "null"
            ]
          },
          "description_of_injury": {
            "type": [
              "string",
              "null"
            ]
          },
          "injury_nature": {
            "type": [
              "string",
              "null"
            ]
          },
          "body_parts": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "classification": {
            "type": [
              "string",
              "null"
            ]
          },
          "resulted_in_death": {
            "type": "boolean"
          },
          "days_away_flag": {
            "type": "boolean"
          },
          "job_transfer_or_restriction": {
            "type": "boolean"
          },
          "other_recordable_case": {
            "type": "boolean"
          },
          "num_days_away": {
            "type": [
              "integer",
              "null"
            ],
            "description": "As stored. If the record uses date ranges or an override, the in-app total may differ."
          },
          "num_days_restricted": {
            "type": [
              "integer",
              "null"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ]
          },
          "year": {
            "type": [
              "integer",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Asset": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "location_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "asset_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "identifier": {
            "type": [
              "string",
              "null"
            ]
          },
          "manufacturer": {
            "type": [
              "string",
              "null"
            ]
          },
          "model": {
            "type": [
              "string",
              "null"
            ]
          },
          "location_label": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ]
          },
          "check_interval_unit": {
            "type": [
              "string",
              "null"
            ]
          },
          "check_interval_value": {
            "type": [
              "integer",
              "null"
            ]
          },
          "last_checked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "photo_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Chemical": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "location_id": {
            "type": "string",
            "format": "uuid"
          },
          "chemical_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "manufacturer": {
            "type": [
              "string",
              "null"
            ]
          },
          "sub_location": {
            "type": [
              "string",
              "null"
            ]
          },
          "quantity": {
            "type": [
              "string",
              "null"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "received_on": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "next_review_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "last_reviewed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "tier_ii_reportable": {
            "type": "boolean"
          },
          "tri_reportable": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Scan": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "location_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "state_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "state_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "osha_jurisdiction": {
            "type": [
              "string",
              "null"
            ]
          },
          "overall_risk_level": {
            "type": [
              "string",
              "null"
            ]
          },
          "environment_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "summary": {
            "type": [
              "string",
              "null"
            ]
          },
          "violation_count": {
            "type": "integer"
          },
          "equipment_label": {
            "type": [
              "string",
              "null"
            ]
          },
          "asset_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "source_media_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "image_count": {
            "type": [
              "integer",
              "null"
            ]
          },
          "generated_by": {
            "type": [
              "string",
              "null"
            ],
            "description": "ai, human, or imported."
          },
          "location": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "lat": {
                "type": "number"
              },
              "lng": {
                "type": "number"
              },
              "accuracy_m": {
                "type": [
                  "number",
                  "null"
                ]
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Violation": {
        "type": "object",
        "description": "One finding. AI-generated — review before acting on it.",
        "properties": {
          "id": {
            "type": [
              "string",
              "null"
            ]
          },
          "hazard_category": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "priority": {
            "type": [
              "string",
              "null"
            ]
          },
          "corrective_action": {
            "type": [
              "string",
              "null"
            ]
          },
          "confidence": {
            "type": [
              "number",
              "null"
            ]
          }
        }
      },
      "Job": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "succeeded",
              "failed",
              "dead"
            ],
            "description": "`dead` means it will not be retried."
          },
          "profile_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The location the work was scoped to, if any."
          },
          "result": {
            "type": [
              "object",
              "null"
            ],
            "description": "Shape depends on `type`. For hazard_scan: { analyzable, scan_id, violation_count, overall_risk_level, environment_type, violations[], external_id? }. When analyzable is false the image could not be assessed and `message` explains why — that is a successful job with a negative answer, not a failure.",
            "properties": {
              "analyzable": {
                "type": "boolean"
              },
              "scan_id": {
                "type": "string",
                "format": "uuid"
              },
              "violation_count": {
                "type": "integer"
              },
              "overall_risk_level": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "environment_type": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "violations": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Violation"
                }
              },
              "external_id": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          },
          "error": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "message": {
                "type": "string"
              }
            }
          },
          "attempts": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "started_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "IngestResult": {
        "type": "object",
        "properties": {
          "module": {
            "type": "string"
          },
          "mapping_id": {
            "type": "string",
            "format": "uuid"
          },
          "accepted": {
            "type": "array",
            "description": "One entry per row that was written. Several rows can share an `id` when they were grouped into one inspection.",
            "items": {
              "type": "object",
              "properties": {
                "index": {
                  "type": "integer",
                  "description": "Position in the `rows` array you sent."
                },
                "client_key": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The row's own id where it had one, otherwise the key we derived for it."
                },
                "id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "The record created."
                },
                "deduplicated": {
                  "type": "boolean",
                  "description": "True when this row already existed and was not written again."
                }
              }
            }
          },
          "rejected": {
            "type": "array",
            "description": "Rows that were not written, and why. Fix and re-send just these.",
            "items": {
              "type": "object",
              "properties": {
                "index": {
                  "type": "integer"
                },
                "client_key": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "code": {
                  "type": "string",
                  "enum": [
                    "missing_required",
                    "unrecognized_value",
                    "unknown_location",
                    "invalid_row"
                  ],
                  "description": "Branch on this rather than on the message."
                },
                "field": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Which of our fields the problem is in."
                },
                "error": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "description": "Rows that WERE written but where something was not fully understood - an unrecognized finding status recorded as open, for instance.",
            "items": {
              "type": "object",
              "properties": {
                "index": {
                  "type": "integer"
                },
                "client_key": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          },
          "counts": {
            "type": "object",
            "properties": {
              "rows_received": {
                "type": "integer"
              },
              "rows_accepted": {
                "type": "integer"
              },
              "rows_rejected": {
                "type": "integer"
              },
              "records_created": {
                "type": "integer"
              },
              "records_deduplicated": {
                "type": "integer"
              }
            }
          },
          "timed_out": {
            "type": "boolean",
            "description": "True when the batch stopped at the time limit. Everything reported is saved; re-send the same batch and the finished rows come back deduplicated."
          },
          "rows_unprocessed": {
            "type": "integer",
            "description": "Rows not attempted because the batch stopped early. These were not refused - re-send them."
          }
        }
      }
    },
    "responses": {
      "Created": {
        "description": "Created",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "data": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "Unauthenticated": {
        "description": "Missing, invalid, revoked, or expired key — all return the same response.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The key lacks the required scope, or the Integration API is not enabled.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Not found, or outside this key's scope.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ValidationFailed": {
        "description": "The request body failed validation.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unprocessable": {
        "description": "Well-formed but not actionable — e.g. a corporate location that cannot accept records.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded. See X-RateLimit-Remaining and Retry-After.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}