Skip to main content

NG-SOS API

The NG-SOS API is the inbound half of an integration: your system calls NG-SOS. It reports calls, creates incidents, keeps dispatcher-entered data current, reaches the caller through chat, live video or a location SMS, and closes the incident.

The outbound half, where NG-SOS calls your system, is NG-SOS Webhooks v3.

EnvironmentBase URL
Productionhttps://api.ng-sos.com
Azerothhttps://ems.azeroth.ng-sos.com

Before you start

Obtain a client_id, a client_secret, the scopes your application may request and the agency IDs it may act for from NG-SOS. The Identity Server guide covers the token request in full; this page covers only what the API expects.

The typical flow

  1. Get a token from NG-SOS Identity with the scopes the operations need and agency_context set to the agency you act for. Every incident operation, creation included, also needs username.required in scope and the username of the acting dispatcher. Cache it until shortly before it expires.
  2. Report the call with POST /v1/calls, and POST /v1/calls/{callId}/end when it ends, if your system handles the telephony leg.
  3. Create the incident with POST /v1/incidents. caller.phoneNumber is required. The response carries the incident ID.
  4. Keep the incident current. Each dispatcher-entered field has its own PATCH operation: call sign, caller full name, incident label, incident summary and patient description. A caller location is added with POST /v1/incidents/{incidentId}/caller/track.
  5. Reach the caller with caller/locate, which sends a location-link SMS, or with caller/chat/activate and caller/live-video/activate, which return the Portal URL to open for the dispatcher.
  6. Close the incident with POST /v1/incidents/{incidentId}/close. A closed incident rejects every further change with 409 IncidentClosed.

Every operation and its exact request and response shape is in the API reference.

PATCH or POST

VerbMeaning
PATCH on a fieldSets a value you can set again. The five text fields also clear with an empty string.
POST on a collectionAppends an entry that stays. caller/track is the only one.
POST on a sub-resourcePerforms an action, or a transition that happens once.

A transition that already happened answers 409: IncidentClosed, CallAlreadyEnded, ChatAlreadyActive, LiveVideoAlreadyActive, CollaborationInvitationAlreadyResolved.

Routing an incident

POST /v1/incidents takes one of two shapes, selected by routingMode:

routingModeMeaning
TokenAgencyThe incident belongs to the agency in the access token.
JurisdictionNG-SOS resolves the responsible agencies from caller.location, optionally narrowed by preferredAgencyTypes.

caller.location is required for Jurisdiction routing, and NG-SOS stores it as the caller's first known position - the same kind of position POST /v1/incidents/{incidentId}/caller/track adds. Both are GeoPoint: a position a dispatcher or partner picks has no capture time beyond the moment it was picked, and no accuracy. Measured positions with a capture time, an accuracy and a source reach NG-SOS from devices, not over this API, and NG-SOS Webhooks v3 reports them as Location.

Adding to the caller track

POST /v1/incidents/{incidentId}/caller/track appends a position a dispatcher established - the caller described a landmark, a colleague called back with an address. Every location you add is kept: a later one is preferred for display and supersedes the earlier one without replacing it, so the incident keeps the whole sequence of what was known and when. There is no operation to correct or remove an entry; add the better position and it takes over.

The track is the same one NG-SOS Webhooks v3 delivers back as caller.tracks, where each entry carries the source it came from. Positions you add over this API form the track for the dispatcher source; positions measured by a device form their own.

This is why it is a POST on a collection rather than a PATCH on a field. A PATCH in this API sets a value you can set again, and a caller location is not that.

The caller phone number

POST /v1/incidents requires caller.phoneNumber in both routing modes. Your system takes the call, so it knows the number, and NG-SOS needs it for everything it can do for that caller afterwards: caller/locate sends the location-link SMS to it, and caller/chat/activate and caller/live-video/activate reach the caller the same way. An incident created without one could never use any of them.

The number cannot be changed over this API: there is no PATCH for it, and a repeat of POST /v1/incidents under the same requestedIncidentId does not rewrite it. NG-SOS may still resolve a number of its own for incidents raised elsewhere, and NG-SOS Webhooks v3 reports that as PATCH /v3/incident/{incidentId}/caller/phone-number.

Repeating a request that timed out

POST /v1/calls and POST /v1/incidents follow the same rule, so one retry branch covers both: each recognises a repeat on a key, answers 201 when it created the resource and 200 when the key was already taken by a resource matching the request, and answers 409 when the key is taken by something that does not match. A 409 changes nothing.

