Skip to content

web sdk incidents

Andre Lafleur edited this page Apr 24, 2026 · 17 revisions

The /incident endpoint

The /incident endpoint provides programmatic control over Security Center incidents. Incidents are records used to document and track events, investigations, and operational activities within your security environment.

Overview

The incident endpoint provides operations for creating, reading, and updating incidents.

Key characteristics:

  • Create new incidents with field initialization
  • Read existing incident data
  • Update existing incident properties
  • Support for batch incident creation
  • Link incidents to alarms, events, and entities
  • Read attached data and tile recording metadata
  • Geographic location support
  • Category management via SystemConfiguration entity
  • Uses q= parameter for create, update, and projected read operations

To search and list incidents, use the Incident report described in The /report endpoint.

Important limitations:

  • Incidents cannot be deleted via the Web SDK
  • Updating incidents requires the Modify reported incidents privilege
  • Category management uses the SystemConfiguration entity, not the /incident endpoint

HTTP methods

HTTP Method Purpose When to Use
PUT Create or update incidents Creating new incidents or modifying existing ones
GET Read incidents Retrieving incident data

Note

DELETE and POST methods are not supported on the /incident endpoint.

Creating incidents

Use PUT requests to create incidents and initialize fields in the same q= string.

Basic incident creation

Create a new incident and retrieve its GUID:

PUT /incident?q=Title=Security Breach,Notes=Unauthorized access detected,Guid

Response:

{
  "Rsp": {
    "Status": "Ok",
    "Result": {
      "Guid": "68f12d82-4831-4944-b894-375599c5e596"
    }
  }
}

Important

Always request the Guid field to get the newly created incident's identifier.

Setting incident properties during creation

All writable incident properties:

PUT /incident?q=Title=Perimeter Breach,Notes=Motion detected in restricted area,Category=Security,FormattedText=<p>Detailed notes</p>,Source={camera-guid},Timestamp=2025-01-15T10:30:00Z,References@{entity-guid1}@{entity-guid2},Guid

Key properties:

  • Title (string) - Incident title
  • Notes (string) - Investigation notes and details
  • Category (string) - Incident category (must exist in system)
  • FormattedText (string) - Rich text formatted content
  • Source (GUID) - Source entity GUID
  • Timestamp (DateTime) - When incident occurred (defaults to 0001-01-01T00:00:00)
  • References (Collection) - Related entity GUIDs
  • Template (GUID) - Incident template GUID
  • Event (EventType) - Trigger event type (enum name or numeric value)
  • EventEntities (Collection) - Entities involved in the event
  • AlarmInstance (int) - Linked alarm instance ID
  • Location (GeoCoordinate) - Geographic location

Creating incident with category

Important

Categories must exist in the system before assignment.

PUT /incident?q=Title=Door Forced Open,Notes=Maintenance required,Category=Security,Guid

If category doesn't exist:

{
  "Rsp": {
    "Status": "Fail",
    "Result": {
      "SdkErrorCode": "InvalidOperation",
      "Message": "Could not save incident: Unknown category"
    }
  }
}

Solution: Add the category first using SystemConfiguration entity (see Incident Category Management).

Creating incident from alarm

Link incident to a triggered alarm:

PUT /incident?q=Title=Alarm Investigation,AlarmInstance=42,Source={alarm-guid},References@{alarm-guid}@{source-entity-guid},Guid

Parameters:

  • AlarmInstance - Instance ID returned from TriggerAlarm()
  • Source - Alarm entity GUID
  • References - Related entities

Creating incident from event

Associate incident with a specific event type:

PUT /incident?q=Title=Access Denied Event,Event=10,Source={door-guid},EventEntities@{cardholder-guid}@{credential-guid},References@{door-guid},Guid

Parameters:

  • Event - EventType value (numeric or enum name, e.g., 10 or None)
  • EventEntities - Collection of entities involved in the event
  • Source - Entity that raised the event

Note

Event accepts valid EventType enum names and numeric values. For common EventType values, see Monitoring events and alarms.

Reading incidents

Use GET requests to retrieve incident data, either as selected fields or as the full incident object.

Read specific fields

Retrieve specific incident properties:

GET /incident?q=GetIncident({incident-guid}),Title,Notes,Category,CreatedBy,CreationTime

