Skip to main content

ServiceNow API

IT Service Management

The ServiceNow API connector integrates with ServiceNow custom API endpoints described by an OpenAPI 3.x specification. Unlike the standard ServiceNow connector, which syncs a fixed set of predefined tables, this connector discovers its models at runtime from a specification you supply — so one connector covers any scoped application or proprietary endpoint set, without code changes.

Because the models come from your specification, the object set is different for every integration. The sections below describe how a specification becomes models, what kinds of model it can produce, how attributes and record identity are resolved, and the settings available where an inference needs correcting.

How models are discovered​

The connector parses the specification, groups its endpoints by the resource each one acts on, and registers one model per resource — so the endpoints that read, create, update and delete the same records become a single model rather than one model each.

Grouping reduces a path to the noun it is about: templated segments are dropped, trailing action segments (create, update, delete, query, list, search, and so on) are stripped, and the final segment is singularized. All of these describe one resource and produce one User model:

GET /api/x/user POST /api/x/user/create
POST /api/x/user GET /api/x/user/query
PATCH /api/x/user/{sys_id} POST /api/x/user/update
DELETE /api/x/user/{sys_id} POST /api/x/user/delete

The last literal segment is never stripped, so a resource genuinely named list or search survives, and matching is exact — watchlist and getRequestList are resources, not actions.

Within a resource, roles are claimed in order — list, then create, then update, then delete. Claiming removes an endpoint from the pool, so one endpoint fills at most one role, and the first matching rule wins:

RoleRules, in order
ListlistEndpoint setting · GET on */query/list · POST on */query/list · a GET with no path template whose response is a record collection · a GET opted in by recordsPath
CreatecreateEndpoint setting · POST whose last path segment is create, insert, new or add · POST at the collection with no path template · PUT with no template, where nothing else could be the update
UpdateupdateEndpoint setting · PATCH · PUT · POST whose last path segment is update, edit, modify, upsert, save, set or patch · POST at a templated path
DeletedeleteEndpoint setting · DELETE · POST whose last path segment is delete, remove or destroy

PATCH is preferred over PUT for updates: the connector sends only the attributes the platform supplied, which is patch semantics. Create and update additionally require a request body a write can be built from — either one declaring mappable fields, or a raw payload body the connector can send as file content. A body that is absent, or nothing but an array of objects, is neither, and cannot serve as a create or update however its path is named. Delete has no such requirement, but it must be able to say which record it acts on.

An endpoint that fills no role is not modelled. Discovery logs it with the reason, and it can be brought in by naming it in modelOverrides. The full mapping — every resource, every endpoint, and what became of each — is written to the connector log at discovery and again during Test Connection.

Model kinds​

A resource produces one of three kinds of model, and the model's title says which.

Record model. The resource offers a list endpoint, so the model reads. It also writes through whichever of create, update and delete were found. This is the ordinary case.

Push-only model. The resource offers no list endpoint — only POST /api/x/ticket/create and PUT /api/x/ticket/{sys_id}, say. Its title reads Ticket (push only). Such a model writes and never reads: a sync of it always reports zero records, and reports success, making no HTTP call at all. That is deliberate. The platform syncs a model immediately after pushing to it, to reconcile the ids it was handed, and failing there would mark a push that genuinely reached ServiceNow as a failure.

Prefer a model that reads where one is available. If the resource is readable through a GET that role inference did not recognize as a list, name it and the model appears with both sides and no push-only label:

modelOverrides = {"Ticket":{"listEndpoint":"GET /api/x/ticket/search"}}

A resource offering only a delete is still not modelled: with no list, create or update it has no records to show and no attributes to declare, so there would be nothing to aim the delete at.

Two things to plan for before mapping a push-only model. The identity its create response returns is the only record of the push — nothing revisits it, so that id has to be one that exists in ServiceNow. And the record the platform stores is a permanent stub carrying the pushed identity and none of the attributes the push mapped, so a file's name, size or URL is visible only at the source. For the same reason, do not configure a data lifecycle policy on a push-only model's source data model: a purge removes the pushed dataset but not the push metadata behind it, so the platform goes on treating later pushes to those records as updates while showing nothing for them.

