NAV Navbar
shell

Authentication

To authorize, use this code:

# Every request requires an Authorization header with your API token
curl "api_endpoint_here"
  -H "Authorization: Bearer your_api_token"

Make sure to replace your_api_token with your API key.

SafetyAmp uses API keys to allow access to the API. To request an API key, please e-mail our support team.

In most cases, your API key is tied to a user profile in the SafetyAmp account and you will likely create a new user to represent the api user.

SafetyAmp expects for the API key to be included in all API requests to the server in a header that looks like the following:

Authorization: Bearer api_key

Making Requests

Apart from including the Authorization header, there are additional headers that are required for every API request.

Required HTTP Headers

Name Example Value Description
Authorization Bearer api_key This is used to authenticate your client
Fqdn your_subdomain.safetyamp.com This is the subdomain of the account your token is for
User-Agent YourOrg/1.0 Give your client a descriptive user agent that is unique to your organization
Accept application/json

Dates and Time

This API will respond with dates and times in the format YYYY-mm-dd H:i:s in the UTC timezone. Most requests expect a similarly formatted string also in UTC. Be sure to send datetime strings in UTC in this format.

Pagination

On list endpoints that support pagination, the following query parameters are available:

Parameter Default Description
limit 25 The maximum number of results to return in a single response.
page 0 The zero-indexed page number. page=0 returns the first page, page=1 the second, and so on.

Paginated responses wrap the results array in a data key and include a meta object with the total row count and current window:

{
    "data": [ ... ],
    "meta": {
        "total": 39,
        "current_page": 0,
        "per_page": 25,
        "last_page": 1
    }
}

meta.last_page is the index of the final page (also zero-indexed), so a total of 39 rows at a page size of 25 yields last_page: 1. Non-list endpoints and read-only lookup endpoints (e.g. incident_involved_person_types) return data without a meta block.

Rate Limit

To avoid receiving 429 - Too Many Requests error responses, ensure your client sends no more than 60 api calls per minute.

Filtering, Sorting, and Includes

List endpoints accept a standard request envelope for narrowing, ordering, and expanding the returned data. Each endpoint advertises which keys it supports for filtering, sorting, and including related resources.

# Filter utility bills to a single billing month, sort by period date, and
# include the related utility account and line items.
curl -G "https://api.safetyamp.com/api/utility_bills" \
  -H "Authorization: Bearer ..." \
  --data-urlencode 'filter_groups[0][filters][0][key]=billing_year' \
  --data-urlencode 'filter_groups[0][filters][0][operator]=eq' \
  --data-urlencode 'filter_groups[0][filters][0][value]=2025' \
  --data-urlencode 'filter_groups[0][filters][1][key]=billing_month' \
  --data-urlencode 'filter_groups[0][filters][1][operator]=eq' \
  --data-urlencode 'filter_groups[0][filters][1][value]=3' \
  --data-urlencode 'sort[0][key]=period_date' \
  --data-urlencode 'sort[0][direction]=desc' \
  --data-urlencode 'includes[]=utility_account' \
  --data-urlencode 'includes[]=line_items'

Filters

Filters are grouped under filter_groups[N][filters][M]. Each filter is an object with key, operator, and value. Filters within a group are combined with AND by default; pass filter_groups[N][or]=true to combine with OR instead.

Operator Description
eq Equal to
neq Not equal to
in Value matches any item in a comma-separated or array value
gt, gte Greater than (or equal)
lt, lte Less than (or equal)

Only the keys advertised by an endpoint may be used; unknown keys return 422 Unprocessable Entity.

Sorting

Sort entries are objects with key and optional direction (asc or desc, default asc):

sort[0][key]=period_date&sort[0][direction]=desc

Only the keys advertised by an endpoint are accepted.

Includes

includes[] expands related resources inline in the response. Endpoints document the include keys they support (e.g. utility_account, line_items.meter). Nested relations are addressed with dot notation.

Sites

Sites are physical locations of plants and facilities.

Get All Sites

curl "https://api.safetyamp.com/api/sites"
  -H "Authorization: Bearer ..."
  -H "Content-Type: application/json"

The above command returns JSON structured like this:

{
  "data": [
      {
          "id": 1,
          "name": "Hybrid Plant - Austin, TX",
          "street": "111 Paradise Lane",
          "street2": null,
          "city": "Austin",
          "state": "TX",
          "zip_code": "11111",
          "country": "USA",
          "latitude": "",
          "longitude": "",
          "naics_code": null,
          "industry_description": "",
      },
      ...
  ]
}

This endpoint retrieves all sites.

HTTP Request

GET https://api.safetyamp.com/api/sites

Get a Specific Site

curl "GET https://api.safetyamp.com/api/sites/1"
  -H "Authorization: Bearer ..."

Refer to list endpoint for response

This endpoint retrieves a specific site.

HTTP Request

GET https://api.safetyamp.com/api/sites/<id>

URL Parameters

Parameter Description
id The id of the site to retrieve

Roles

SafetyAmp implements role based access control. Users are assigned to multiple roles and receive aggregate permissions from their combined roles.

Get All Roles

curl "https://api.safetyamp.com/api/roles"
  -H "Authorization: Bearer ..."
  -H "Content-Type: application/json"

The above command returns JSON structured like this:

{
    "data": [
        {
            "id": 1,
            "name": "Plant Manager"
        },
        ...
    ]
}

This endpoint retrieves all roles.

HTTP Request

GET https://api.safetyamp.com/api/roles

Titles

SafetyAmp maintains a set of titles for your company. Users can be assigned one of these.

Get All Titles

curl "https://api.safetyamp.com/api/user_titles"
  -H "Authorization: Bearer ..."
  -H "Content-Type: application/json"

The above command returns JSON structured like this:

{
    "data": [
        {
            "id": 1,
            "name": "Plant Assitant Manager"
        },
        ...
    ]
}

This endpoint retrieves all titles.

HTTP Request

GET https://api.safetyamp.com/api/user_titles

Users

Creating a User

This endpoint allows the creation of new users in your account.

curl -X POST "https://api.safetyamp.com/api/users" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{
        "first_name": "Jane",
        "middle_name": null,
        "last_name": "Doe",
        "nickname": null,
        "gender": "1",
        "email": "jane.doe@example.com",
        "date_of_birth": "1999-01-31",
        "street": "11 Paradise Ln",
        "street2": null,
        "city": "San Francisco",
        "state": "CA",
        "zip_code": "94131",
        "country":"US",
        "mobile_phone": "+11231231234",
        "home_phone": "+12342342345",
        "work_phone": "+13453453456",
        "pending":1,
        "system_access":1,
        "bypass_sso":0,
        "text_opt_out": 0,
        "text_opt_out":0,
        "timezone":"America/New_York",
        "roles":[{"id": 1}],
        "home_site_id": 1,
        "sites":[{"id": 1}],
        "current_title_id":1,
        "current_department_id":1,
        "current_supervisor_id":1
      }'

HTTP Request

POST https://api.safetyamp.com/api/users

Request Body

Parameter Description
first_name (Required) The first name of the user
last_name (Required) The last name of the user
email (Required) The email address of the user, must be unique, currently used as username
mobile_phone Integers only, used for SMS messaging if provided
system_access 1 if user can login to SafetyAmp, 0 if user cannot
pending Boolean, a user input with pending as true will not send Activation email on create
activated Set to 1 to activate user bypassing email validation
bypass_sso If SSO enabled for account, set to 1 to allow user to login without SSO
text_opt_out 1 if user does not wish to receive SMS on mobile number
timezone chosen from list of timezones in Object Reference
roles[] An array of objects containing id properties of the roles to assign to the user
home_site_id uses the id property of the site the user is primarily at. This will automatically be added to sites for site access
sites[] An array of objects containing the id property of the sites that user is allowed access to

Getting All Users

curl "https://api.safetyamp.com/api/users"
  -H "Authorization: Bearer ..."
  -H "Content-Type: application/json"

The above command returns JSON structured like this:

{
    "data": [
        {
            "id": 1,
            "first_name": "Jane",
            "middle_name": null,
            "last_name": "Doe",
            "nickname": null,
            "date_of_birth": 1999-01-31,
            "owner": true,
            "email": "jane.doe@example.com",
            "work_phone": null,
            "home_phone": null,
            "mobile_phone": null,
            "avatar_location": null,
            "activated": 1,
            "gender": null,
            "street": "11 Paradise Ln",
            "city": "San Francisco",
            "state": "CA",
            "zip_code": "94131",
            "country": "USA",
            "street2": null,
            "text_opt_out": 0,
            "system_access": 1,
            "timezone": "America/Los_Angeles",
            "home_site_id": 1,
            "last_logged_in": "2019-10-25 21:40:22",
            "current_title": null,
            "deleted_at": null
        },
        ...
    ]
}

HTTP Request

GET https://api.safetyamp.com/api/users

This endpoint supports pagination.

Getting a Specific User

curl "GET https://api.safetyamp.com/api/users/1"
  -H "Authorization: Bearer ..."

Refer to list endpoint for response

HTTP Request

GET https://api.safetyamp.com/api/users/<id>

URL Parameters

Parameter Description
id The id of the user to retrieve

Updating a User

curl -X PUT "https://api.safetyamp.com/api/users/<id>" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{
    "first_name": "John",
    "last_name": "Doe",
    "email": "john.doe@example.com"
  }'

HTTP Request

PUT https://api.safetyamp.com/api/users/<id>

URL Parameters

first_name, last_name, and email are required fields on PUT calls. Refer to creating a user for list of additional parameters that can be updated. Only provided values will be updated.

Remove a User

Removing a user inactivates their user account and does not allow the user to access the SafetyAmp platform. The user data will be retained.

curl -X DELETE "https://api.safetyamp.com/api/users/<id>" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \

HTTP Request

DELETE https://api.safetyamp.com/api/users/<id>

URL Parameters

Parameter Description
id The id of the user to retrieve

SiteSense

SiteSense is SafetyAmp's energy management module. It tracks utility accounts (the relationship between a site and a utility provider), the meters attached to those accounts, and the bills (invoices) recorded against them. PDF copies of invoices are stored as attachments on the bill.

Permissions

SiteSense endpoints require one of two permissions:

Permission What it grants
InvoiceViewer Read-only access to utility accounts and bills. Account access is scoped to the user's accessible sites.
EnergyAdministrator Full read/write access to all SiteSense resources.

Endpoints that require EnergyAdministrator are noted on each section below.

Utility Types

Utility types are the categories of utility tracked by SiteSense (e.g. electricity, natural gas, water). The set of types is managed by SafetyAmp and cannot be created, updated, or deleted via the API.

List Utility Types

curl "https://api.safetyamp.com/api/utility_types" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json"

The above command returns JSON structured like this:

{
    "data": [
        {
            "id": 1,
            "name": "Electricity",
            "slug": "electricity",
            "unit_consumption": "kWh",
            "unit_demand": "kW"
        }
    ]
}

HTTP Request

GET https://api.safetyamp.com/api/utility_types

This endpoint supports pagination.

Get a Utility Type

curl "https://api.safetyamp.com/api/utility_types/1" \
  -H "Authorization: Bearer ..."

Refer to list endpoint for response

HTTP Request

GET https://api.safetyamp.com/api/utility_types/<id>

URL Parameters

Parameter Description
id The id of the utility type to retrieve

Utility Accounts

A utility account represents a single account a site holds with a utility provider. Bills are recorded against accounts.