Use GetIncident({guid}) as the existing-incident selector. incident({guid}) is not a valid selector on /incident.

Important

GET /incident?q=GetIncident(...) changes the incident's modification metadata. The LastModificationTime and LastModifiedBy values returned by that same query can still reflect the earlier state. Use GET /incident/{incident-guid} when you need a read that preserves the incident or when you need to confirm the current modification metadata.

Response:

{
  "Rsp": {
    "Status": "Ok",
    "Result": {
      "Title": "Security Breach",
      "Notes": "Unauthorized access detected",
      "Category": "Security",
      "CreatedBy": "00000000-0000-0000-0000-000000000003",
      "CreationTime": "2025-11-15T08:05:50.38Z"
    }
  }
}

Get complete incident data

Retrieve all properties of an incident:

GET /incident/{incident-guid}

GET /incident/{incident-guid} does not support field projection. It always returns the full incident object.

Response includes all incident properties:

{
  "Rsp": {
    "Status": "Ok",
    "Result": {
      "Guid": "68f12d82-4831-4944-b894-375599c5e596",
      "Title": "Test Incident",
      "Notes": "",
      "Category": "",
      "FormattedText": null,
      "Source": "00000000-0000-0000-0000-000000000000",
      "Template": "00000000-0000-0000-0000-000000000000",
      "Timestamp": "0001-01-01T00:00:00",
      "Event": "None",
      "EventEntities": [],
      "References": [],
      "AlarmInstance": -1,
      "Location": null,
      "CreatedBy": "00000000-0000-0000-0000-000000000003",
      "CreationTime": "2025-11-15T08:05:50.38Z",
      "LastModifiedBy": "00000000-0000-0000-0000-000000000003",
      "LastModificationTime": "2025-11-15T08:05:50.38Z"
    }
  }
}

Read multiple incidents

Query multiple incidents in one request:

GET /incident?q=GetIncident({guid1}),Title,Category,GetIncident({guid2}),Title,Category,GetIncident({guid3}),Title,Category

Response:

{
  "Rsp": {
    "Status": "Ok",
    "Result": [
      {
        "Title": "Incident 1",
        "Category": "Security"
      },
      {
        "Title": "Incident 2",
        "Category": "Safety"
      },
      {
        "Title": "Incident 3",
        "Category": "Operational"
      }
    ]
  }
}

Read incident metadata

Retrieve creation and modification metadata:

GET /incident?q=GetIncident({incident-guid}),CreatedBy,CreationTime,LastModifiedBy,LastModificationTime

Response:

{
  "Rsp": {
    "Status": "Ok",
    "Result": {
      "CreatedBy": "00000000-0000-0000-0000-000000000003",
      "CreationTime": "2025-11-15T08:05:50.38Z",
      "LastModifiedBy": "00000000-0000-0000-0000-000000000003",
      "LastModificationTime": "2025-11-15T08:05:50.38Z"
    }
  }
}

Incident properties reference

The table below separates writable properties from server-managed metadata.

Complete property list

Property Type Read/Write Default Value Description
Guid GUID Read-only Auto-generated Unique incident identifier
Title String Read/Write "" (empty) Incident title
Notes String Read/Write "" (empty) Investigation notes
Category String Read/Write "" (empty) Incident category (must exist)
FormattedText String Read/Write null Rich text formatted content
Source GUID Read/Write 00000000-0000-0000-0000-000000000000 Source entity GUID
Template GUID Read/Write 00000000-0000-0000-0000-000000000000 Incident template GUID
Timestamp DateTime Read/Write 0001-01-01T00:00:00 When incident occurred
Event EventType Read/Write "None" Trigger event type (enum name or numeric value on input)
EventEntities Collection Read/Write [] (empty array) Entities involved in event
References Collection Read/Write [] (empty array) Related entities
AlarmInstance Integer Read/Write -1 Linked alarm instance ID
Location GeoCoordinate Read/Write null Geographic coordinates
CreatedBy GUID Read-only Authenticated user User who created incident
CreationTime DateTime Read-only Current time Creation timestamp
LastModifiedBy GUID Read-only Authenticated user User who last modified
LastModificationTime DateTime Read-only Current time Last modification timestamp

Read-only vs writable properties