File content model. Some endpoints take a file rather than a record: the request body is the content itself and everything else travels in the query string. An attachment endpoint is the usual example. The connector infers this from the specification — a request body that declares no application/json media type and whose schema has no properties can only be written as a raw payload. Such a model's title reads (file content), and most also have no list endpoint, so the full title is typically Attachment (push only,file content).

A file content model is written like this:

AttributeWhere it goes
file_contentThe request body. Multi-valued — each value becomes one line, joined with a newline and sent UTF-8 encoded
every other mapped attributeA query parameter of that endpoint

Query parameters are mappable attributes here and only here. A parameter normally describes what an endpoint accepts rather than what a record contains, but in this mode the body is the payload, so the query string is the only place a file name or a parent record number can travel. Two are withheld: the parameter carrying the record id, which the connector sets itself, and anything already sent as a fixed parameter. A required parameter left unmapped fails the write naming it, before anything is sent — and so does a missing file_content, since a mapping that produced nothing was not asking to store an empty file.

Binary files are not supported. The payload is built from platform text attributes, so it is text. A specification listing application/pdf does not make a PDF push possible; text media types (text/plain, application/xml, and application/octet-stream carrying text) are what this mode can actually send.

Because the platform treats every push after a record's first as an update, a create that sends a raw body also serves the update role automatically, with no configuration — another push means another file, which is what such an endpoint is for. A record pushed on five runs leaves five files, and the identity the platform holds stays the one the first create returned. Set operations to ["create"] to have a second push fail instead. A JSON create is never reused this way, since repeating it would create a duplicate record; naming both create and update in operations opts that case in explicitly.

Child-sourced records. Where a model's own list endpoint cannot be called — a required query parameter with no value to supply — its records are read from a parent record's embedded array instead. Such a model is read-only by default: its records are keyed by whatever the parent's array carries, which need not be the identifier its own write endpoint expects, so an inferred write could address the wrong record. Naming an endpoint with createEndpoint / updateEndpoint / deleteEndpoint, paired with uidField so the two agree on identity, re-enables writes.

Attribute discovery​

The first time a model's schema is needed, the connector issues a small probe — GET <list endpoint>?start=0&count=10 — and uses the response to infer per-attribute types across the sampled records:

Sampled JSON values for a keyInferred attribute type
All booleansBoolean
All integersLong
Numeric mix of integers and floatsDouble
Strings matching yyyy-MM-dd HH:mm:ss or ISO-8601Instant
Other stringsString
Nested objectsexpanded — one attribute per nested value
Arraysmulti-valued, element type inferred recursively
A key seen as both an array and a scalarmulti-valued — one observed array settles it
Mixed kinds across recordsString, as a safe fallback
Key present but every value nullString, and the key is still included

A model's attributes are the union of the record schema in the specification, the probe, and the create and update request bodies. Query parameters are not attributes — a parameter says what an endpoint accepts, not what its records carry, and nothing could consume such an attribute. A parameter that happens to name a real column still appears, discovered from the records themselves and typed from them.

Where the probe returns no records, or could not be reached, the schema falls back to UID, NAME and LAST_CAPTURED plus whatever the request bodies declare. A probe that succeeds and returns zero records is cached — an empty collection is an answer. A probe that failed is not, so the model recovers on its own once the endpoint does.

Nested objects. A record field that is an object becomes one attribute per value inside it, named with a dot. Given

{ "number": "PRB0042",
"vendor1": { "name": "Vendor 1", "ticket": "12378" } }

the model carries vendor1.name and vendor1.ticket; the container itself is not an attribute. Nesting is followed to ten levels, and each leaf is typed like any other field. These are the same names a request body uses, so a field that is both read and written is a single attribute carrying both flags.

Only arrays are not expanded, including arrays of objects: an array is one multi-valued attribute and its elements are never expanded. Every object does expand, a {"display_value": "Jane Doe", "value": "user-sysid-9001"} reference pair included — that yields owner.display_value and owner.value, keeping the sys_id rather than discarding it.

