Acessing Audit Data

Audit entries provide a detailed record of changes made to entities within the Salsa platform. As a partner, you can query audit entries for your employers to track modifications, monitor activity, and maintain compliance records.

Authorization

To query audit entries, you'll need to authenticate using your API credentials. See our authorization documentation for details on obtaining and using access tokens.

GraphQL Query

The auditEntries query allows you to retrieve audit logs with various filtering and sorting options. This query returns paginated results with information about who made changes, what was changed, and when.

Query Signature

auditEntries(
  page: Int = 0
  size: Int = 500
  filterBy: AuditEntriesFilterBy
  orderBy: AuditEntriesOrderBy! = { field: AUDIT_TIMESTAMP, sort: ASCENDING }
): AuditEntryConnection!

Available Fields

Here is the complete list of fields available on an AuditEntry:

FieldTypeDescription
idID!Unique identifier for the audit entry
partnerPartnerThe partner associated with the audit entry (if relevant)
employerEmployerThe employer associated with the audit entry (if relevant)
workerWorkerThe worker associated with the audit entry (if relevant)
auditTimestampDateTime!Timestamp of when the audited event occurred (UTC)
entityIdID!The ID of the entity that was changed
entityTypeString!Type of entity (e.g., "Worker", "Employer", "PayrollRun")
parentEntityIdIDID of the parent entity, if applicable
parentEntityTypeStringType of the parent entity, if applicable
actorAuditActor!Who performed the action (see Actor Types below)
actionString!High-level action performed (e.g., "CREATE", "UPDATE", "DELETE")
eventStringDetailed description of what changed, including old and new values
entityString!Full representation of the change
httpRequestAuditHttpRequest!Details about the API request that triggered the change

Actor Types

The actor field tells you who performed the action. It can be one of three types:

AuditUser (a human user):

FieldTypeDescription
typeAuditActorType!Always USER
displayNameString!Display name of the actor
securityRoleString!Role of the user
userIdentifierStringUnique identifier of the user
userEmailStringEmail address of the user

AuditApiClient (an API client):

FieldTypeDescription
typeAuditActorType!Always API_CLIENT
displayNameString!Display name of the actor
securityRoleString!Role of the API client
apiClientString!Name of the API client
userIdentifierStringUser identifier provided by the API client, if any

AuditSystem (system-initiated):

FieldTypeDescription
typeAuditActorType!Always SYSTEM
displayNameString!Display name of the actor
securityRoleString!Security role
userIdentifierStringUser identifier, typically empty for system actions

HTTP Request Details

The httpRequest field tells you where the action originated:

FieldTypeDescription
traceIdString!Unique API request ID for the change
requestIdStringRequest ID supplied by the partner
ipAddressStringIP address that triggered the change
originStringOrigin of the request (null for server-to-server)
requestUrlStringThe original request URL

Available Filters

Filter FieldTypeDescription
entityTypeStringFilter by entity type (e.g., "Worker", "Employer")
workerIdStringFilter by worker ID
auditTimestampBoundedDateTimeIntervalInputFilter by date range (see below)

Date Range Filter

The auditTimestamp filter accepts a bounded date range:

auditTimestamp: {
  start: DateTime!  # When the interval starts (required)
  end: DateTime!    # When the interval ends (required)
}

Sorting Options

You can sort results using the orderBy parameter:

FieldAllowed Values
fieldAUDIT_TIMESTAMP, ACTION, PARTNER_ID, EMPLOYER_ID, WORKER_ID
sortASCENDING, DESCENDING

Default: { field: AUDIT_TIMESTAMP, sort: ASCENDING }


Example Queries

Basic Query: Get Recent Audit Entries for an Employer

query GetAuditEntries($filterBy: AuditEntriesFilterBy, $page: Int, $size: Int) {
  auditEntries(filterBy: $filterBy, page: $page, size: $size) {
    nodes {
      id
      auditTimestamp
      entityId
      entityType
      action
      event
      entity
    }
    totalPages
  }
}

Variables:

{
  "filterBy": {
    "auditTimestamp": {
      "start": "2024-11-01T00:00:00Z",
      "end": "2024-11-30T23:59:59Z"
    }
  },
  "page": 0,
  "size": 25
}

Query with Actor Details: See Who Made Changes

query GetAuditEntriesWithActor($filterBy: AuditEntriesFilterBy, $page: Int, $size: Int) {
  auditEntries(filterBy: $filterBy, page: $page, size: $size) {
    nodes {
      id
      auditTimestamp
      entityId
      entityType
      action
      event
      actor {
        type
        displayName
        securityRole
        ... on AuditUser {
          userIdentifier
          userEmail
        }
        ... on AuditApiClient {
          apiClient
          userIdentifier
        }
      }
    }
    totalPages
  }
}

Query for Specific Entity Type: Track Worker Changes

query GetWorkerAuditEntries($filterBy: AuditEntriesFilterBy, $page: Int, $size: Int) {
  auditEntries(filterBy: $filterBy, page: $page, size: $size) {
    nodes {
      id
      auditTimestamp
      action
      event
      entity
      worker {
        id
        firstName
        lastName
      }
      actor {
        type
        displayName
      }
    }
    totalPages
  }
}

Variables:

{
  "filterBy": {
    "entityType": "Worker",
    "auditTimestamp": {
      "start": "2024-11-01T00:00:00Z",
      "end": "2024-11-30T23:59:59Z"
    }
  },
  "page": 0,
  "size": 25
}