Read-only properties (automatically managed):

  • Guid - Assigned at creation
  • CreatedBy - Authenticated user at creation
  • CreationTime - Timestamp when incident created
  • LastModifiedBy - Authenticated user on last update
  • LastModificationTime - Timestamp of last update

Writable properties (can be set during creation or updated later):

  • All others listed in table above

Important

Updating existing incidents requires the Modify reported incidents privilege.

Updating incidents

Update properties on an existing incident using GetIncident({guid}) with PUT:

PUT /incident?q=GetIncident({incident-guid}),Notes=Updated investigation notes

Response:

{
  "Rsp": {
    "Status": "Ok"
  }
}

Update multiple properties at once:

PUT /incident?q=GetIncident({incident-guid}),Notes=Case reassigned,Category=Security

Linking incidents to alarms and events

These patterns help you create incidents that preserve the original alarm or event context.

Linking to alarm instance

When an alarm triggers and you want to create a follow-up incident:

Step 1: Trigger alarm and capture instance ID

GET /alarm?q=TriggerAlarm({alarm-guid},{source-guid})

Response:

{
  "Rsp": {
    "Status": "Ok",
    "Result": {
      "alarminstanceid": 42
    }
  }
}

Step 2: Create incident linked to alarm instance

PUT /incident?q=Title=Alarm Follow-up,AlarmInstance=42,Source={alarm-guid},References@{alarm-guid}@{source-guid},Guid

Linking to event

Associate incident with specific event occurrence:

PUT /incident?q=Title=Access Violation,Event=11,Source={door-guid},EventEntities@{cardholder-guid}@{credential-guid}@{door-guid},References@{door-guid}@{cardholder-guid},Timestamp=2025-01-15T10:30:15Z,Guid

Fields explained:

  • Event - EventType value (name or numeric)
  • Source - Entity that raised the event
  • EventEntities - All entities involved (cardholder, credential, door, etc.)
  • References - Primary entities for incident context
  • Timestamp - Exact time event occurred

Common EventType values:

  • AccessGranted (35)
  • AccessRefused (36)
  • CardholderAccessGranted (10)
  • CardholderAccessRefused (11)
  • DoorOpen (27)
  • DoorClose (28)
  • AlarmTriggered (2)
  • AlarmAcknowledged (3)

For the complete list, see the event reference table in the events and alarms article.

Complete alarm-to-incident workflow

Automated workflow example:

  1. Trigger the alarm.

    GET /alarm?q=TriggerAlarm({alarm-guid},{door-guid})

    The response includes alarminstanceid: 42.

  2. Create the incident linked to the alarm.

    PUT /incident?q=Title=Door Breach Investigation,Notes=Forced door entry detected,AlarmInstance=42,Event=29,Source={door-guid},EventEntities@{door-guid},References@{alarm-guid}@{door-guid},Timestamp=2025-01-15T10:30:00Z,Category=Security,Guid
  3. Optionally acknowledge the alarm.

    GET /alarm?q=ForceAcknowledgeAlarm(42,{alarm-guid})

Geographic location support

Incidents support geographic coordinates for mapping incident locations.

Setting location during creation

PUT /incident?q=Title=Outdoor Incident,Notes=Perimeter breach,References@{camera-guid},Location=GeoCoordinate(45.5017,-73.5673),Guid

GeoCoordinate format:

GeoCoordinate(latitude,longitude)

Parameters:

  • latitude - Decimal degrees (e.g., 45.5017 for Montreal)
  • longitude - Decimal degrees (e.g., -73.5673 for Montreal)

Reading location

GET /incident?q=GetIncident({incident-guid}),Location

Response:

{
  "Rsp": {
    "Status": "Ok",
    "Result": {
      "Location": {
        "IsUnknown": false,
        "Altitude": null,
        "Latitude": 45.5017,
        "Longitude": -73.5673
      }
    }
  }
}

Use cases for location

Mobile incidents:

  • Patroller incidents with GPS coordinates
  • Mobile asset tracking incidents
  • Outdoor perimeter breaches

Integration with maps:

  • Display incidents on facility maps
  • Geographic clustering analysis
  • Proximity-based incident correlation

Incident attachments

The endpoint exposes read methods for incident attached data and tile recording metadata. Attachment setters are not available through /incident?q=....

Attached data

Retrieve custom key-value data attached to incidents, organized by "attachment kind".

Get attached data