So describe what the API actually returns. The specification is taken at its word, and where it disagrees with its API the attributes it declared are the ones left empty. A field declared an object that the API answers with a single value leaves its declared leaves empty; a field declared a single value that the API answers with an object leaves that attribute empty while the object's leaves arrive under their own dotted names. Either way the data is not lost, but it is not under the name the specification promised.

Cardinality. Whether an attribute holds one value or several is fixed when the model is built, and records are held to it. The specification decides where it declares the field — an array property is multi-valued, anything else is single-valued — and the probe decides only for fields the specification leaves undocumented. At sync time, a field arriving as an array under a single-valued attribute is unwrapped if it carries one value, and fails the sync if it carries several, naming the model, the field, the record and the values. Several values are fatal because every alternative syncs a record contradicting the model it was declared against. The remedy is always the specification: declare the field as an array with an items type and resync.

Attribute naming​

A few attribute names are reserved by the Brinqa platform for its own use on every data model. A discovered source field whose name collides with one of them, case-insensitively, is emitted under a source_-prefixed name so the value is preserved rather than dropped:

Reserved source fieldPublished asWhy it is reserved
activesource_activeThe platform's own record flag
typesource_typePlatform system field name — an attribute colliding with it breaks model registration
modelsource_modelAs above
versionsource_versionAs above

All four are ordinary ServiceNow column names, so a specification is more likely to hit one than not. The renamed attribute is the name to use everywhere on the platform — field mappings, queries and reports all target source_type, never type. The rename is reversed when a write is built, so the API still receives the field it declared. If the rename lands on a name another source field already holds, the model decides which one wins, in the order the specification declares them, and the losing field is absent from the model and from records alike.

A field only a request body accepts still has to be declared, because the platform can only send attributes that belong to the model. Left unmarked it would sit indistinguishably among the readable ones, so it is emitted under a push_ prefix — a state field the API accepts but never returns becomes the push_state attribute, titled Push - State. The prefix answers one question: whether a value comes back. It is stripped again when the request is built, and it is applied before the reserved-name check, so a write-only active becomes push_active rather than push_source_active.

The names do change if a later schema build discovers the read side — push_state becomes state once records are seen carrying it. That is worth knowing before building field mappings against a model whose list endpoint was unreachable at setup: the mapping targets are provisional until the model has been built from a successful probe. A push-only model skips the prefix entirely, since every attribute there is write-only and marking them all says nothing.

Record identity​

Each record's UID is taken from the first present of a prioritized list: the ServiceNow surrogate keys sys_id, number, id, then generic surrogate ids uuid, guid, key, then common natural-identity fields user_name, username, login, email, sys_name. A flat user list keyed by user_name therefore syncs with no configuration. An x-brinqa-uid hint in the specification, or a uidField override, always takes precedence. A record carrying none of these is skipped and logged.

A model that also creates or updates records needs one identity every operation agrees on, because the UID a create returns is stored as the record's external id and matched against synced records. If a create reported a sys_id while a sync keyed the same record by number, the platform would hold two records for one — silently, with nothing failing. So for a model with write endpoints the UID is the first identity field the list endpoint provides that the create and update endpoints can also handle: the list supplies the candidates and their priority, the create must be able to report the value back, and the update must be able to carry it. A candidate is skipped only on positive evidence that an endpoint cannot handle it. DELETE gets no vote — if it cannot carry the chosen identity, the delete is dropped rather than constraining the whole model.

This narrowing is not cosmetic. On a Problem API whose list returns both sys_id and number but whose update body requires number, candidate order alone would pick sys_id and every update would be rejected by ServiceNow. The resolved identity is logged per model as identity=<field> when the schema is built.

What a create can report and an update can carry is only ever recognized among the surrogate keys — sys_id, number, id, uuid, guid, key. A natural-identity field such as user_name is never counted as one a write endpoint handles, so a model whose list keys records naturally while its create or update declares a surrogate key resolves no identity and does not load, even where the create response plainly returns the natural field too. Setting uidField does not lift this; the resolution is to drop the write, which leaves the model reading exactly as before:

{"Users": {"createEndpoint": "none", "updateEndpoint": "none"}}

If no candidate survives, the model does not load — reporting an id that no sync will reproduce is worse than refusing. The error names what each side can handle:

SituationResolution
No candidate is both reportable by create and carryable by updateSet uidField to a field all of them handle, or drop a write
The create endpoint declares no response schema, so nothing can be verifiedSet uidField to confirm what the response returns, or drop a write
An explicit uidField or x-brinqa-uid names a field the create response cannot reportName a field the create response returns
The list keys records by a natural-identity field while a write endpoint offers a surrogate keyDrop the write, leaving a read-only model

One case does not fail the integration: on a push-only model whose create response declares fields but none that can serve as an identity, and where nothing named one, the model is left out of the schema and Test Connection passes. The connector log names the model and the fields the response does declare; setting uidField to one of them brings it back. An identity that was asked for and cannot be honored is always reported rather than dropped — a typo'd uidField must not look like a model the connector chose not to support.

Record identity is derived, not fixed, and it can move without any deliberate change: when a create or update endpoint is added, removed or re-declared; when the list record schema gains or loses an identity field; when an x-brinqa-uid hint or uidField changes; when recordsPath or listEndpoint makes a different object "the record"; when a path rename merges or splits resources; or when the API simply starts returning a different field with no specification change at all.

Model overrides​

modelOverrides is a JSON object keyed by model name — the name discovery assigned the resource, as it appears in the discovery summary in the connector log. Every field is optional; each one, when set, replaces what the connector would otherwise infer from the specification.

{
"Problem": {
"recordsPath": "result.records",
"uidField": "number",
"createEndpoint": "POST /api/example/problem/create",
"sinceParam": "sys_updated_on_greaterthan"
},
"ProblemPtask": {
"enabled": false
}
}

For worked examples — what a specification looks like when each of these is needed, and the symptom it produces — see the model override reference in the setup guide.

Model selection

FieldTypeDefaultDescription
enabledBooleantrueSet false to skip the model entirely — it is not discovered and never registered

Endpoint selection. Each endpoint field takes an endpoint as "METHOD /path", exactly the token discovery prints in its mapping summary and in the error an unsupported operation throws, so it can be pasted back verbatim. The method may be omitted, in which case the role's default method applies — GET to list, POST to create or update, DELETE to delete. That is a default, not a wildcard: a path the resource serves under some other method has to name it. The value "none" leaves the role unmapped rather than inferred. A pin can only select among the endpoints of the same resource; naming anything else is an error listing the endpoints the resource actually has.

FieldTypeDefault methodDescription
listEndpointStringGETThe endpoint records are read from. Use it when a resource offers several plausible collection endpoints and inference picks the wrong one, or when a resource that only writes has a GET inference did not recognize as its list — without one the model is push-only
createEndpointStringPOSTThe endpoint records are created through. For a resource with more than one create-shaped POST, or a create verb the connector does not recognize
updateEndpointStringPOSTThe endpoint records are updated through
deleteEndpointStringDELETEThe endpoint records are deleted through
operationsString[]-Allow-list over create, update and delete, case-insensitive; an operation not named is dropped even where an endpoint was found. Naming update where only a create endpoint exists reuses it for updates too, for an upsert endpoint — with create alongside it to keep both, or alone to expose the endpoint purely as an upsert. A raw-body create is reused for updates without this; ["create"] opts that back out

File content writes. All three are only consulted for a model whose writes send a raw body. Setting contentType or contentField on a model whose writes are JSON is an error, not a no-op.