Query with Full Request Context

query GetAuditEntriesWithContext($filterBy: AuditEntriesFilterBy, $page: Int, $size: Int) {
  auditEntries(filterBy: $filterBy, page: $page, size: $size) {
    nodes {
      id
      auditTimestamp
      entityId
      entityType
      parentEntityId
      parentEntityType
      action
      event
      entity
      actor {
        type
        displayName
        securityRole
      }
      httpRequest {
        traceId
        requestId
        ipAddress
        requestUrl
      }
    }
    totalPages
  }
}

Understanding Changes (Old vs New Values)

The event field contains a human-readable description of the change, often including the old and new values. For example:

"Worker.Updated: email changed from '[email protected]' to '[email protected]'"

The entity field contains the full representation of the change in a structured format.

Example Response

{
  "data": {
    "auditEntries": {
      "nodes": [
        {
          "id": "auditentry_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
          "auditTimestamp": "2024-11-15T14:32:10Z",
          "entityId": "wkr_12345678-90ab-cdef-1234-567890abcdef",
          "entityType": "Worker",
          "action": "UPDATE",
          "event": "Worker.Updated: email changed from '[email protected]' to '[email protected]'",
          "entity": "{\"id\":\"wkr_12345678\",\"email\":\"[email protected]\",\"previousEmail\":\"[email protected]\"}",
          "actor": {
            "type": "API_CLIENT",
            "displayName": "partner-api-client",
            "securityRole": "PARTNER_ADMIN",
            "apiClient": "partner-api-client",
            "userIdentifier": "[email protected]"
          },
          "httpRequest": {
            "traceId": "trace_98765432-10fe-dcba",
            "requestId": "req_98765432",
            "ipAddress": "192.168.1.1",
            "requestUrl": "/graphql"
          }
        }
      ],
      "totalPages": 5
    }
  }
}

Pagination

The auditEntries query uses page-based pagination:

ParameterTypeDefaultDescription
pageInt0Page number (zero-indexed)
sizeInt500Number of entries per page (max 500)

The response includes totalPages to help you determine how many pages of results exist.

Example: Paginating Through Results

First page:

{
  "filterBy": { "entityType": "PayrollRun" },
  "page": 0,
  "size": 25
}

Second page:

{
  "filterBy": { "entityType": "PayrollRun" },
  "page": 1,
  "size": 25
}

Continue incrementing page until you reach totalPages - 1.


Common Use Cases

1. Track All Changes to a Specific Worker

{
  "filterBy": {
    "workerId": "wkr_12345678-90ab-cdef-1234-567890abcdef"
  }
}

2. Find All Changes in a Date Range

{
  "filterBy": {
    "auditTimestamp": {
      "start": "2024-11-01T00:00:00Z",
      "end": "2024-11-30T23:59:59Z"
    }
  }
}

3. Track Payroll Run Changes

{
  "filterBy": {
    "entityType": "PayrollRun"
  }
}

4. Track Worker Changes in a Date Range

{
  "filterBy": {
    "workerId": "wkr_12345678-90ab-cdef-1234-567890abcdef",
    "auditTimestamp": {
      "start": "2024-11-01T00:00:00Z",
      "end": "2024-11-30T23:59:59Z"
    }
  }
}

Audit Coverage

This section describes which actions generate audit entries and which do not.

Currently Audited Entities

The following entity types and actions will generate audit entries:

ModuleEntityActions
Money MovementEmployer bank accountCREATE, UPDATE, DELETE
Worker bank accountCREATE, UPDATE, DELETE
Employer money movement policyCREATE, UPDATE
Pay distributionCREATE, UPDATE
ProfileEmployer taxes setupCREATE, UPDATE, DELETE
Worker taxes setupCREATE, UPDATE, DELETE, OVERRIDE
Worker addressCREATE, UPDATE, TERMINATE
Worker contractCREATE, UPDATE, DELETE
Worker work locationADD, UPDATE, REMOVE
Employer addressCREATE, UPDATE, DELETE
PayrollPayroll runRUN, CONFIRM, CANCEL, DELETE, UPDATE, CONVERT_TO_MANUAL_DISBURSEMENT
Worker payroll runCREATE
Payroll run workers listINCLUDE, EXCLUDE
Worker payroll elementUPDATE
Worker pay distributionUPDATE
Partner payroll settingsCREATE, UPDATE, DELETE
AccountingPartner accounting settingsCREATE, UPDATE, DELETE
TransmissionRTS transmission requirement eligibilityEVALUATE

Not Currently Audited

The following changes do not generate audit entries:

  • Worker primary data: firstName, lastName, displayName, email, employerSpecifiedIdentifier, partnerExternalId
  • Employer primary data: name, displayName, legal name
  • Worker personal information: dateOfBirth, pronouns, gender

Why Some Changes Don't Appear in the Audit Log

Salsa uses an explicit audit logging system where only specific entities and actions are configured for auditing. If you make a change that doesn't appear in the audit log (such as updating a worker's name), it's because that particular entity or action is not currently included in audit coverage.

This approach:

  • Keeps audit logs focused on high-value compliance and financial data
  • Reduces noise from frequent, low-risk changes
  • Optimizes query performance for compliance reporting

If you require audit coverage for additional entities or actions, please contact Salsa support to discuss your requirements.