PUT /incident?q=GetIncident({incident-guid}),GetAttachedData(investigation)

This call must use PUT. GET and POST do not work for this method.

Important

GetAttachedData(...) and GetTileRecording() are not side-effect-free reads. These PUT /incident?q=GetIncident(...),... calls update the incident's modification metadata, including LastModificationTime.

Response:

{
  "Rsp": {
    "Status": "Ok",
    "Result": {
      "GetAttachedData": [
        {
          "Identifier": "witness",
          "Data": "John Doe"
        }
      ]
    }
  }
}

When no data exists for that attachment kind, GetAttachedData(...) returns an empty array.

Attached recordings

Retrieve tile recordings linked to incidents.

Get tile recording

PUT /incident?q=GetIncident({incident-guid}),GetTileRecording()

This call must use PUT. GET and POST do not work for this method.

Response:

{
  "Rsp": {
    "Status": "Ok",
    "Result": {
      "GetTileRecording": {}
    }
  }
}

When no tile recording is attached, GetTileRecording() returns an empty object.

SetAttachedData(...) is not supported in /incident?q=... requests.

Incident category management

Incident categories must exist in the system before they can be assigned to incidents. Category management is performed through the SystemConfiguration entity (see Well-Known Entity GUIDs), not the /incident endpoint.

SystemConfiguration GUID: 00000000-0000-0000-0000-000000000007

Get all categories

GET /entity?q=entity=00000000-0000-0000-0000-000000000007,GetIncidentCategories()

Response:

{
  "Rsp": {
    "Status": "Ok",
    "Result": {
      "GetIncidentCategories": [
        "Security",
        "Safety Incidents",
        "Operational",
        "Maintenance"
      ]
    }
  }
}

Add new category

POST /entity?q=entity=00000000-0000-0000-0000-000000000007,AddIncidentCategory(Emergency%20Response)

URL-encode category names that contain spaces or special characters.

Response:

{
  "Rsp": {
    "Status": "Ok",
    "Result": {
      "AddIncidentCategory": true
    }
  }
}

Result values:

  • true - Category added successfully
  • false - Category already exists

Edit category

Rename an existing category:

POST /entity?q=entity=00000000-0000-0000-0000-000000000007,EditIncidentCategory(Security,Security%20Incidents)

Parameters:

  • First parameter: Original category name
  • Second parameter: New category name

URL-encode category names that contain spaces or special characters.

Response:

{
  "Rsp": {
    "Status": "Ok",
    "Result": {
      "EditIncidentCategory": true
    }
  }
}

Result values:

  • true - Category renamed successfully
  • false - Original category not found or new name already exists

Important

Renaming a category updates the category list but does not update existing incidents. Incidents keep the old category string, and GET /incident?q=GetIncident(...) then fails with Unknown category for those incidents. Use GET /incident/{incident-guid} to read them, and update the incident category explicitly if you need projected query reads to work again.

Remove category

POST /entity?q=entity=00000000-0000-0000-0000-000000000007,RemoveIncidentCategory(Maintenance)

Response:

{
  "Rsp": {
    "Status": "Ok",
    "Result": {
      "RemoveIncidentCategory": true
    }
  }
}

Result values:

  • true - Category removed successfully
  • false - Category not found

Important

  • Removing a category does not delete incidents using that category
  • Incidents with a removed category retain the category value
  • The removed category will not appear in category dropdown/autocomplete
  • Query-based reads (GET /incident?q=GetIncident(...)) fail with Unknown category for incidents that still reference a category name that no longer exists in the category list. The same behavior occurs after renaming a category. Use GET /incident/{incident-guid} to read these incidents.

Category validation on incident creation

When creating an incident with a category:

Valid category (exists in system):

PUT /incident?q=Title=Test,Category=Security,Guid

Result: The request succeeds.

Invalid category (does not exist):

PUT /incident?q=Title=Test,Category=NonExistent,Guid

Response:

{
  "Rsp": {
    "Status": "Fail",
    "Result": {
      "SdkErrorCode": "InvalidOperation",
      "Message": "Could not save incident: Unknown category"
    }
  }
}

Solution: Add the category, then create the incident.

  1. Add the category.

    POST /entity?q=entity=00000000-0000-0000-0000-000000000007,AddIncidentCategory(NonExistent)
  2. Create the incident.

    PUT /incident?q=Title=Test,Category=NonExistent,Guid