Requires InvoiceViewer or EnergyAdministrator. Users without EnergyAdministrator only see accounts at sites they have access to.

List Utility Accounts

curl "https://api.safetyamp.com/api/utility_accounts" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json"

The above command returns JSON structured like this:

{
    "data": [
        {
            "id": 12,
            "site_id": 1,
            "provider_ref": "ACCT-44123",
            "provider_name": "Austin Energy",
            "type": "delivery",
            "linked_to": null,
            "notes": null,
            "start_date": "2024-01-01",
            "is_excluded": false,
            "is_closed": false,
            "integration_type": null,
            "integration_ext_id": null,
            "integration_status": null,
            "last_missing_invoice_notification": null
        }
    ]
}

HTTP Request

GET https://api.safetyamp.com/api/utility_accounts

Filter Keys

Key Type
id integer
provider_id integer
provider_name string
provider_ref string
site_id integer
type_id integer
is_closed boolean
is_excluded boolean
integration_type string
integration_status string

Sort Keys

Key
site
provider_name
provider_ref

Include Keys

Key What it adds
site Site object.
meters Meters attached to the account.
linked The account this one is linked to (delivery ↔ supply pairing).

Get a Utility Account

curl "https://api.safetyamp.com/api/utility_accounts/12" \
  -H "Authorization: Bearer ..."

Refer to list endpoint for response

HTTP Request

GET https://api.safetyamp.com/api/utility_accounts/<id>

URL Parameters

Parameter Description
id The id of the utility account to retrieve

Include Keys

Key What it adds
site Site object.
meters Meters attached to the account.
linked The account this one is linked to.
default_line_items Default line items configured for the account.

Create a Utility Account

Requires EnergyAdministrator. Returns 409 Conflict if an account with the same site_id, provider_ref, and provider_name already exists.

curl -X POST "https://api.safetyamp.com/api/utility_accounts" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{
        "site_id": 1,
        "provider_name": "Austin Energy",
        "provider_ref": "ACCT-44123",
        "type": "delivery",
        "start_date": "2024-01-01",
        "notes": null,
        "is_excluded": false,
        "is_closed": false
      }'

HTTP Request

POST https://api.safetyamp.com/api/utility_accounts

Request Body

Parameter Description
site_id (Required) The id of the site the account belongs to
provider_name (Required) Name of the utility provider, max 255 chars
start_date (Required) Date the account starts tracking bills, YYYY-MM-DD
provider_id Optional reference to a known utility provider record
provider_ref The provider's external account number, max 255 chars
type One of delivery, supply, general
linked_to The id of another active utility account this one is paired with
is_excluded Boolean, if true the account is excluded from energy model aggregations
is_closed Boolean, if true no new bills are expected on this account
integration_type arc to enable Arc integration. When set, the account is validated against Arc and tied to an Arc account id
meters An array of meter objects to create on the account. See "Sync Utility Account Meters" for the shape

Update a Utility Account

Requires EnergyAdministrator.

site_id cannot be changed after creation. If the account is configured with an integration, provider_ref and provider_name cannot be changed.

curl -X PATCH "https://api.safetyamp.com/api/utility_accounts/<id>" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{
        "notes": "Switched to time-of-use rate in March",
        "is_closed": false
      }'

HTTP Request

PATCH https://api.safetyamp.com/api/utility_accounts/<id>

Request Body

All fields are optional. Same parameters as create, except site_id which is immutable. Also accepts default_line_items: an array of {utility_meter_id, type, desc} objects that fully replace the account's default line items used when creating new bills.

Delete a Utility Account

Requires EnergyAdministrator. Deleting an account also deletes any bills attached to it.

curl -X DELETE "https://api.safetyamp.com/api/utility_accounts/<id>" \
  -H "Authorization: Bearer ..."

HTTP Request

DELETE https://api.safetyamp.com/api/utility_accounts/<id>

Export Utility Account Bills

Requires EnergyAdministrator. Queues a background job that produces a CSV export of all bills at a site for a given year. The response is a JobStatus resource that can be polled to check progress and retrieve the resulting file.

curl -X POST "https://api.safetyamp.com/api/utility_accounts:export" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{
        "site": 1,
        "year": 2025
      }'

HTTP Request

POST https://api.safetyamp.com/api/utility_accounts:export

Request Body

Parameter Description
site (Required) The id of the site to export bills for. Must have energy management enabled.
year (Required) The year to export, >= 2000

Utility Account Meters

Meters represent the individual measurement points on a utility account. A single account can have multiple meters (e.g. main panel and a sub-panel), each tied to a utility_type.

Requires EnergyAdministrator.

List Utility Account Meters

curl "https://api.safetyamp.com/api/utility_accounts/12/meters" \
  -H "Authorization: Bearer ..."

The above command returns JSON structured like this:

{
    "data": [
        {
            "id": 88,
            "name": "Main Electric Meter",
            "type_id": 1,
            "portfolio_mgr_id": null
        }
    ]
}

HTTP Request

GET https://api.safetyamp.com/api/utility_accounts/<id>/meters

Sync Utility Account Meters

Requires EnergyAdministrator. This is a full sync: the request body must contain every meter that should exist on the account. Meters present on the account but absent from the payload are deleted. Meters with an id are updated; meters without one are created.

curl -X PUT "https://api.safetyamp.com/api/utility_accounts/12/meters" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d `[
        { "id": 88, "name": "Main Electric Meter", "type_id": 1 },
        { "name": "Sub Panel A", "type_id": 1, "portfolio_mgr_id": "PM-1234" }
      ]`

HTTP Request

PUT https://api.safetyamp.com/api/utility_accounts/<id>/meters

Request Body

Each item in the array supports:

Parameter Description
id The id of an existing meter on this account. Omit to create a new meter.
name (Required when creating) The display name, max 255 chars
type_id (Required when creating) The id of a utility_type
portfolio_mgr_id Optional ENERGY STAR Portfolio Manager id, max 255 chars

Utility Bills

Utility bills (also called invoices) represent a single billing period on a utility account. Each bill carries one or more line items (usage, demand, or charges) and optionally one or more PDF attachments.

Requires InvoiceViewer or EnergyAdministrator. Users without EnergyAdministrator only see bills at sites they have access to.

List Utility Bills

curl "https://api.safetyamp.com/api/utility_bills" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json"

The above command returns JSON structured like this:

{
    "data": [
        {
            "id": 4801,
            "import_ref": null,
            "import_src": null,
            "utility_account_id": 12,
            "billing_year": 2025,
            "billing_month": 3,
            "period_date": "2025-02-15",
            "period_days": 30,
            "currency": "USD",
            "total_charges": 1842.55,
            "flags": [],
            "flags_silenced": false,
            "unavailable": false,
            "created_at": "2025-04-01 12:00:00",
            "updated_at": "2025-04-01 12:00:00"
        }
    ]
}

HTTP Request

GET https://api.safetyamp.com/api/utility_bills

Filter Keys

Key Type
id integer
utility_account_id integer
billing_year integer
billing_month integer (112)

Sort Keys

Key
period_date

Include Keys

Key What it adds
site Site object for the bill's utility account.
attachments Attached files (PDF invoices, etc.).
utility_account Parent utility account.
line_items Line items on the bill.
line_items.meter Nested: the meter on each line item.

Response Fields

Field Description
id The bill identifier
utility_account_id The id of the account this bill belongs to
billing_year Calendar year the bill covers
billing_month Calendar month the bill covers, 1-12
period_date First day of the billing period, YYYY-MM-DD
period_days Length of the billing period in days
currency One of USD, BSD, CAD, CRC, EUR, GBP, MXN, PAB, SEK
total_charges The sum of Charge line items in the bill's currency
flags An array of automated validation flags raised against this bill
flags_silenced Boolean, whether the flags were silenced by an admin
unavailable Boolean, true if the bill is recorded as known-missing for the period (placeholder; cannot have line items or attachments)
import_ref External id from the source system when the bill was imported. Cleared automatically if any non-flags_silenced field is updated.
import_src Name of the source system the bill was imported from

Get a Utility Bill

curl "https://api.safetyamp.com/api/utility_bills/4801" \
  -H "Authorization: Bearer ..."

Refer to list endpoint for response

HTTP Request

GET https://api.safetyamp.com/api/utility_bills/<id>

Include Keys

Key What it adds
site Site object for the bill's utility account.
attachments Attached files.
utility_account Parent utility account.
utility_account.meters Nested: meters attached to the parent account.
line_items Line items on the bill.
line_items.meter Nested: the meter on each line item.
creator User who created the bill.
updater User who last updated the bill.

Create a Utility Bill

Requires EnergyAdministrator.

curl -X POST "https://api.safetyamp.com/api/utility_bills" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{
        "utility_account_id": 12,
        "billing_year": 2025,
        "billing_month": 3,
        "period_date": "2025-02-15",
        "period_days": 30,
        "currency": "USD",
        "items": [
          { "type": 1, "meter_id": 88, "desc": "Usage",      "value": 12450 },
          { "type": 3, "meter_id": null, "desc": "Amount Due", "value": 1842.55 }
        ]
      }'

HTTP Request

POST https://api.safetyamp.com/api/utility_bills

Request Body

Parameter Description
utility_account_id (Required) The id of the account this bill belongs to
billing_year (Required) Calendar year, integer
billing_month (Required) Calendar month, integer 1-12
period_date (Required) First day of the billing period, YYYY-MM-DD
period_days (Required) Length of the billing period in days, integer ≥ 1
currency (Required) Three-letter currency code (see list endpoint)
items An array of line items. See "Sync Utility Bill Line Items" for the shape. Prohibited if unavailable is true.
flags_silenced Boolean, silences automated validation flags on creation
unavailable Boolean, mark the bill as known-missing. The bill cannot have line items or attachments.

A 422 is returned if the new bill overlaps an existing unavailable bill on the same account/period.

Update a Utility Bill

Requires EnergyAdministrator. The utility_account_id is immutable. Bills with unavailable=true cannot be updated.

curl -X PATCH "https://api.safetyamp.com/api/utility_bills/<id>" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{
        "period_days": 31,
        "items": [
          { "id": 1, "type": 1, "meter_id": 88, "desc": "Usage", "value": 12500 }
        ]
      }'

HTTP Request

PATCH https://api.safetyamp.com/api/utility_bills/<id>

Request Body

All fields optional. Same parameters as create, except utility_account_id. If any field other than flags_silenced is updated, the bill's import_ref and import_src are cleared.

Delete a Utility Bill

Requires EnergyAdministrator. Also removes any PDF attachments on the bill.

curl -X DELETE "https://api.safetyamp.com/api/utility_bills/<id>" \
  -H "Authorization: Bearer ..."

HTTP Request

DELETE https://api.safetyamp.com/api/utility_bills/<id>

Utility Bill Line Items

A bill's line items break down its usage, demand, and charges, optionally tied to a specific meter.

List Utility Bill Line Items

curl "https://api.safetyamp.com/api/utility_bills/4801/line_items" \
  -H "Authorization: Bearer ..."

The above command returns JSON structured like this:

{
    "data": [
        {
            "id": 1,
            "meter_id": 88,
            "desc": "Usage",
            "type": 1,
            "value": 12450
        }
    ]
}

HTTP Request

GET https://api.safetyamp.com/api/utility_bills/<id>/line_items

Line Item Types

Value Type Notes
1 Usage Quantity of the metered utility consumed (e.g. kWh)
2 Demand Peak demand for the period (e.g. kW)
3 Charge Monetary amount in the bill's currency