FieldTypeDefaultDescription
binaryModeBooleaninferred from the request bodytrue forces a write to be sent as a raw body where the specification documents a JSON one; false suppresses the mode, returning the endpoint to being rejected for declaring no mappable properties
contentTypeStringthe first media type the request body declares — parameters stripped, a wildcard such as */* ignored — else application/octet-streamThe Content-Type header. One static value per model — where the API also takes the content type as a query parameter, map that parameter to the same value. Parameters are allowed, as in text/plain; charset=utf-8, except a charset naming anything but UTF-8: the payload is built from text attributes and sent UTF-8 encoded, so another charset would misdescribe the bytes. A value that cannot be sent is reported when the integration is set up, not at push time
contentFieldStringfile_contentThe attribute the payload is taken from. Its values are joined one per line and sent UTF-8 encoded

Record addressing on update and delete. An update or delete needs the record's id to reach the request. The id is always the model's single resolved identity — it is never re-derived per endpoint — and what the connector resolves here is only how that one value travels, in this order: uidBodyField, then uidQueryParam, then a {...} path template, then a request-body field named for the identity, then a query parameter of that name. Configuration deliberately precedes the spec-derived routes, since it exists for a specification that documents no id route at all. An endpoint with no id route is refused rather than sent unaddressed: an update is reported unsupported and a delete is dropped from the model. Only one path template is supported — the id is substituted into the first {...} and any others are left alone, which is logged as a warning.

FieldTypeDefaultDescription
uidBodyFieldString-Request-body field to stamp the record UID into, for an endpoint whose path carries no id template. An error on a DELETE, which carries no body
uidQueryParamString-Query parameter carrying the record UID, for an endpoint that addresses records through the query string. Needed when an endpoint declares several plausible id parameters and inference cannot tell them apart

Records and identity

FieldTypeDefaultDescription
recordsPathStringinferred from the response schemaDotted path to the record array in the list response, such as result.records. Setting it also opts a GET in as a list endpoint that inference would not have recognized
uidFieldStringfirst present of the prioritized identity fields — sys_id, number, id, …Record field to use as the connector UID. Takes precedence over an x-brinqa-uid hint, which is read from the list response's record schema — or, on a push-only model, the create response's. It must name a field the list response provides and the create response returns and the update endpoint can carry
nameFieldStringfirst present of number, name, short_descriptionRecord field to use as the connector NAME. Takes precedence over an x-brinqa-name hint
updatedFieldStringsys_updated_on, sys_updated, updated_on, updated_atRecord field holding the last-updated timestamp, compared against the sync token for client-side incremental sync
recordTimeZoneStringthe connector's recordTimeZone, itself UTCTime zone this model's zone-less record timestamps are expressed in, for an endpoint that renders them differently from the rest. Only for that case — the zone is a property of the instance and the integration user, so setting it per model and forgetting one leaves that model skewed with nothing to show for it. Applies to reads and writes alike. Distinct from sinceTimeZone, which formats the outbound sinceParam value rather than reading a returned one

Pagination and filtering

FieldTypeDefaultDescription
pageParamStringauto-detectedQuery parameter carrying a page number, for an API that paginates by page rather than record offset. When set, the connector sends <pageParam>=1,2,3… instead of an offset
pageStartIndexInteger1First page number for page-based pagination
sinceParamString-Query parameter for server-side incremental filtering by timestamp, such as sys_updated_on_greaterthan. When set, the connector sends the sync token formatted yyyy-MM-dd HH:mm:ss so the API returns only changed records, instead of filtering client-side
sinceTimeZoneStringrecordTimeZoneTime zone the sinceParam value is formatted in. Falls back to recordTimeZone rather than to a zone of its own, so the server filters on the same clock the connector reads returned timestamps with — when the two disagreed, the records caught between them were dropped. Ignored when sinceParam is unset. A value that is not a zone id is a configuration error, since a mistyped zone would silently shift the incremental window by hours
requiredParamsMap<String,String>-Values to supply for otherwise-unsatisfiable required query parameters, such as {"parent": "PRB0001000"}. These satisfy the required-parameter check and are sent on every request for the model

Every field above is validated when models are discovered, and a setting the connector cannot apply fails rather than being ignored — an unrecognized key, an endpoint naming something the resource does not have, an unknown value in operations, uidBodyField on a DELETE, an invalid time zone, a contentType that cannot be sent as a header, or a uidField the write endpoints cannot agree on. An entry in modelOverrides exists to correct an inference, so quietly falling back to that inference is the one outcome it was written to prevent.

Field transformations​

fieldTransformations is a per-sync operation option, applied to each record after standard attribute mapping. Pass a JSON string serializing a map keyed by the target attribute name; iteration order is preserved.

@typeFieldsBehavior
upperCase–Upper-cases the source string
lowerCase–Lower-cases the source string
regexpattern, replaceReplaces matches of pattern with replace, or with an empty string if omitted
concatdelimiter, fieldsJoins the named source fields with delimiter; null source fields are skipped
chainfield, transformationReads field from the source record and applies the nested transformation to that value
formatpattern, fieldsApplies a Java MessageFormat pattern over the named source fields

upperCase, lowerCase and regex read the source value at the target attribute name — use them to overwrite a field in place. concat, chain and format read the original record and never see another transformation's output, so reordering the JSON does not change their input.

{
"category": { "@type": "upperCase" },
"label": {
"@type": "concat",
"delimiter": "-",
"fields": ["country_code", "number"]
},
"short_description_normalized": {
"@type": "chain",
"field": "short_description",
"transformation": {
"@type": "regex",
"pattern": "\\s+",
"replace": "_"
}
}
}

For a record carrying country_code US, number PRB0001000, category Database and short_description DB pool exhausted, this upper-cases category in place, adds label as US-PRB0001000, and adds short_description_normalized as DB_pool_exhausted.

A transformation that fails — most often a format or regex applied to a field of a different type than the pattern expects — fails the sync, naming the model, the target attribute and the reason. A transformation is configuration, so a failure is corrected and resynced rather than skipped: skipping would leave the target either absent or still holding the value the transformation was configured to replace, and neither is distinguishable from a sync that worked. That is distinct from a transformation with nothing to do — one whose source field the record does not carry, or whose input is not of a type it applies to — which produces no value, leaves its target as it was, and does not stop the sync. A malformed pattern is reported earlier still, when the option is parsed, before any record is read.

Sync Behavior​

The connector supports incremental (delta) sync. The first run has no sync token, so every record is read; each subsequent run applies a since filter derived from the previous run, so only records changed in the interval are processed.

By default, the filter is applied client-side, by comparing each record's last-updated field against the sync token. Which field that is resolved per model: modelOverrides.<Model>.updatedField where it is set, otherwise the first of sys_updated_on, sys_updated, updated_on or updated_at the records carry. Where a model declares a server-side filter parameter with modelOverrides.<Model>.sinceParam, the connector sends the token to the API instead, and ServiceNow returns only changed records.

That comparison is between a timestamp the API returned and a token in UTC, so the two have to be on the same clock. ServiceNow's datetime format carries no offset, and the API renders it in the ServiceNow time zone of the integration user — which is UTC only if that user is set to UTC. Where it is not, set recordTimeZone to the zone the instance renders in. Otherwise every record timestamp is read as UTC, lands hours early, and records changed since the last sync test as older than the token and are skipped. Each sync logs how many records the token held back alongside how many it emitted, so a count far above expectations points at this setting. A timestamp that carries an offset of its own, or a trailing Z, is unaffected.

Data retrieved from ServiceNow API​

Connector ObjectRequiredMaps to Data Model
Record modelYesRecord model
Push-only modelYesPush-only model
File content modelYesFile content model

Model relationships​

note

For detailed steps on how to view the data retrieved from ServiceNow API in the Brinqa Platform, see How to view your data.

warning

After changing the OpenAPI specification, the upstream ServiceNow API, or a model's uidField, purge the integration's data and run a beginning-of-time sync.

The connector cannot detect that identity moved between runs — there is no connector-side state to compare against — so this is guidance rather than something it enforces. Records synced under the old identity are never matched again: they linger while the same records reappear as new, and an incremental sync only adds new-identity records alongside the stale ones. A model that has never synced needs nothing, and a read-only model is unaffected by changes to its write endpoints.