Incident retention management

Incident retention is also managed through the SystemConfiguration entity, not through the /incident endpoint.

Important

GetRetentionPeriods() and SetRetentionPeriods(...) require a Security Center administrator account.

Use the same SystemConfiguration GUID:

00000000-0000-0000-0000-000000000007

Read current retention settings

GET /entity?q=entity=00000000-0000-0000-0000-000000000007,GetRetentionPeriods()

GetRetentionPeriods() returns all retention families managed by Security Center, including:

  • ArchivedAlarmRetention
  • AuditTrailRetention
  • IncidentRetention
  • InfiniteArchivedAlarmRetention
  • InfiniteAuditTrailRetention
  • InfiniteIncidentRetention

When you are working with incidents, the important fields are IncidentRetention and InfiniteIncidentRetention.

Set the incident retention period

POST /entity?q=entity=00000000-0000-0000-0000-000000000007,SetRetentionPeriods(IncidentRetention,{days})

days is the number of days to keep incidents before automatic cleanup.

Enable or disable infinite incident retention

POST /entity?q=entity=00000000-0000-0000-0000-000000000007,SetRetentionPeriods(InfiniteIncidentRetention,{true-or-false})

Use true to keep incidents indefinitely, or false to use the numeric retention period.

SetRetentionPeriods returns the full retention settings object after the change:

Note

The field EnableAudiTrailLogging is the actual name returned by the server (the missing t in "Audit" is a known historical quirk in the product, not a documentation typo).

{
  "SetRetentionPeriods": {
    "ArchivedAlarmRetention": 90,
    "InfiniteArchivedAlarmRetention": false,
    "EnableAudiTrailLogging": true,
    "AuditTrailRetention": 90,
    "InfiniteAuditTrailRetention": false,
    "IncidentRetention": 90,
    "InfiniteIncidentRetention": false
  }
}

Caution

Retention settings are system-wide. Changing IncidentRetention affects all incidents in the system, not just a specific category or subset.

Batch operations

Use batching when you need to create or read several incidents in one request.

Batch incident creation

Create multiple incidents in a single request using the NewIncident keyword:

PUT /incident?q=Title=Incident 1,Notes=First incident,Category=Security,NewIncident,Title=Incident 2,Notes=Second incident,Category=Security,NewIncident,Title=Incident 3,Notes=Third incident,Category=Security

How it works:

  1. Properties before first NewIncident create the first incident
  2. Each NewIncident keyword triggers creation of the previous incident and starts a new one
  3. Properties after a NewIncident apply to that new incident
  4. Final incident is created when request completes

Important

Batch incident creation is not all-or-nothing. Each incident is created before the request moves on to the next NewIncident block. If a later block fails, incidents that were already created remain created. A failed response does not necessarily return the GUIDs of the incidents that were already committed, so reconcile partial success by reading the created incidents back or by querying the Incident report.

Response (without Guid requested):

{
  "Rsp": {
    "Status": "Ok"
  }
}

To retrieve GUIDs of created incidents:

PUT /incident?q=Title=Incident 1,Category=Security,Guid,NewIncident,Title=Incident 2,Category=Security,Guid

Response:

{
  "Rsp": {
    "Status": "Ok",
    "Result": [
      {
        "Guid": "2e6e5fc5-dc10-4094-acfd-a0b1f3bcb382"
      },
      {
        "Guid": "eeb169ed-98bb-4ddb-a46d-e6ea88d7db80"
      }
    ]
  }
}

Batch read operations

Query multiple incidents with different fields:

GET /incident?q=GetIncident({guid1}),Title,Category,CreationTime,GetIncident({guid2}),Title,Notes,GetIncident({guid3}),AlarmInstance,Source

Response:

{
  "Rsp": {
    "Status": "Ok",
    "Result": [
      {
        "Title": "Incident 1",
        "Category": "Security",
        "CreationTime": "2025-11-15T08:00:00Z"
      },
      {
        "Title": "Incident 2",
        "Notes": "Investigation notes"
      },
      {
        "AlarmInstance": 42,
        "Source": "alarm-guid"
      }
    ]
  }
}

Batch category operations

Add multiple categories:

POST /entity?q=entity=00000000-0000-0000-0000-000000000007,AddIncidentCategory(Security),AddIncidentCategory(Safety),AddIncidentCategory(Operational)