Line items without a meter_id (ad-hoc lines) must have type = 3 (Charge).

Sync Utility Bill Line Items

Requires EnergyAdministrator. A full sync: the array supplied replaces the bill's line items entirely.

curl -X PUT "https://api.safetyamp.com/api/utility_bills/4801/line_items" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d `[
        { "id": 1, "type": 1, "meter_id": 88, "desc": "Usage",      "value": 12500 },
        { "type": 3,           "meter_id": null, "desc": "Late Fee",  "value": 25.00 }
      ]`

HTTP Request

PUT https://api.safetyamp.com/api/utility_bills/<id>/line_items

Request Body

Each item in the array supports:

Parameter Description
id The id of an existing line item. Omit to create a new one.
type (Required) One of 1 (Usage), 2 (Demand), 3 (Charge). Ad-hoc lines (meter_id=null) must use 3.
desc (Required) Description, max 255 chars
meter_id The id of a meter on this bill's account, or null for ad-hoc charges
value Numeric value, up to 4 decimal places

Utility Bill Attachments

PDFs (and other files) attached to a utility bill are exposed via short-lived signed URLs. To download an attachment:

  1. Call GET /api/utility_bills/<id>/attachments to list attachments on the bill, or GET /api/utility_bills/<id>/attachments/<attachment_id> for a single one.
  2. The response contains a temporary_url field. Issue a GET against that URL to download the file.
  3. The signed URL expires; re-request the attachment to obtain a fresh URL if needed.

List Utility Bill Attachments

curl "https://api.safetyamp.com/api/utility_bills/4801/attachments" \
  -H "Authorization: Bearer ..."

The above command returns JSON structured like this:

{
    "data": [
        {
            "id": 901,
            "file_name": "invoice-2025-03.pdf",
            "display_name": "March 2025 Invoice",
            "file_size": 184320,
            "mime_type": "application/pdf",
            "type": "default",
            "description": null,
            "private": false,
            "external_url": null,
            "temporary_url": "https://storage.safetyamp.com/...signed...",
            "created_at": "2025-04-01 12:00:00",
            "updated_at": "2025-04-01 12:00:00"
        }
    ]
}

HTTP Request

GET https://api.safetyamp.com/api/utility_bills/<id>/attachments

Get a Utility Bill Attachment

curl "https://api.safetyamp.com/api/utility_bills/4801/attachments/901" \
  -H "Authorization: Bearer ..."

Refer to list endpoint for response

HTTP Request

GET https://api.safetyamp.com/api/utility_bills/<id>/attachments/<attachment_id>

A 404 is returned if the attachment is not attached to the specified bill.

Upload a Utility Bill Attachment

Requires EnergyAdministrator. Bills marked unavailable=true cannot have attachments. Uploads are sent as multipart/form-data with a single attachment file field.

curl -X POST "https://api.safetyamp.com/api/utility_bills/4801/attachments" \
  -H "Authorization: Bearer ..." \
  -F "attachment=@invoice-2025-03.pdf"

Refer to list endpoint for response

HTTP Request

POST https://api.safetyamp.com/api/utility_bills/<id>/attachments

Request Body (multipart)

Parameter Description
attachment (Required) The file to upload

Delete a Utility Bill Attachment

Requires EnergyAdministrator.

curl -X DELETE "https://api.safetyamp.com/api/utility_bills/4801/attachments/901" \
  -H "Authorization: Bearer ..."

HTTP Request

DELETE https://api.safetyamp.com/api/utility_bills/<id>/attachments/<attachment_id>

A 404 is returned if the attachment is not attached to the specified bill.

Incidents

Incidents are the core record of "something happened that safety cares about" — an injury, near-miss, spill, equipment issue, and so on. An incident carries the event context (site, date/time, description), links to the people involved, tracks the investigation, and (optionally) rolls up into a claim filed with an insurance carrier.

Incidents are only available on accounts with has_incidents enabled — every endpoint returns 404 Not Found if it's off, before any permission check runs.

Permissions

Every incident endpoint requires at least one of:

Permission slug In-app name What it grants
can_administer_incidents Can Administer Incidents Create, view, edit, and delete every incident in the account.
can_investigate_incidents Can Investigate Incidents Add root-cause analyses, involved persons, attachments, comments; edit the investigator payload.
can_create_incidents Can Create Incidents File new incidents.
can_view_all_incidents Can View All Incidents Read every incident in the account.
can_view_subordinates_incidents Can View Subordinates Incidents Read incidents filed by users the current user supervises.
can_view_own_incidents Can View Own Incidents Read only own incidents — created by, responsible for, or listed as primary involved person.
can_edit_all_incidents Can Edit All Incidents Edit every incident in the account.
can_edit_subordinates_incidents Can Edit Subordinates Incidents Edit incidents filed by users the current user supervises.
can_edit_own_incidents Can Edit Own Incidents Edit only own incidents.
can_delete_all_incidents Can Delete All Incidents Delete every incident in the account.
can_delete_subordinates_incidents Can Delete Subordinates Incidents Delete incidents filed by supervisees.
can_delete_own_incidents Can Delete Own Incidents Delete only own incidents.
can_manage_case_and_medical_data Can Manage Case And Medical Data Edit case-and-medical fields on involved persons and set osha_recordable.
can_set_osha_recordable Can Set Osha Recordable Toggle an incident's OSHA recordable flag.
can_export_osha_documents Can Export Osha Documents Export OSHA regulatory documents.

Company Administrators (or any custom role with company_administration) bypass every check in this table.

Callers with can_administer_incidents or can_investigate_incidents receive the investigator view — the full field set plus investigation-side blocks (root causes, RCAs, costings, comments, attachments, accident classifications, all involved persons). Other callers get the reporter view — same top-level scalars, but investigation blocks are omitted and action_items is filtered to items they created, own, or are assigned to (unless they hold can_view_all_action_items). Field tables below mark investigator-only fields.

List Incidents

Requires any view or edit permission from the permissions table.

curl "https://api.safetyamp.com/api/incidents" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json"

The above command returns JSON structured like this:

{
    "data": [
        {
            "id": "x6Ad2",
            "status": 0,
            "severity": "High",
            "event_date": "2025-09-19",
            "event_time": "10:00:00",
            "time_indeterminate": 0,
            "investigation_date": "2025-09-19 15:41:16",
            "report_date": "2025-09-19",
            "report_time": "10:00:00",
            "site_id": 1,
            "claim_id": null,
            "case_number_prefix": "INC",
            "case_number": 1,
            "location_id": 4,
            "location": "Loading Dock",
            "asset_id": 4,
            "stage_id": null,
            "department_id": 4,
            "description": "Employee slipped on wet floor at loading dock.",
            "title": "Slip and fall — loading dock",
            "osha_recordable": 0,
            "occurred_off_premise": 0,
            "// … off_premise_* fields (null unless occurred_off_premise=1)": null,
            "others_injured": 0,
            "shift_id": 4,
            "responsible_person": "5V4A2",
            "completed_by": null,
            "private": false,
            "open_claims_count": 0,
            "claims_count": null,
            "created_at": "2025-09-19 15:41:16",
            "updated_at": "2025-09-19 15:41:16",
            "created_by": "5V4A2",
            "updated_by": "5V4A2",
            "action_items": [],
            "types": [ /* Attached types  see Incident Types */ ],
            "creator": { "id": "5V4A2", "first_name": "Jane", "last_name": "Doe" },
            "responsible_person_user": { "id": "5V4A2", "first_name": "Jane", "last_name": "Doe" },
            "site": { "id": 1, "name": "Charlottesville" },
            "location_object": { "id": 4, "site_id": 1, "name": "Loading Dock" },
            "shift": { "id": 4, "site_id": 1, "name": "Day Shift" },
            "stage": null
        }
    ],
    "meta": {
        "total": 1,
        "current_page": 0,
        "per_page": 25,
        "last_page": 1
    }
}

HTTP Request

GET https://api.safetyamp.com/api/incidents

This endpoint supports pagination.

Filter Keys

Key Type Notes
title string Partial match.
title_or_id string Match against the incident's title or its case number.
event_date date YYYY-MM-DD Supports gte / lte / eq operators.
has_injury boolean 0 or 1.
osha_recordable boolean 0 or 1.
severity enum Low, Medium, High.
status integer 0 (In Progress) or 1 (Complete). Derived from stage.
site_id integer Site DB id.
cluster_id integer Site cluster DB id.
stage_id integer Workflow stage DB id.
case_number integer Account-local case number.
department_id integer Department DB id.
location_id integer Location DB id.
shift_id integer Shift DB id.
account_types.id integer Account-authored incident type DB id.
default_types.id integer Default incident type DB id.
involved_persons.injury boolean 0 or 1.
involved_persons.illness boolean 0 or 1.
involved_persons.user_id integer User DB id of the involved person (raw integer, not encoded).
involved_persons.type_id integer Involved-person-type DB id.

Sort Keys

Key
id
event_date
created_at
title
severity
case_number

Included Relationships

Always eager-loaded on the list response — the client cannot opt out:

Key What it adds
site Basic site object (id, name).
types Attached incident types with the nested type object.
location_object Location record (when location_id is set).
stage Workflow stage record.
creator Basic creator user object.
responsible_person_user Basic assignee user object.
shift Shift record.
action_items Attached action items.

On accounts with claims enabled, each row also carries an open_claims_count integer.

The list respects the caller's view/edit scope from the permissions table above; can_create_incidents is treated as a legacy alias for can_view_own_incidents. Site restrictions apply on top.

Get an Incident

Requires any view or edit permission covering this incident.

curl "https://api.safetyamp.com/api/incidents/x6Ad2" \
  -H "Authorization: Bearer ..."

Returns the same fields as the list endpoint, plus these additional relationship blocks:

{
    "data": {
        "id": "x6Ad2",
        "// … all scalar fields shown above": null,
        "attachments": [ /* Attachment objects  see Incident Attachments */ ],
        "comments": [ /* Comment objects  see Incident Comments */ ],
        "costings": [ /* Costing objects  see Incident Costings */ ],
        "involved_persons": [ /* Involved Person objects  see Involved Persons */ ],
        "root_cause_analyses": [ /* RCA objects  see Root Cause Analyses */ ],
        "root_causes": [],
        "selected_root_causes": [],
        "accident_classifications": [],
        "forms": [],
        "audit_trails": [],
        "custom_fields": [ /* CustomField objects with nested answers[]  see Custom Fields */ ],
        "asset": { "id": 3, "name": "Forklift #7" },
        "department": { "id": 3, "name": "Warehouse Operations" }
    }
}

HTTP Request

GET https://api.safetyamp.com/api/incidents/<id>

URL Parameters

Parameter Description
id The ID of the incident to retrieve (e.g. x6Ad2)

Returns 403 if the caller lacks a view permission covering this incident. Returns 404 if the incident does not exist or is soft-deleted.

Create an Incident

Requires any incident permission. Investigator-only fields (marked below) are silently dropped for callers without can_administer_incidents or can_investigate_incidents.

curl -X POST "https://api.safetyamp.com/api/incidents" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{
        "title": "Slip and fall — loading dock",
        "event_date": "2025-09-19",
        "event_time": "11:42:00",
        "report_date": "2025-09-19",
        "report_time": "11:42:00",
        "site_id": 1,
        "description": "Employee slipped on wet floor.",
        "severity": "Low",
        "responsible_person": "5V4A2",
        "types": [
          { "incident_type_id": 1, "incident_type_model": "default" }
        ],
        "involved_persons": [
          {
            "user_id": "5V4A2",
            "type_id": 2,
            "injury": 1,
            "injuries": [
              {
                "injury_type_model": "default",
                "injury_type_id": 4,
                "location_model": "default",
                "location_id": 6,
                "side_of_body": "Right"
              }
            ]
          }
        ]
      }'