OperationKey201200409
POST /v1/callsthe agency in the token, callerPhoneNumber and startedAtUtcthe call was createdthe call already existedCallAlreadyExists — the call already ended at a time other than the endedAtUtc you sent
POST /v1/incidentsrequestedIncidentIdthe incident was createdthe incident already existed and matches the requestIncidentAlreadyExists — the ID is taken by an incident that does not match the request

Both 200 and 201 carry the same body, so a retry needs no special handling: read the ID and carry on.

The call key is derived from the request, so POST /v1/calls is repeatable without any extra field. requestedIncidentId — a UUID you generate — is optional, and it is the only thing that makes POST /v1/incidents repeatable: without it NG-SOS cannot tell a repeat from a new incident and every request creates one.

Recovering from a 409:

  • CallAlreadyExists — repeat without endedAtUtc to obtain the call ID, then end the call with POST /v1/calls/{callId}/end. There is no read operation for a call, so repeating the request is the only way to obtain its ID.
  • IncidentAlreadyExists — the incident exists under the ID you sent. Continue with the operations on /v1/incidents/{incidentId} instead of creating another incident. Note that the comparison is against the incident as it stands now, so a repeat sent long after the original request can differ from it because the incident has since been edited.

Ending a call is a transition, not a creation, and is not repeatable: a second POST /v1/calls/{callId}/end answers 409 CallAlreadyEnded.

Clearing a text field

Every PATCH operation that carries dispatcher-entered text — call sign, caller full name, incident label, incident summary, patient description — clears the value when you send null. The property itself stays required, so send it as null rather than leaving it out. An empty string and a value of only whitespace are both rejected with 400 ValidationFailed.

curl --request PATCH "https://api.ng-sos.com/v1/incidents/$INCIDENT_ID/label" \
--header "Authorization: Bearer $ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{"label":null}'

This is the same value NG-SOS Webhooks v3 delivers for a cleared field, so a system that implements both halves can carry a value between them unchanged: the null you receive in IncidentLabelChanged is the null you send back here.

Scopes and token context

Each operation lists the scope it requires. Beyond the scope, operations differ in what the token must identify:

ContextWhat the token request needsOperations
Agencyagency_contextGET /v1/agency/info, POST /v1/calls, POST /v1/calls/{callId}/end
Agency and userusername.required in scope, plus agency_context and usernamePOST /v1/incidents and every operation on an existing incident

An operation that needs the user context but receives an application-only token fails with 403 UserContextRequired; a missing agency context fails with 403 AgencyContextRequired.

Errors

Every error is application/problem+json with a stable code and a type of https://ng-sos.dev/errors/{code}. Log code and traceId; branch on code, never on detail.

{
"type": "https://ng-sos.dev/errors/IncidentClosed",
"title": "Conflict",
"status": 409,
"detail": "The incident is already closed.",
"instance": "/v1/incidents/497f6eca-6276-4993-bfeb-53cbbbba6f08",
"code": "IncidentClosed",
"traceId": "00-example-trace-id"
}
StatuscodeMeaning
400ValidationFailedA field did not satisfy the documented constraints. errors names the offending fields.
400CallerPhoneNumberRequiredThe incident has no caller phone number.
400AgencyNotAllowedThe submitted agency may not be invited to this incident.
401AuthenticationRequiredA valid access token is required.
403AgencyContextRequiredThe token does not identify an agency.
403UserContextRequiredThe token does not identify a user.
403AuthorizationDeniedThe access token does not grant this operation.
404IncidentNotFoundNo incident with that ID is accessible to the token agency.
404CallNotFoundNo call with that ID is owned by the token agency.
409IncidentAlreadyExistsrequestedIncidentId is taken by an incident that does not match the request.
409CallAlreadyExistsThe call already ended at a time other than the endedAtUtc you sent.
409IncidentClosedThe incident is closed and accepts no further change.
409UpdateConflictThe resource changed concurrently and could not be updated.
409ChatAlreadyActiveCaller chat is already active.
409LiveVideoAlreadyActiveLive video is already active on the incident. Activation never fails because a device cannot do video.
409CallAlreadyEndedThe call already has an end time.
409CollaborationInvitationPendingAn invitation for that agency is still open.
409CollaborationAlreadyActiveThat agency already collaborates on the incident.
409NoPendingCollaborationInvitationNo open invitation exists for the authenticated agency.
409CollaborationInvitationAlreadyResolvedThe invitation was already accepted or declined.
415UnsupportedMediaTypeSend the body as application/json.
500InternalServerErrorThe request could not be completed.

An incident that belongs to another agency answers 404 IncidentNotFound rather than 403, so the API never reveals that an incident exists.

Collaboration

An agency involved in an incident can invite another agency from GET /v1/agency/inforelatedAgencies. The invited agency accepts or declines with collaboration/accept or collaboration/decline.