Response:

{
  "Rsp": {
    "Status": "Ok",
    "Result": {
      "AddIncidentCategory": true
    }
  }
}

Note

When the same method is called multiple times in one request, the response contains only the last result. Verify the final state with GetIncidentCategories() to confirm all categories were added.

Error handling

These response shapes show the most common success and failure patterns returned by /incident.

Successful operation

{
  "Rsp": {
    "Status": "Ok",
    "Result": {
      "Guid": "68f12d82-4831-4944-b894-375599c5e596"
    }
  }
}

Failed operation

{
  "Rsp": {
    "Status": "Fail",
    "Result": {
      "SdkErrorCode": "InvalidOperation",
      "Message": "Could not find property 'InvalidProperty' from filter: 'InvalidProperty=test'"
    }
  }
}

Common errors

Invalid category:

{
  "Rsp": {
    "Status": "Fail",
    "Result": {
      "SdkErrorCode": "InvalidOperation",
      "Message": "Could not save incident: Unknown category"
    }
  }
}

Using GET method for incident creation:

{
  "Rsp": {
    "Status": "Fail",
    "Result": {
      "SdkErrorCode": "InvalidOperation",
      "Message": "Cannot create new incident with GET method: NewIncident"
    }
  }
}

Invalid property name:

{
  "Rsp": {
    "Status": "Fail",
    "Result": {
      "SdkErrorCode": "InvalidOperation",
      "Message": "Could not find property 'Description' from filter: 'Description=test'"
    }
  }
}

Invalid GUID format:

{
  "Rsp": {
    "Status": "Fail",
    "Result": {
      "SdkErrorCode": "InvalidValue",
      "Message": "Could not parse to a GUID value 'invalid-guid' from request 'GetIncident(invalid-guid)'"
    }
  }
}

Incident not found:

{
  "Rsp": {
    "Status": "Fail",
    "Result": {
      "SdkErrorCode": "InvalidValue",
      "Message": "Could not find incident with guid {guid}. Error: Unknown error"
    }
  }
}

Permission denied (create incidents):

{
  "Rsp": {
    "Status": "Fail",
    "Result": {
      "SdkErrorCode": "UnsufficientPrivilege",
      "Message": "System.Exception: Logged used does not have the privilege to create incident."
    }
  }
}

Permission denied (modify incidents):

{
  "Rsp": {
    "Status": "Fail",
    "Result": {
      "SdkErrorCode": "InvalidOperation",
      "Message": "Cannot execute following operation without ModifyIncidents privilege: {filter}"
    }
  }
}

Missing query parameter:

{
  "Rsp": {
    "Status": "Fail",
    "Result": {
      "SdkErrorCode": "InvalidOperation",
      "Message": "Must supply query."
    }
  }
}

Invalid Event value (unrecognized enum name):

{
  "Rsp": {
    "Status": "Fail",
    "Result": {
      "SdkErrorCode": "InvalidOperation",
      "Message": "Could not parse value InvalidEventName for property Event from filter: 'Event=InvalidEventName'"
    }
  }
}

Unsupported attached-data setter call:

{
  "Rsp": {
    "Status": "Fail",
    "Result": {
      "SdkErrorCode": "InvalidOperation",
      "Message": "Could not call method SetAttachedData from SetAttachedData(...). Error: No method matches the parameters."
    }
  }
}

Permissions

The required privilege depends on whether you are creating, reading, or modifying incidents.

Required privileges

Create incidents:

  • Privilege: Report incidents
  • Operations: PUT with new incident

View incidents:

  • Operations: GET operations

Update incidents:

  • Privilege: Modify reported incidents
  • Operations: PUT with GetIncident({guid}) to modify existing incidents

Manage categories:

  • Privilege: Modify incident categories
  • Operations: AddIncidentCategory, EditIncidentCategory, RemoveIncidentCategory

Checking user privileges

Configure privileges in Config Tool:

  1. Navigate to User configuration
  2. Select user or user group
  3. Go to Privileges tab
  4. Find "Incidents" section
  5. Enable required privileges

See also

Platform SDK

Plugin SDK

Workspace SDK

Media SDK

Macro SDK

Web SDK

Media Gateway

Genetec Web Player

Clone this wiki locally