HTTP Request

POST https://api.safetyamp.com/api/incidents

Request Body

Field Description
title (Required) Short summary of the incident, max 255 chars.
event_date (Required) Date the event happened, YYYY-MM-DD.
event_time Time the event happened, HH:MM:SS. Send null and set time_indeterminate to 1 when the exact time is unknown.
time_indeterminate Boolean. 1 marks event_time as unknown.
report_date (Required) Date the incident was reported, YYYY-MM-DD.
report_time Time the incident was reported, HH:MM:SS. Nullable.
site_id Integer ID of the site the incident occurred at.
location_id Integer ID of a specific location record. Nullable.
location Free-text location description used when a location record isn't appropriate.
asset_id Integer ID of an involved asset.
department_id Integer ID of a department.
shift_id Integer ID of a shift.
stage_id Integer ID of the workflow stage. Defaults to the account's first Incident stage. Setting it to the account's closed stage marks the incident complete — see notes below.
severity One of Low, Medium, High. Defaults to Low.
description Free-text description, up to 65534 chars.
responsible_person User ID of the assigned investigator, encoded (e.g. "5V4A2").
completed_by User ID of the user marking the incident complete, encoded. Automatically populated by the server.
others_injured Boolean. true if additional non-listed people were injured.
occurred_off_premise Boolean. When true, the off_premise_* fields become required.
off_premise_street, off_premise_street_2, off_premise_city, off_premise_state, off_premise_zip_code, off_premise_country, off_premise_phone_number, off_premise_note Off-premise address block. off_premise_state and off_premise_country must be exactly two characters (state code and ISO alpha-2 country). off_premise_zip_code maxes at 6 characters.
types Array of { incident_type_id, incident_type_model } objects. incident_type_model must be default or account. See "Incident Types" below. On update the full array replaces the previous value.
accident_classifications Array of { id, name } objects. Passing only a name (no id) creates a new classification.
root_causes Array of `{ root_cause_type: "default"\
involved_persons Array of involved-person objects. See "Involved Persons" below for the shape.
costings Array of costing objects. See "Incident Costings" below.
custom_field_answers Array of { custom_field_id, answer } entries. See below for the answer sub-shape.
attachments Array of { file_name, description, type, external_url } objects. Each item must include an external_url pointing at a file you host.
osha_recordable (Investigator-only) Boolean flagging the incident as OSHA recordable. Requires can_administer_incidents or can_manage_case_and_medical_data on the request, plus can_administer_incidents or can_investigate_incidents at the service layer.
private (Investigator-only) Boolean. true marks the incident as a privacy case — see the callout below.

status, case_number, case_number_prefix, claim_id, open_claims_count, claims_count are server-controlled and cannot be set through this endpoint.

Request Body — Custom Field Answers

Each entry in custom_field_answers targets one incident-scoped custom field. Fetch definitions from GET /api/custom_fields to look up custom_field_id values and their type.

Field Description
custom_field_id (Required) Integer ID of the custom field being answered.
answer (Required) Type-specific object; shape depends on the parent field's type.

answer shape by parent field type:

Parent type answer
ShortText, LongText { "value": "..." }
Date { "value": "YYYY-MM-DD" }
Time { "value": "HH:MM:SS" }
DateTime { "value": "YYYY-MM-DD HH:MM:SS" }
MultipleChoice { "custom_field_choice_id": <choice_id> }
Upload { "uuid": "...", "type": "image/png", "file_name": "photo.png" }

For Upload, the uuid must reference a file already staged in temp storage — upload the file to the attachments upload endpoint first, then attach it here by uuid.

Update an Incident

Requires an edit permission covering this incident.

Same body shape as create, with these differences:

Callers without can_administer_incidents or can_investigate_incidents may only update: severity, event_date, event_time, investigation_date, report_date, report_time, site_id, location_id, location, shift_id, asset_id, stage_id, department_id, description, title, types, responsible_person, completed_by, osha_recordable. Any other field is dropped silently.

curl -X PATCH "https://api.safetyamp.com/api/incidents/x6Ad2" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{
        "title": "Slip and fall — loading dock (updated)",
        "severity": "Medium"
      }'

HTTP Request

PATCH https://api.safetyamp.com/api/incidents/<id>

URL Parameters

Parameter Description
id The ID of the incident to update

Delete an Incident

Requires a delete permission covering this incident.

curl -X DELETE "https://api.safetyamp.com/api/incidents/x6Ad2" \
  -H "Authorization: Bearer ..."

Soft-deletes the incident and writes an audit-trail entry. Related sub-resources (attachments, comments, involved persons, RCAs, costings) remain in the database but stop surfacing through any other endpoint.

HTTP Request

DELETE https://api.safetyamp.com/api/incidents/<id>

Responds 204 No Content.

Incident Attachments

Attachments on incidents are links (URLs) to files you host. Host the file yourself, then send its public URL to SafetyAmp using the endpoints below.

All attachment endpoints require can_administer_incidents or can_investigate_incidents.

List Attachments

curl "https://api.safetyamp.com/api/incidents/x6Ad2/attachments" \
  -H "Authorization: Bearer ..."

Response:

{
    "data": [
        {
            "id": 42,
            "attachable_id": 1,
            "attachable_type": "incident",
            "file_name": "loading-dock-photo.jpg",
            "display_name": null,
            "type": "image",
            "external_url": "https://example.com/incident-photos/dock.jpg",
            "description": null,
            "private": false,
            "created_at": "2025-09-19 15:45:00",
            "created_by": "5V4A2",
            "updated_at": "2025-09-19 15:45:00",
            "updated_by": "5V4A2"
        }
    ]
}

HTTP Request

GET https://api.safetyamp.com/api/incidents/<incident_id>/attachments

Add an Attachment

Two options: link to a file you host, or upload the file directly.

Attach by URL (JSON):

curl -X POST "https://api.safetyamp.com/api/incidents/x6Ad2/attachments" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{
        "attachments": [
          {
            "file_name": "loading-dock-photo.jpg",
            "description": "Photo of the wet floor",
            "type": "image",
            "external_url": "https://example.com/incident-photos/dock.jpg"
          }
        ]
      }'

Upload files (multipart/form-data):

curl -X POST "https://api.safetyamp.com/api/incidents/x6Ad2/attachments" \
  -H "Authorization: Bearer ..." \
  -F "attachments[0][file_name]=loading-dock-photo.jpg" \
  -F "attachments[0][type]=image" \
  -F "attachments[0][attachment]=@/path/to/dock.jpg"

HTTP Request

POST https://api.safetyamp.com/api/incidents/<incident_id>/attachments

Request Body

The endpoint accepts a batch. Send attachments as an array even for a single file. Each entry must include exactly one of attachment, external_url, or uuid.

Field Description
attachments (Required) Array of attachment items.
attachments[].file_name (Required) A display filename for the attachment, max 255 chars.
attachments[].type (Required) Short category slug, max 80 chars. Examples: image, document.
attachments[].attachment (Required if neither external_url nor uuid is set) The file itself, sent via multipart/form-data.
attachments[].external_url (Required if neither attachment nor uuid is set) The URL of a file you host, up to 2048 chars. Must be reachable by SafetyAmp.
attachments[].description Optional description, max 255 chars.

Responds 201 with the created attachment objects. Uploaded files are processed asynchronously — image compression and PDF conversion run after the response.

Get an Attachment

curl "https://api.safetyamp.com/api/incidents/x6Ad2/attachments/42" \
  -H "Authorization: Bearer ..."

GET https://api.safetyamp.com/api/incidents/<incident_id>/attachments/<attachment_id>

Returns 422 if the attachment is not attached to the given incident.

Update an Attachment

description, file_name, external_url, and type may all be updated.

curl -X PATCH "https://api.safetyamp.com/api/incidents/x6Ad2/attachments/42" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{
        "description": "Photo taken from north side",
        "external_url": "https://example.com/incident-photos/dock-v2.jpg",
        "file_name": "dock-v2.jpg",
        "type": "image"
      }'

HTTP Request

PATCH https://api.safetyamp.com/api/incidents/<incident_id>/attachments/<attachment_id>

Delete an Attachment

curl -X DELETE "https://api.safetyamp.com/api/incidents/x6Ad2/attachments/42" \
  -H "Authorization: Bearer ..."

DELETE https://api.safetyamp.com/api/incidents/<incident_id>/attachments/<attachment_id>

Responds 204. Soft-delete — the row persists with deleted_at set. Returns 422 if the attachment is not attached to the given incident.

Incident Comments

Comments are threaded to an incident with author + timestamp + rich body. Both plain strings and structured rich-text JSON are accepted; the server returns the resolved rich-text structure on read.

All comment endpoints require can_administer_incidents or can_investigate_incidents. Additionally, only the original author can update a comment. Any Company Administrator (or the author) can delete a comment.

List Comments

curl "https://api.safetyamp.com/api/incidents/x6Ad2/comments" \
  -H "Authorization: Bearer ..."

Response:

{
    "data": [
        {
            "id": 12,
            "commentable_id": "x6Ad2",
            "commentable_type": "incident",
            "template_revision_element_id": null,
            "form_section_id": null,
            "body": {
                "version": 1,
                "type": "comment",
                "content": [
                    {
                        "type": "paragraph",
                        "content": [
                            {
                                "type": "text",
                                "text": "Adjuster called back — awaiting quote."
                            }
                        ]
                    }
                ]
            },
            "uuid": null,
            "created_by": "5V4A2",
            "created_at": "2025-09-19 15:41:16",
            "updated_at": "2025-09-19 15:41:16",
            "updated_by": "5V4A2",
            "deleted_at": null,
            "deleted_by": null,
            "creator": {
                "id": "5V4A2",
                "first_name": "Jane",
                "last_name": "Doe",
                "// … remaining user fields": "see Object Reference"
            }
        }
    ]
}

HTTP Request

GET https://api.safetyamp.com/api/incidents/<incident_id>/comments

Create a Comment

curl -X POST "https://api.safetyamp.com/api/incidents/x6Ad2/comments" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{ "body": "Reviewed footage." }'

HTTP Request

POST https://api.safetyamp.com/api/incidents/<incident_id>/comments

Request Body

Field Description
body (Required) Either a plain-text string, or a JSON string containing a structured comment (TipTap/ProseMirror shape). Max 65535 chars. Plain strings are wrapped in a single paragraph on read.
uuid Optional client-supplied UUID for idempotency.

A plain-text body ({ "body": "Reviewed footage." }) is wrapped into a single paragraph on read.

For rich formatting — including user mentions that fire notification emails — body may instead be a JSON string encoding a TipTap/ProseMirror document. When decoded, the payload structure is:

{
    "version": 1,
    "type": "comment",
    "content": [
        {
            "type": "paragraph",
            "content": [
                {
                    "type": "text",
                    "text": "Reviewed "
                }
            ]
        }
    ]
}

Serialize the above with JSON.stringify (or your language's equivalent) and send the resulting string as body. A mention node with an encoded user id sends that user a notification email when the comment is created.

Returns 422 with body: "json is malformed" if body starts to look like JSON but doesn't parse.

Update a Comment

Only the original author can update.

curl -X PATCH "https://api.safetyamp.com/api/incidents/x6Ad2/comments/12" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{ "body": "Reviewed footage — filed follow-up." }'

HTTP Request

PATCH https://api.safetyamp.com/api/incidents/<incident_id>/comments/<comment_id>

Delete a Comment

curl -X DELETE "https://api.safetyamp.com/api/incidents/x6Ad2/comments/12" \
  -H "Authorization: Bearer ..."

DELETE https://api.safetyamp.com/api/incidents/<incident_id>/comments/<comment_id>

Responds 204. Returns 403 if the caller is neither the author nor a Company Administrator.

Involved Persons

An involved person is anyone connected to an incident — the primary injured worker, a witness, a manager, and so on. Every involved person is tagged with a type_id from the involved-person-type lookup.

An involved person can either link to an existing user (user_id) or be freeform (name). Exactly one involved person per incident may have type_id = 2 (Primary Involved Person).

Read endpoints require can_administer_incidents or can_investigate_incidents. Create/update/delete are policed by the parent incident's edit permission. Fields marked "case-and-medical" below only appear in responses and are only writable when the caller holds can_administer_incidents or can_manage_case_and_medical_data.

List Involved Persons

curl "https://api.safetyamp.com/api/incidents/x6Ad2/involved_persons" \
  -H "Authorization: Bearer ..."

Response:

{
    "data": [
        {
            "id": 4,
            "incident_id": "x6Ad2",
            "user_id": "5V4A2",
            "type_id": 2,
            "name": "Jane Doe",
            "user_type": "Employee",
            "injury": 1,
            "illness": null,
            "// … narrative fields": "before_incident_description, injury_description, injury_occurrence_description, harming_object_description, notes",
            "// … case-and-medical fields": "drug_test/alcohol_test + _at, ppe_used, treated_onsite, medical_facility_id, physician_name, treatment_facility_*, emergency_room, ambulance, seen_at_clinic, in_patient, treatment_medical, treatment_description, illness_classification, estimated_restricted/lost_days, death, death_date, classification_dates",
            "user": { "id": "5V4A2", "first_name": "Jane", "last_name": "Doe" },
            "type": { "id": 2, "name": "Primary Involved Person" },
            "attachments": [],
            "injuries": [ /* Injury objects  see the Create body for shape */ ],
            "ppe": [],
            "created_at": "2025-09-19 15:41:16",
            "updated_at": "2025-09-19 15:41:16",
            "created_by": "5V4A2",
            "updated_by": "5V4A2"
        }
    ]
}

Non-investigator callers see only the row with type_id = 2 (the primary involved person).

HTTP Request

GET https://api.safetyamp.com/api/incidents/<incident_id>/involved_persons

Get an Involved Person

GET https://api.safetyamp.com/api/incidents/<incident_id>/involved_persons/<involved_person_id>

Additional fields loaded on show: full injuries[].type, injuries[].location, and classification_dates (grouped into away_from_work and restricted when the caller can see case-and-medical fields).

Response:

{
    "data": {
        "id": 4,
        "incident_id": "x6Ad2",
        "user_id": "5V4A2",
        "type_id": 2,
        "name": "Jane Doe",
        "user_type": "Employee",
        "injury": 1,
        "illness": null,
        "// … narrative fields": "before_incident_description, injury_description, injury_occurrence_description, harming_object_description, notes",
        "// … case-and-medical fields": "drug_test/alcohol_test + _at, ppe_used, treated_onsite, medical_facility_id, physician_name, treatment_facility_*, emergency_room, ambulance, seen_at_clinic, in_patient, treatment_medical, treatment_description, illness_classification, estimated_restricted/lost_days, death, death_date, classification_dates",
        "user": { "id": "5V4A2", "first_name": "Jane", "last_name": "Doe" },
        "type": { "id": 2, "name": "Primary Involved Person" },
        "attachments": [],
        "injuries": [ /* Injury objects  see the Create body for shape */ ],
        "ppe": [],
        "created_at": "2025-09-19 15:41:16",
        "updated_at": "2025-09-19 15:41:16",
        "created_by": "5V4A2",
        "updated_by": "5V4A2"
    }
}

Create an Involved Person

curl -X POST "https://api.safetyamp.com/api/incidents/x6Ad2/involved_persons" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{
        "user_id": "5V4A2",
        "type_id": 2,
        "user_type": "Employee",
        "injury": 1,
        "injuries": [
          {
            "injury_type_model": "default",
            "injury_type_id": 4,
            "location_model": "default",
            "location_id": 6,
            "side_of_body": "Right"
          }
        ]
      }'

HTTP Request

POST https://api.safetyamp.com/api/incidents/<incident_id>/involved_persons

Request Body

Field Description
user_id User ID (encoded) linking this involved person to a user record. Required if name is not sent. When set, name/address/phone/hire-date are copied from the user record.
name Freeform name. Required if user_id is not sent.
type_id (Required) Integer ID from the involved-person-type lookup. 2 = Primary Involved Person, 3 = Witness, 1 = Reporter, 4 = Secondary Involved Person, 5 = Manager.
user_type One of Other, Private, Employee, Contractor. Defaults to Employee.
job_title String.
hired_date Date, YYYY-MM-DD.
company_name String, max 255.
contact_info String, max 255. Freeform contact block for freeform involved persons.
signature Base64-encoded signature or image data, up to 5000 chars.
injury Boolean. When 1, injuries[], emergency_room, and in_patient become required.
illness Boolean. When 1, illness_classification becomes required.
injuries Array of injury objects (see below).
injuries[].injury_type_model default or account.
injuries[].injury_type_id Integer ID from incident_injury_types (account) or the seeded defaults.
injuries[].location_model default or account.
injuries[].location_id Integer ID from incident_injury_locations (account) or the seeded defaults.
injuries[].side_of_body One of Upper, Lower, Bilateral, Left, Center, Right, N/A.
drug_test / alcohol_test 1 (Yes), 2 (No), 3 (Unsure), or null.
drug_test_at / alcohol_test_at Date, YYYY-MM-DD.
ppe_used 1 / 2 / 3 / null.
ppe Array of { id } or { name } entries selecting or creating PPE items.
before_incident_description, injury_description, injury_occurrence_description, harming_object_description Free-text narrative fields.
notes Free-text notes.
time_work_began Time, HH:MM:SS.

Case-and-medical fields (require can_administer_incidents or can_manage_case_and_medical_data):

Field Description
medical_facility_id Encoded ID of a medical facility record.
treated_onsite, treatment_medical, ambulance, seen_at_clinic, emergency_room, in_patient, loss_of_consciousness Booleans.
treatment_description Free text.
physician_name String, max 254.
treatment_facility, treatment_facility_street, treatment_facility_street2, treatment_facility_city, treatment_facility_state, treatment_facility_zip_code, treatment_facility_country Freeform treating-facility address (used when medical_facility_id is not applicable).
illness_classification One of SkinDisorder, RespiratoryCondition, Poisoning, HearingLoss, AllOtherIllnesses. Required when illness=1.
death, death_date Boolean and date.
estimated_restricted_days, estimated_lost_days Integers.
classification_dates.restricted[] / classification_dates.away_from_work[] Arrays of { case_classification, start_date, end_date, notes } marking OSHA case-classification date ranges. case_classification must be one of Death, DaysAwayFromWork, JobTransferOrRestriction, OtherRecordableCases.

Returns 422 with An involved person has already been assigned to this incident if you try to create a second row with type_id = 2.

Update an Involved Person

Same body shape as create. id on nested injuries, ppe, and classification_dates entries lets you update rows in place; entries missing an existing ID are treated as new; existing rows not present in the payload are removed.

Non-investigator callers have their update payload whitelisted to user_type, type_id, contact_info, company_name, and name. Other fields are dropped without error.

PATCH https://api.safetyamp.com/api/incidents/<incident_id>/involved_persons/<involved_person_id>

Delete an Involved Person

DELETE https://api.safetyamp.com/api/incidents/<incident_id>/involved_persons/<involved_person_id>

Responds 204. Soft-delete.

Root Cause Analyses

A Root Cause Analysis (RCA) is a structured investigation session attached to an incident. Three analysis types are supported:

The specific structure of the analysis is stored in a config array — the API accepts any array shape here, so clients are responsible for producing the correct nested structure for the chosen type.

All RCA endpoints require can_administer_incidents or can_investigate_incidents, plus edit-scope on the parent incident.

List RCAs

curl "https://api.safetyamp.com/api/incidents/x6Ad2/incident_rcas" \
  -H "Authorization: Bearer ..."

Response:

{
    "data": [
        {
            "id": 3,
            "type": 2,
            "title": "Loading dock slip",
            "config": {
                "body": "Employee slipped on wet floor at loading dock."
            },
            "created_at": "2025-09-19 15:41:16",
            "updated_at": "2025-09-19 15:41:16",
            "created_by": "5V4A2",
            "updated_by": "5V4A2",
            "incident_id": "x6Ad2",
            "action_items": null
        }
    ],
    "includes": [],
    "meta": {
        "total": 1,
        "current_page": 0,
        "per_page": 25,
        "last_page": 0
    }
}

HTTP Request

GET https://api.safetyamp.com/api/incidents/<incident_id>/incident_rcas

Get an RCA

GET https://api.safetyamp.com/api/incidents/<incident_id>/incident_rcas/<rca_id>

Create an RCA

curl -X POST "https://api.safetyamp.com/api/incidents/x6Ad2/incident_rcas" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{
        "type": 0,
        "title": "Loading dock slip",
        "config": [ { "id": 0, "order": 1, "text": "Wet floor", "children": [] } ],
        "users": [ { "id": "5V4A2" } ]
      }'

HTTP Request

POST https://api.safetyamp.com/api/incidents/<incident_id>/incident_rcas

Request Body

Field Description
type (Required) Integer, 0 (Contributing Factor Analysis), 1 (Five Whys), or 2 (Plain Text).
title (Required) String, max 255.
config (Required) Array. Structure depends on type — the server does not validate the inner shape.
users Array of { id } entries — user IDs (encoded) of the RCA participants.
action_items Object { add: [...], remove: [...] } linking action items into the analysis. add[] items are { id, node_id }; remove[] items are just id. Applied as a delta against the existing set.

Update an RCA

Same shape as create. type cannot be changed — attempting to changes it returns 422 Cannot change analysis type.

PATCH https://api.safetyamp.com/api/incidents/<incident_id>/incident_rcas/<rca_id>

Delete an RCA

DELETE https://api.safetyamp.com/api/incidents/<incident_id>/incident_rcas/<rca_id>

Responds 204.

Incident Costings

Costings are line items tracking the dollar impact of an incident (medical costs, equipment damage, lost production, etc). Each row has an amount, a costing_type (either a system-provided default or an account-defined category), and a potential flag for estimated vs actual.

All costing endpoints require can_administer_incidents or can_investigate_incidents.

List Costings

curl "https://api.safetyamp.com/api/incidents/x6Ad2/incident_costings" \
  -H "Authorization: Bearer ..."

Response:

{
    "data": [
        {
            "id": 8,
            "incident_id": 1,
            "costing_type_model": "account",
            "costing_type_id": 4,
            "amount": "1250.00",
            "potential": 0,
            "description": null,
            "created_at": "2025-09-19 15:41:16",
            "updated_at": "2025-09-19 15:41:16",
            "deleted_at": null,
            "created_by": "5V4A2",
            "updated_by": "5V4A2",
            "deleted_by": null,
            "costing_type": {
                "id": 4,
                "title": "Medical Costs",
                "category": 1,
                "created_at": "2025-09-19 15:41:16",
                "updated_at": "2025-09-19 15:41:16",
                "deleted_at": null,
                "created_by": "5V4A2",
                "updated_by": "5V4A2",
                "deleted_by": null
            }
        }
    ]
}

HTTP Request

GET https://api.safetyamp.com/api/incidents/<incident_id>/incident_costings

Create a Costing

curl -X POST "https://api.safetyamp.com/api/incidents/x6Ad2/incident_costings" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{
        "amount": 1250.00,
        "costing_type_model": "default",
        "costing_type_id": 3,
        "potential": false,
        "description": "Medical bills"
      }'

HTTP Request

POST https://api.safetyamp.com/api/incidents/<incident_id>/incident_costings

Request Body

Field Description
amount (Required) Numeric dollar amount. Currency is implicit (account-level).
costing_type_model (Required) default or account.
costing_type_id (Required) Integer ID from costing_default_types or the account's costing_types.
potential Boolean. true marks the row as an estimated potential cost rather than an actual one.
description Optional free-text description.

Update / Delete

PATCH /api/incidents/<incident_id>/incident_costings/<costing_id> — same body shape as create.

DELETE /api/incidents/<incident_id>/incident_costings/<costing_id> — responds 204.

Lookups

Lookup tables used when creating or filtering incidents. All Company Administrators bypass permission checks on these — customers integrating with a Company Administrator user can freely list any of them.

Incident Types

Incident types are the classification a customer picks when filing an incident — Injury, Vehicle Accident, Spill, and so on. The set is a merge of twelve SafetyAmp-provided defaults and any account-authored additions (which can shadow a default via default_type_id).

Type definitions are managed through the SafetyAmp UI, not the public API. The list endpoint below returns them so you can look up incident_type_id values before setting types[] on an incident.

List Incident Types

curl "https://api.safetyamp.com/api/incident_types" \
  -H "Authorization: Bearer ..."

Response:

{
    "data": [
        {
            "id": 3,
            "default_type_id": null,
            "name": "Behavior",
            "model": "default",
            "disabled": 0,
            "templates": []
        }
    ]
}

Each row is either the account's customization (model: "account") or an unmodified default (model: "default"). templates[] lists the form templates that will be attached when an incident of that type is filed.

HTTP Request

GET https://api.safetyamp.com/api/incident_types

Filter Keys
Key Type Notes
disabled boolean 0 or 1.
Sort Keys
Key
name

Default sort: name ascending.

Injury Types

The set of injury types available when marking an involved person as injured. Twelve seeded defaults (Cut, Laceration, Puncture, Abrasion, Strain, Sprain, Dislocation, etc.) plus any account-defined additions.

Reads are available to any authenticated user. Writes require company_administration.

List Injury Types

curl "https://api.safetyamp.com/api/incident_injury_types" \
  -H "Authorization: Bearer ..."

Response:

{
    "data": [
        {
            "id": 6,
            "default_type_id": null,
            "name": "Sprain",
            "model": "default",
            "disabled": 0
        }
    ]
}
HTTP Request

GET https://api.safetyamp.com/api/incident_injury_types

Filter Keys
Key Type Notes
disabled boolean 0 or 1.
Sort Keys
Key
name

Create / Update / Delete

POST /api/incident_injury_types PATCH /api/incident_injury_types/<id> DELETE /api/incident_injury_types/<id>

Field Description
name (Required) String.
default_type_id Optional. Marks this row as a customization of a default.
disabled Boolean.

Injury Locations

Body-location taxonomy — Head, Neck, Shoulder, Torso, Back, Arm, Hand, Leg, Foot, and so on. Each location is optionally tagged with a coarse body_part grouping.

Reads are available to any authenticated user. Writes require company_administration.

List Injury Locations

curl "https://api.safetyamp.com/api/incident_injury_locations" \
  -H "Authorization: Bearer ..."

Response:

{
    "data": [
        {
            "id": 1,
            "default_location_id": null,
            "name": "Head",
            "model": "default",
            "disabled": 0,
            "body_part": 0
        }
    ]
}
HTTP Request

GET https://api.safetyamp.com/api/incident_injury_locations

Filter Keys
Key Type Notes
disabled boolean 0 or 1.
Sort Keys
Key
name

Create / Update / Delete

POST /api/incident_injury_locations PATCH /api/incident_injury_locations/<id> DELETE /api/incident_injury_locations/<id>

Field Description
name (Required) String.
default_location_id Optional. Marks this row as a customization of a default.
disabled Boolean.
body_part Integer 0..80 Head, 1 Neck, 2 Shoulder, 3 Torso, 4 Back, 5 Arm, 6 Hand, 7 Leg, 8 Foot. If default_location_id is set, this value is overwritten from the default's own body_part.

Accident Classifications

Account-defined labels describing how an accident occurred — e.g. "Slip and Fall", "Struck By", "Chemical Exposure". Entirely account-authored; there are no seeded defaults.

Reads are available to any authenticated user. Writes require can_administer_incidents.

List Accident Classifications

curl "https://api.safetyamp.com/api/accident_classifications" \
  -H "Authorization: Bearer ..."

Response:

{
    "data": [
        {
            "id": 1,
            "name": "Chemical Exposure",
            "disabled": false,
            "created_at": "2025-09-19 15:41:16",
            "created_by": "5V4A2",
            "updated_at": "2025-09-19 15:41:16",
            "updated_by": "5V4A2",
            "deleted_at": null,
            "deleted_by": null
        }
    ],
    "meta": {
        "total": 1,
        "current_page": 1,
        "per_page": 25,
        "last_page": 1
    }
}
HTTP Request

GET https://api.safetyamp.com/api/accident_classifications

Filter Keys
Key Type Notes
disabled boolean 0 or 1.
Sort Keys
Key
name

Create / Update / Delete

POST /api/accident_classifications PATCH /api/accident_classifications/<id> DELETE /api/accident_classifications/<id>

Field Description
name (Required) String, max 254 chars.
disabled Boolean.

Involved Person Types Lookup

Read-only lookup — the seeded set of involved-person types.

List

curl "https://api.safetyamp.com/api/incident_involved_person_types" \
  -H "Authorization: Bearer ..."

Response:

{
    "data": [
        {
            "id": 1,
            "name": "Reporter"
        }
    ]
}
HTTP Request

GET https://api.safetyamp.com/api/incident_involved_person_types

Read-only — no create/update/delete.

Involved Person Employment Statuses Lookup

Read-only lookup — the seeded set of employment statuses used on involved persons.

List

curl "https://api.safetyamp.com/api/incident_involved_person_employment_statuses" \
  -H "Authorization: Bearer ..."

Response:

{
    "data": [
        {
            "id": 1,
            "name": "Employee"
        }
    ]
}
HTTP Request

GET https://api.safetyamp.com/api/incident_involved_person_employment_statuses

Read-only — no create/update/delete.

Custom Fields

Custom fields are account-defined attributes that attach optional data to incidents. Each field has a source_type that controls which incidents it appears on:

Seven field types are supported: ShortText, LongText, Date, Time, DateTime, MultipleChoice, and Upload.

Field definitions are managed through the SafetyAmp UI, not the public API. The list endpoint below returns your account's definitions so you can populate custom_field_answers when creating or updating incidents.

List Custom Fields

curl "https://api.safetyamp.com/api/custom_fields" \
  -H "Authorization: Bearer ..."

Response:

{
    "data": [
        {
            "id": 5,
            "type": "MultipleChoice",
            "title": "Root cause detail",
            "hint": null,
            "order": 0,
            "config": {
                "required": false
            },
            "choices": [
                {
                    "id": 12,
                    "custom_field_id": 5,
                    "name": "Slip/Trip",
                    "// ... 10 fields total; see corresponding section": null
                }
            ],
            "source_type": "incident",
            "connections": [
                {
                    "id": 5,
                    "custom_field_id": 5,
                    "connection_id": null,
                    "connection_type": "incident"
                }
            ]
        }
    ],
    "includes": [],
    "meta": {
        "total": 1,
        "current_page": 0,
        "per_page": 25,
        "last_page": 0
    }
}
HTTP Request

GET https://api.safetyamp.com/api/custom_fields

Filter Keys
Key Type Notes
source_type enum incident or incident_type.
Sort Keys
Key
order
Include Keys
Key What it adds
choices Choice options for MultipleChoice fields.
connections Rows mapping this field to specific incident types (only meaningful when source_type=incident_type).
connections.resource Nested — the Type / DefaultType object each connection points at.

Claims

Claims are insurance claims (workers' comp, auto, general liability, property damage) filed against an incident. A claim records the person involved, event context, medical/employment info, and can be pushed to an integrated claims broker.

Claims layer on top of incidents: they require the incidents feature (has_incidents) and additionally the claims feature (has_claims). Both settings are on the customer's account; if either is off, every endpoint below returns 404.

Permissions

Every claim endpoint (including carriers, medical facilities, contacts, and claim attachments/comments) requires the can_administer_claims permission. There are no granular scopes — either you can administer claims for the account, or you can't touch them.

Permission slug In-app name What it grants
can_administer_claims Can Administer Claims Create, view, edit, and delete every claim in the account. Also required for carriers, medical facilities, contacts, and claim attachments/comments.

Users assigned the Company Administrator role bypass this check.

Site restrictions still apply — a user restricted from a site cannot see or write claims tied to incidents at that site.

List Claims

curl "https://api.safetyamp.com/api/claims" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json"

Response (fields trimmed for brevity — see "Get a Claim" for the full shape):

{
    "data": [
        {
            "id": "5V4A2",
            "type": "workers_comp",
            "status": "open",
            "case_number": "2026-0001",
            "external_claim_number": null,
            "incident_id": "R2wvV",
            "initiated_on": "2026-03-30",
            "integration_id": null,
            "carrier_contact_id": null,
            "sync_status": null,
            "last_synced": null,
            "pip_user_id": "5V4A2",
            "pip_name": "Hampus Backman",
            "site_id": 1,
            "event_date": "2026-02-11",
            "created_at": "2026-03-30 16:08:50",
            "created_by": 1,
            "updated_at": "2026-03-30 16:11:37",
            "updated_by": 1
        }
    ],
    "meta": {
        "total": 1,
        "current_page": 0,
        "per_page": 25,
        "last_page": 0
    }
}
HTTP Request

GET https://api.safetyamp.com/api/claims

Filter Keys
Key Type Notes
status enum open or closed.
sync_status enum queued or sent.
site_id integer Site DB id.
incident_id string Encoded incident ID.
pip_user_id string Encoded user ID of the primary involved person.
integration_id string Encoded integration ID.
carrier_contact_id string Encoded contact ID.
Sort Keys
Key
site
created_at
updated_at
case_number
employee_last_name
created_by_last_name
incident_case_number
Include Keys
Key What it adds
creator Basic user object for the claim's creator.
incident The full incident payload (see Incidents section).
carrier_contact Contact object attached to the claim's carrier.
integration Integration record (name, type, settings).
integration.carrier Nested — the carrier belonging to the integration.

Each key accepts a :count suffix (e.g. ?includes[]=incident:count) to return just the relation count instead of the full payload.

List Response Scope

Callers without can_administer_incidents see only claims whose site_id is one of their accessible sites. Callers with can_administer_incidents see every claim in the account.

Get a Claim

curl "https://api.safetyamp.com/api/claims/5V4A2" \
  -H "Authorization: Bearer ..."

Response (full shape):

{
    "data": {
        "id": "5V4A2",
        "type": "workers_comp",
        "status": "open",
        "case_number": "2025-0001",
        "external_claim_number": null,
        "incident_id": "x6Ad2",
        "initiated_on": "2025-09-19",
        "integration_id": null,
        "carrier_contact_id": null,
        "contact_user_id": "5V4A2",
        "sync_status": null,
        "last_synced": null,
        "event_date": "2025-09-19",
        "event_time": "10:00:00",
        "report_date": "2025-09-19",
        "report_time": "10:00:00",
        "site_id": 1,
        "site_name": "Charlottesville",
        "site_state": "VA",
        "site_city": "Charlottesville",
        "location_id": null,
        "location_name": null,
        "location_description": null,
        "description": "Employee slipped on wet floor at loading dock.",
        "claim_notes": null,
        "// … primary-involved-person fields": "pip_user_id, pip_name, pip_user_type, pip_job_title, pip_emp_id, pip_department, pip_hire_date, pip_date_of_birth, pip_gender, pip_street/_2, pip_city, pip_state, pip_zip_code, pip_country, pip_home_phone, pip_work_phone, pip_nature_of_claim, pip_insurance_company, pip_insurance_company_phone_number, pip_policy_number, pip_policy_holder_name",
        "// … narrative fields": "before_incident_description, injury_description, injury_occurrence_description, harming_object_description",
        "// … case classification fields": "injuries, accident_classifications, illness_classification, employment_status, state_of_hire, payroll_state, started_current_position, performing_regular_job, hours_worked_per_day, time_work_began, full_wages_on_date_of_injury, total_days_away_from_work, restricted_work, death, death_date",
        "// … medical fields": "in_patient, ambulance, emergency_room, seen_at_clinic, treated_onsite, loss_of_consciousness, date_of_treatment, physician_name, treatment_description, facility_type, facility_name, facility_street/_2, facility_city, facility_state, facility_zip_code, facility_country, facility_phone_number",
        "// … off-premise fields (null unless occurred_off_premise=1)": null,
        "occurred_off_premise": 0,
        "created_at": "2025-09-19 15:41:16",
        "created_by": 1,
        "updated_at": "2025-09-19 15:41:16",
        "updated_by": 1,
        "vehicles": [],
        "witnesses": [],
        "property_damages": [],
        "medical_visits": [],
        "attachments": [],
        "comments": [],
        "action_items": [],
        "integration": null,
        "carrier_contact": null,
        "creator": { "id": "5V4A2", "first_name": "Jane", "last_name": "Doe" }
    }
}
HTTP Request

GET https://api.safetyamp.com/api/claims/<id>

The show endpoint eager-loads every sub-collection (vehicles, witnesses, property damages, medical visits, attachments, comments, action items) plus the integration and its carrier. Sub-collections are inlined into the response rather than returned as a separate includes block.

Draft a Claim from an Incident

Generate an unsaved draft claim pre-populated from an incident. Useful for building a "new claim" form — take the response, let the user tweak it, then POST it back to /api/claims to persist.

curl -X POST "https://api.safetyamp.com/api/claims/make" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{ "incident_id": "R2wvV", "type": "workers_comp" }'
HTTP Request

POST https://api.safetyamp.com/api/claims/make

Request Body
Field Description
incident_id (Required) Encoded ID of the source incident.
type (Required) One of auto, general, property_damage, workers_comp.
integration_id Optional encoded ID of a claims integration to associate this claim with once created.

Returns a hydrated (but not persisted) Claim payload matching the shape of "Get a Claim". Fields are populated from the incident: site, event date/time, off-premise, description, primary involved person's user + address + phone + hire date + DOB, injuries, accident classifications, days-away calculations, restricted-work state. Medical visits are derived from the involved person's medical fields when available.

Create a Claim

curl -X POST "https://api.safetyamp.com/api/claims" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{
        "incident_id": "R2wvV",
        "type": "workers_comp",
        "pip_user_id": "5V4A2",
        "pip_name": "Hampus Backman",
        "contact_user_id": "5V4A2",
        "occurred_off_premise": false,
        "event_date": "2026-02-11",
        "site_id": 1,
        "description": "Slipped and fell in the parking lot."
      }'
HTTP Request

POST https://api.safetyamp.com/api/claims

Request Body — Required Fields
Field Description
incident_id (Required) Encoded incident ID. The parent incident must exist and the caller must have permission to update it.
type (Required) One of auto, general, property_damage, workers_comp. Immutable once set.
pip_user_id (Required) Encoded user ID of the primary involved person.
pip_name (Required) Display name of the primary involved person, max 255.
contact_user_id (Required) Encoded user ID of the internal contact for this claim.
occurred_off_premise (Required) Boolean.
Request Body — Optional Scalars
Field Description
claim_notes, description, before_incident_description, injury_occurrence_description, injury_description, harming_object_description, off_premise_note, time_work_began Free-text narrative fields.
external_claim_number External reference from the broker, string max 255.
pip_job_title, pip_emp_id, pip_department, pip_nature_of_claim, pip_insurance_company, pip_insurance_company_phone_number, pip_policy_number, pip_policy_holder_name Primary involved person identifying info, strings.
pip_street, pip_street_2, pip_city, pip_state, pip_zip_code, pip_country, pip_home_phone, pip_work_phone Primary involved person address / phone.
pip_date_of_birth, pip_hire_date Dates, YYYY-MM-DD.
pip_gender Integer, 0 or 1.
site_id, location_id Integer IDs referencing site and location records.
site_name, site_city, site_state, location_name, location_description Denormalized site/location strings (max 255).
event_date, report_date, initiated_on, started_current_position, death_date Dates, YYYY-MM-DD.
event_time, report_time Times, HH:MM:SS.
occurred_off_premise, off_premise_street, off_premise_street_2, off_premise_city, off_premise_state, off_premise_zip_code, off_premise_country, off_premise_phone_number Off-premise address block, strings.
injuries, accident_classifications, illness_classification, state_of_hire, payroll_state Free-text summary fields, strings.
death, full_wages_on_date_of_injury, restricted_work, performing_regular_job Booleans.
total_days_away_from_work Integer, ≤180.
hours_worked_per_day Integer, ≤24.
employment_status One of full_time, part_time, seasonal, temporary, unemployed, unknown.
integration_id Encoded ID of a claims integration. When set, sync_status is force-initialized to queued.
carrier_contact_id Encoded ID of a contact (adjuster) attached to a carrier.
Request Body — Nested Sub-Collections

witnesses[] — witness objects.

Field Description
name (Required) String, max 255.
user_id Encoded user ID.
street, street_2, city, state, zip_code, country, phone_number Address and contact.
notes Free-text.

property_damages[] — property damage objects. Only synced when the claim's type is auto, general, or property_damage.

Field Description
title (Required) String, max 255.
repair_cost, replacement_cost, amount_claimed Numeric.

vehicles[] — vehicle objects. Only synced when the claim's type is auto.

Field Description
type (Required) primary or secondary.
make, model String.
year 4-digit integer.
license_plate, vin String.
number_of_passengers Integer, ≤100.
names_of_passengers String.
driver_user_id Integer DB id (not encoded).
driver_street, driver_street_2, driver_city, driver_state, driver_zip_code, driver_country, driver_phone_number Driver address and contact.
driver_license_no, driver_license_state String.

medical_visits[] — medical visit objects.

Field Description
treatment_type One of initial, follow_up, specialist.
treatment_date Date.
treatment_tz Timezone string.
doctor_name String.
facility_type physician or hospital_clinic.
medical_facility_id Encoded ID.
phone_number, notes String.
treated_on_site, loss_of_consciousness, ambulance, emergency_room, seen_at_clinic, in_patient Booleans.

answers[] — Broadspire integration answers. Only synced when the linked integration is Broadspire; silently dropped otherwise.

Field Description
key (Required) String matching a Broadspire answerable key.
value String.
Server-Controlled Fields

case_number, sync_status, last_synced, status (open/closed), id, created_at, created_by, updated_at, updated_by cannot be set by the client. case_number is generated as YYYY-#### (creation year + incrementing counter). sync_status transitions to queued when an integration is attached, and to sent when a sync is triggered.

Update a Claim

Same body shape as create, with these differences:

curl -X PATCH "https://api.safetyamp.com/api/claims/5V4A2" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{ "claim_notes": "Adjuster contacted", "status": "open" }'
HTTP Request

PATCH https://api.safetyamp.com/api/claims/<id>

Delete a Claim

curl -X DELETE "https://api.safetyamp.com/api/claims/5V4A2" \
  -H "Authorization: Bearer ..."

DELETE https://api.safetyamp.com/api/claims/<id>

Responds 204. Soft-delete — the row persists with deleted_at set. Sub-collections (vehicles, witnesses, property_damages, medical_visits, attachments, comments) are not cascade-deleted; they remain in the database.

List Claim Integrations

Lists the account's claims-broker integrations. Each row is the raw integration record plus its linked carrier.

curl "https://api.safetyamp.com/api/claims/integrations" \
  -H "Authorization: Bearer ..."

Response:

{
    "data": [
        {
            "id": "8bZ4y",
            "name": "Broadspire",
            "settings": { /* provider-specific config */ },
            "integration_type": "BroadSpireSftpSync",
            "carrier": {
                "id": "aC2xD",
                "name": "Broadspire Insurance",
                "claim_type": "workers_comp",
                "integration_id": "8bZ4y",
                "contacts": []
            },
            "answerable_fields": [
                {
                    "name": "employee_ssn",
                    "title": "Employee SSN",
                    "description": "…",
                    "key": "employee_ssn",
                    "required": true,
                    "default": null,
                    "options": null,
                    "answer": null
                }
            ]
        }
    ]
}

Currently only Broadspire integrations (integration_type = BroadSpireSftpSync) are returned. answerable_fields describes the extra fields the broker requires — these values are supplied via the answers[] array on POST / PATCH /api/claims and returned back here as answer.value when set on a specific claim.

HTTP Request

GET https://api.safetyamp.com/api/claims/integrations

Sync a Claim to Its Integration

Queue an asynchronous push of the claim to its linked integration (currently: Broadspire via SFTP). The endpoint takes no request body — the claim's linkage and payload live on the claim itself.

curl -X POST "https://api.safetyamp.com/api/claims/5V4A2/sync-integration" \
  -H "Authorization: Bearer ..."
HTTP Request

POST https://api.safetyamp.com/api/claims/<claim_id>/sync-integration

Returns the updated claim resource, with sync_status flipped to sent. The actual delivery to the broker happens out-of-band; a 200 response means "queued for delivery", not "acknowledged by the broker."

Returns 409 Conflict with the message Claim already submitted to Broadspire if the claim's current sync_status is not queued. To re-sync a claim after edits, the linkage must go through the sync_status = queued state again — this is currently only achieved by attaching a fresh integration_id.

Claim Attachments

Two options: link to a file you host, or upload the file directly.

Attachments are read as part of the parent claim (GET /api/claims/<id> returns them in the attachments[] array). There is no GET /api/claims/<id>/attachments/<attachment_id> endpoint.

Add an Attachment

Attach by URL (JSON):

curl -X POST "https://api.safetyamp.com/api/claims/5V4A2/attachments" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{
        "file_name": "police-report.pdf",
        "type": "document",
        "external_url": "https://example.com/claims/5V4A2/police-report.pdf"
      }'

Upload a file (multipart/form-data):

curl -X POST "https://api.safetyamp.com/api/claims/5V4A2/attachments" \
  -H "Authorization: Bearer ..." \
  -F "file_name=police-report.pdf" \
  -F "type=document" \
  -F "attachment=@/path/to/police-report.pdf"
HTTP Request

POST https://api.safetyamp.com/api/claims/<claim_id>/attachments

Request Body

Send exactly one of attachment, external_url, or uuid.

Field Description
file_name (Required) A display filename for the attachment, max 255.
type (Required) Short category slug, max 80. Examples: image, document.
attachment (Required if neither external_url nor uuid is set) The file itself, sent via multipart/form-data.
external_url (Required if neither attachment nor uuid is set) The URL of a file you host, up to 2048 chars. Must be reachable by SafetyAmp.

Delete an Attachment

curl -X DELETE "https://api.safetyamp.com/api/claims/5V4A2/attachments/42" \
  -H "Authorization: Bearer ..."

DELETE https://api.safetyamp.com/api/claims/<claim_id>/attachments/<attachment_id>

Responds 204. Returns 422 if the attachment is not attached to the given claim.

Claim Comments

Comments live directly on claims. Author + timestamp + rich body, same TipTap/ProseMirror shape as incident comments (see Incident Comments).

Comments are read as part of the parent claim (GET /api/claims/<id> returns them in the comments[] array). There is no standalone list or show endpoint.

Create a Comment

curl -X POST "https://api.safetyamp.com/api/claims/5V4A2/comments" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{ "body": "Adjuster returned call." }'
HTTP Request

POST https://api.safetyamp.com/api/claims/<claim_id>/comments

Request Body
Field Description
body (Required) Plain string or structured comment JSON, max 65535 chars.
uuid Optional client-supplied UUID for idempotency.

Update a Comment

Only the original author can update a comment.

curl -X PATCH "https://api.safetyamp.com/api/claims/5V4A2/comments/12" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{ "body": "Adjuster returned call — filed follow-up." }'

PATCH https://api.safetyamp.com/api/claims/<claim_id>/comments/<comment_id>

Delete a Comment

DELETE https://api.safetyamp.com/api/claims/<claim_id>/comments/<comment_id>

Responds 204. Returns 403 if the caller is neither the author nor a Company Administrator.

Medical Facilities

A directory of medical facilities where injured workers get treated. Referenced by medical_facility_id on medical visits and involved persons.

Writes require any of can_administer_claims, can_administer_incidents, or can_manage_case_and_medical_data. Read is available to any authenticated user.

List Medical Facilities

curl "https://api.safetyamp.com/api/medical_facilities" \
  -H "Authorization: Bearer ..."

Response:

{
    "data": [
        {
            "id": "aC2xD",
            "name": "UVA Medical Center",
            "// … remaining site fields": "see Object Reference"
        }
    ],
    "count": 1
}
HTTP Request

GET https://api.safetyamp.com/api/medical_facilities

Get / Create / Update / Delete

GET /api/medical_facilities/<id> POST /api/medical_facilities PATCH /api/medical_facilities/<id> DELETE /api/medical_facilities/<id>

Request body:

Field Description
name (Required) Name of the facility, max 255.
street Address, max 255.
street_2 Address line 2, max 255.
city Max 255.
state Free-form state string, max 255 (not an enum).
zip_code Max 255.
country Max 255.
phone_number Max 255 (no format validation).
notes Free text.

Carriers

Insurance / claims carriers linked to a specific claims integration. Claims reference a carrier's contacts via carrier_contact_id.

Every carrier endpoint requires can_administer_claims.

List Carriers

curl "https://api.safetyamp.com/api/carriers" \
  -H "Authorization: Bearer ..."

Response:

{
    "data": [
        {
            "id": "aC2xD",
            "name": "Broadspire Insurance",
            "claim_type": "workers_comp",
            "integration_id": "8bZ4y"
        }
    ]
}

GET /api/carriers?limit=contacts also includes the carrier's contacts inline.

HTTP Request

GET https://api.safetyamp.com/api/carriers

Include Keys
Key What it adds
contacts Array of the carrier's contacts, inlined. Use contacts:count to return the count only.

Get / Create / Update / Delete

GET /api/carriers/<id> — always includes integration and contacts inline. POST /api/carriers PATCH /api/carriers/<id> DELETE /api/carriers/<id> — soft-deletes the carrier and all its contacts in a single transaction.

Request body:

Field Description
name (Required) String, max 255.
integration_id (Required) Encoded ID of a claims integration. There can be only one carrier per integration — attempting to create a second returns 422.
claim_type Optional. One of auto, general, property_damage, workers_comp.

Add a Contact to a Carrier

Convenience endpoint that creates a contact and attaches it to the given carrier in one step. Under the hood the contact is stored with contactable_id = <carrier id> and contactable_type = "carrier".

curl -X POST "https://api.safetyamp.com/api/carriers/aC2xD/contacts" \
  -H "Authorization: Bearer ..." \
  -H "Content-Type: application/json" \
  -d '{
        "first_name": "Jamie",
        "last_name": "Adjuster",
        "phone_number": "555-123-4567",
        "email": "jamie@broadspire.example"
      }'

POST /api/carriers/<carrier_id>/contacts

Same body shape as POST /api/contacts — see Contacts below.

Contacts

People — adjusters, brokers, and other claim-adjacent contacts. Today contacts can only be attached to carriers (the polymorphic morph map registers only carrier); every other contactable_type value is unused.

Every contact endpoint requires can_administer_claims, even though the plain /contacts routes sit under the feature.incidents flag rather than feature.claims.

List Contacts

curl "https://api.safetyamp.com/api/contacts?filter_groups[0][filters][0][key]=contactable_id&filter_groups[0][filters][0][value]=<carrier_db_id>&filter_groups[0][filters][0][operator]=eq" \
  -H "Authorization: Bearer ..."

Response:

{
    "data": [
        {
            "id": "gH5i6",
            "first_name": "Jamie",
            "last_name": "Adjuster",
            "phone_number": "555-123-4567",
            "email": "jamie@broadspire.example",
            "contactable_id": "aC2xD",
            "contactable_type": "carrier"
        }
    ]
}
HTTP Request

GET https://api.safetyamp.com/api/contacts

Filter Keys
Key Type Notes
contactable_id integer Raw integer DB id of the parent row (e.g. a carrier). See the callout below.
contactable_type string Currently only carrier.

Get / Create / Update / Delete

GET /api/contacts/<id> POST /api/contacts — creates a standalone contact with no parent. PATCH /api/contacts/<id> DELETE /api/contacts/<id>

Request body:

Field Description
first_name (Required) String, max 255.
last_name (Required) String, max 255.
phone_number Optional string, max 255 (no format validation).
email Optional string, max 255 (not validated as an email address).

contactable_id and contactable_type are not accepted through this endpoint. To attach a contact to a carrier, use POST /api/carriers/<carrier_id>/contacts instead.

Object Reference

User

{
    "data": {
        "id": "6Q4N8",
        "owner": false,
        "current_supervisor_id": null,
        "current_department_id": null,
        "email": "chris+conf-homer@safetyamp.com",
        "work_phone": "(345)345-3456",
        "home_phone": "(234)234-2345",
        "mobile_phone": "(123)123-1234",
        "avatar_location": null,
        "created_at": "2019-10-26 15:37:13",
        "created_by": "nRAzE",
        "updated_at": "2019-10-26 15:37:13",
        "updated_by": "nRAzE",
        "deleted_at": null,
        "deleted_by": null,
        "last_name": "Simpson",
        "date_of_birth": "1994-04-15",
        "gender": 1,
        "street": "street",
        "city": "city",
        "state": "state",
        "zip_code": "24477",
        "country": "US",
        "timezone": "America/New_York",
        "mailing_address_same_as_physical": 0,
        "mailing_street": "street2",
        "mailing_street2": "apt 22",
        "mailing_city": "city2",
        "mailing_state": "state2",
        "mailing_zip_code": "24472",
        "mailing_country": "US2",
        "current_title_id": null,
        "current_hire_date": null,
        "current_employee_status_id": null,
        "street2": "apt 20",
        "first_name": "Homer",
        "middle_name": null,
        "nickname": null,
        "activated": 0,
        "system_access": 1,
        "bypass_sso": 0,
        "text_opt_out": 0,
        "home_site_id": null,
        "last_logged_in": null,
        "remember_me": 0,
        "rate_limit": null,
        "constructed_permissions": [],
        "current_title": null,
        "preferences": null,
        "roles": [],
        "active_policies": [],
        "eligibility_events": [],
        "module_permissions": [],
        "current_department": null,
        "current_supervisor": null,
        "sites": [],
        "current_status": null,
        "permissions": []
    }
}

User Format

Key Type Description
email string The email address of the user (also, the username)
first_name string The first name of the user
middle_name string The middle name of the user
last_name string The last name of the user
owner boolean True if the user is the designated account owner
home_site_id int Id of users home site, controls record access
sites array[] Ids of sites user should have access to
roles array[] Ids of roles user should be given, controls system access
current_supervisor_id int The id of the users supervisor
current_department_id int The id of the users department
current_title_id int The id of the users title
mobile_phone string The users mobile phone number - SafetyAmp can send SMS
work_phone string The users work phone number
home_phone string The users home phone number
gender int 0 for male, 1 for female
street string User Address: Street address
street2 string User Address: Street address 2
city string User Address: City
state string User Address: State or Province
zip_code string User Address: Postal Code
country string User Address: Country
activated int 1 for activated, 0 for not activated
system_access int 1 if the user is allowed the ability to login
bypass_sso int If SSO enabled for account, set to 1 to allow user to login without SSO
text_opt_out int 1 if user does not wish to receive SMS on mobile number
last_logged_in string The time the user last used credentials to login, does not represent last activity in the system

Timezones

You can find a complete list of supported timezone strings (.e.g, America/New_York) by inspecting any timezone drop-down menu in the SafetyAmp web application.

Errors

Every error response returns a JSON body with a top-level message describing the failure.

{
    "message": "Resource not found"
}

Validation errors (422 Unprocessable Entity) additionally include an errors object keyed by request field, each mapping to an array of human-readable messages. The top-level message summarizes the first error and how many others there are.

{
    "message": "The event date field is required. (and 4 more errors)",
    "errors": {
        "event_date": [
            "The event date field is required."
        ],
        "event_time": [
            "The event time field must be present."
        ],
        "title": [
            "The title field is required."
        ]
    }
}

The SafetyAmp API uses the following status codes:

Status Meaning
400 Bad Request — Your request is invalid.
401 Unauthorized — Your API key is missing or invalid.
403 Forbidden — You do not have access to the requested resource.
404 Not Found — The specified resource could not be found. Also returned when a feature (e.g. Incidents, Claims) is not enabled on your account.
405 Method Not Allowed — You tried to access a resource with an unsupported HTTP method.
409 Conflict — Your request cannot be completed because it would create a duplicate or conflicts with existing state.
422 Unprocessable Entity — Your request is syntactically correct but one or more fields failed validation. See the errors object for field-level details.
429 Too Many Requests — You've submitted more requests than the rate limit allows.
500 Internal Server Error — We had a problem with our server. Try again later.
503 Service Unavailable — We're temporarily offline for maintenance. Please try again later.