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.

Item-sourced records. Some list endpoints cannot be enumerated at all: they answer per record of another resource, and calling one without that value returns nothing useful, or everything. An attachment list taking an incident number is the usual example. Where no parent embeds such records, itemSource binds the model to a source model instead — the sync enumerates the source, calls this model's own list endpoint once per value read from each source record, and stamps every emitted record with the value it was read under:

{"Attachment": {"itemSource": {"model": "Incidents", "sourceField": "number"}}}

This is declared, never inferred. A list endpoint accepting a parameter that happens to name another model's field is not evidence that anyone wants one request per record of a table that may hold millions.

Unlike a child-sourced model, an item-sourced one keeps its write endpoints: it reads its own endpoint and keys records by its own fields, so an inferred write addresses the record it means to. The cost is request count — one call per source record — and the rules that follow from that are under Item-sourced models.

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 — createUidField below does, where the create is the side that cannot report the field. Otherwise the resolution is to drop the write, which leaves the model reading exactly as before:

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

Where a write response reports the identity under another name. One resource may list records under sys_id and answer a create with attachment_id — the same identifier, a different name. createUidField and updateUidField name the field each response reports the identity under, and only that: records are still keyed by uidField, so a create and a later sync of the same record agree.

{"Attachment": {"uidField": "sys_id", "createUidField": "attachment_id"}}

Both require uidField alongside them, and are refused without it. The setting asserts that two specific names carry one value, so both have to be named: left to attach to whatever identity inference happened to land on, a wrong pairing leaves the platform holding a second record for every one created — silently, with nothing failing. Nothing in the specification can verify the assertion. What is checked is the name itself: where the create or update response is described, an alias it does not declare is refused when the schema is built, so Test Connection reports it and no push reaches a misspelled name. A response the specification does not describe at all is silence rather than a refusal and is allowed through, that being the case the setting most often exists to rescue.

createUidField settles the create side of the agreement, so it also lifts a veto cast there — including the surrogate-key rule above. A create that returns user_name while declaring a surrogate key is vetoed only because a natural-identity field is never counted among the names a write endpoint handles; {"uidField": "user_name", "createUidField": "user_name"} says the response does carry it, and the model loads with its writes intact. Note what that example asserts: the create reports the same value under a name the connector would not have looked for. It is not a licence to pair two different identifiers — createUidField: "sys_id" on a model keyed by user_name is exactly the silent duplication this setting is fenced against.

The update side is unaffected: updateUidField governs how the update's response is read, not whether the endpoint can carry the identity to the record, which is still a path template, uidBodyField or uidQueryParam.

How far each response is searched. The declared response shape is tried first, then the conventional result envelope. A create that neither reaches falls back to searching the whole response for the identity field by name, shallowest match first — the record exists by then, and no safe value is left to return. An update does that only where updateUidField is set. Without the alias it keeps the Uid it was called with, which identifies the record whatever the response says: a response reporting no identity of its own but nesting a reference to another record — {"status": "updated", "caller": {"sys_id": "…"}} — would otherwise re-key the record to the caller's id, logged but not refused. Naming the field is the assertion that makes the search worth running.

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 — or, where it returns the same value under another name, set createUidField to that name
The list keys records by a natural-identity field while a write endpoint offers a surrogate keyWhere the create response does return that same field, name it with createUidField; otherwise drop 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 createUidField is adopted, which settles the create side and so lets an earlier candidate win; 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
createUidFieldStringthe model's resolved identity fieldField the create response reports the record identity under, where that endpoint names it something other than uidField — as in a model listed by sys_id whose create answers with attachment_id. Records stay keyed by uidField; only the response is read by this name. Requires uidField, since the setting asserts that two specific names carry one value
updateUidFieldStringthe model's resolved identity fieldThe same for the update response. Separate because the two responses are separate declarations — an endpoint that creates under attachment_id may well update under sys_id. Also requires uidField. It governs how the response is read, not how the id reaches the endpoint; that is uidBodyField / uidQueryParam
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 outbound incremental window is formatted in — the sinceParam value, and the gs.dateGenerate arguments of an encoded query. 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 where neither filter is in play. 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. For a parameter needing a different value per call, see itemSource

The parameter carrying an encoded query is not configured here: it is detected from the specification, exactly as start, count and page are. Filtering by one is a per-sync concern and is expressed through the encodedQuery and encodedQueryParam operation options — see Encoded queries.

Item sourcing. itemSource binds this model's list endpoint to another model's records, so it is called once per source record with that record's value in a query parameter. It is a JSON object with its own keys, and the structural binding is all that lives here — which source records are read is a per-sync concern, expressed through the source.-prefixed operation options.

{
"Attachment": {
"uidField": "sys_id",
"createUidField": "attachment_id",
"itemSource": {"model": "Incidents", "sourceField": "number"}
}
}
FieldTypeRequiredDefaultDescription
modelStringYes-The connector's name for the source model, as the discovery summary prints it — a model name, never a path. It must be discovered, enabled, backed by a list endpoint, and neither child-sourced nor item-sourced itself: a source has to be enumerable on its own
sourceFieldStringYes-Field on each source record the driver value is read from, such as number. A source record carrying no value there is skipped and counted rather than called with an empty parameter, which most endpoints read as "unscoped" and answer with the whole collection
paramStringNosourceFieldQuery parameter on this model's list endpoint the value is sent as. Held apart from the field because the names need not agree — a source may expose a value as sys_id where the dependent endpoint calls it incident_sys_id. Must be one the endpoint declares
linkFieldStringNoparamAttribute each emitted record carries the driver value under, linking it back to the source record. Optional in configuration only: such a response routinely carries the link on its envelope rather than on the records, which is not what the records are read out of

A binding the connector cannot honor leaves that model out of the schema rather than failing the integration, and Test Connection reports it: a source that was not discovered, one disabled by enabled: false, one that is itself child- or item-sourced, or a param the list endpoint does not declare. A block that cannot name what it binds — a missing model or sourceField, or a model naming this model itself — is a configuration error wherever it is read.

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.

Encoded queries​

A ServiceNow encoded query is the ^-separated filter syntax carried by sysparm_query and its scripted-API equivalents — active=true^priority<=2. Where the list endpoint declares a parameter for one, the connector sends a query through it, so the API returns only the records the sync wants rather than the whole collection.

The parameter is detected from the specification, exactly as start, count and page are:

SituationResult
The list operation declares sysparm_query, encoded_query or sysparm_encoded_queryActive, through that parameter
It declares none of themInactive — the incremental filter stays client-side
The encodedQueryParam option names a parameterThat name, whatever was detected
The encodedQueryParam option is noneOff for that sync

Both knobs are operation options, set per sync on the integration like fieldTransformations, not in modelOverrides — a filter scopes a sync rather than correcting an inference.

OptionTypeDescription
encodedQueryStringThe query to filter by, such as active=true^priority<=2. Optional — without one the parameter carries the sync window alone
encodedQueryParamStringOverrides the detected parameter name; none disables the encoded query for that sync

What is sent. On an incremental sync the connector ANDs its own window onto whatever you supplied, so a delta run asks for exactly the records it would otherwise have read and discarded:

ActiveencodedQuerySync tokenSent
Noanythinganynothing
Yesunsetnonenothing
Yesunsetpresentsys_updated_on>=javascript:gs.dateGenerate('2026-04-01','12:00:00')
Yesactive=truenoneactive=true
Yesactive=truepresentactive=true^sys_updated_on>=javascript:gs.dateGenerate(…)
Yes…^sys_updated_on>=…presentyour value verbatim — a window you wrote on that field is not second-guessed

The window constrains one column: modelOverrides.<Model>.updatedField where it is set, and sys_updated_on otherwise. gs.dateGenerate reads its arguments in the ServiceNow time zone of the integration user, which is the zone sinceTimeZone already formats in, so one setting still moves both halves of the incremental filter.

Against sinceParam. The two are alternative ways to filter server-side, and whichever was written deliberately keeps the sync. sinceParam wins over a parameter the connector merely detected — an inference does not displace a setting someone wrote — and encodedQueryParam wins over sinceParam. Both outcomes are logged, naming the filter that went unused and, in the first case, the option that switches it.

^NQ queries. ^NQ starts a new query rather than adding a condition, so active=true^NQpriority=1 is (active=true) OR (priority=1). The window is ANDed onto each branch — active=true^<window>^NQpriority=1^<window> — since each is evaluated on its own. A branch that wrote its own clause on the updated field keeps it while its neighbours are still windowed; only a query whose every branch does is left alone.

Limits. The schema probe sends no query: it infers attribute types from a sample, and a filter matching nothing would drain it. A child-sourced model sends none either, its request going to the parent model's endpoint. And the client-side comparison still runs behind the server filter, so an endpoint that documents the parameter without honoring it costs a slow sync rather than a wrong one.

Item-sourced models​

A model bound with itemSource reads its own list endpoint once per record of a source model, and carries the driver value on each record it emits. The binding is in Model overrides; what follows is how such a sync behaves.

The source is enumerated during this model's sync, over HTTP, every time — never cached, shared between syncs, or read from what an earlier sync left behind. So the model syncs identically whether the source ran an hour ago, is running concurrently, or has never run.

Filters for the enumeration go under a source. prefix on the same sync's operation options; the model's own unprefixed options stay with the per-item calls, since a filter written for attachments names fields incidents do not have. An unprefixed key is dropped from the enumeration rather than passed through.

OptionApplies to
encodedQueryThe per-item calls
encodedQueryParamThe per-item calls
source.encodedQueryThe source enumeration
source.encodedQueryParamThe source enumeration
source.applySyncTokenThe source enumeration — false by default, true windows it by the sync token

Anything else under source. fails the sync, naming the keys that are recognized: a misspelled filter would otherwise leave the source enumerated in full, which looks like a working sync returning far more than was asked for. So does any source. key at all on a model with no itemSource — the prefix names a half of the sync that is not running there, and the error says to drop the prefix or configure the binding. source.fieldTransformations parses but is never read — source records are inspected for a driver value and never emitted — so it warns instead.

The one filter the connector cannot see is one set as the source model's own encodedQuery option: nothing can read another model's operation options. The end-of-sync line prints the filter the fan-out actually applied, so (no filter) beside a model you believe you filtered is the signal to move it to source.encodedQuery.

Delta sync. The incremental window is applied to the records, not to the enumeration:

WhereWindowed?Why
The source enumerationNo, unless source.applySyncToken: trueAn attachment added to an incident does not change the incident. Windowing by default would not delay that attachment but lose it — the token advances regardless, so no later sync returns to it
The per-item callsYes, where the endpoint supports itAn encoded-query parameter or sinceParam on this model's own endpoint windows the dependent records themselves
The emitted recordsYes, unchangedThe ordinary client-side comparison. A record with no parseable timestamp is emitted, which is what makes an attachment list — carrying no timestamp of its own — work at all

The consequence to plan for is request count: a delta sync of an item-sourced model makes as many requests as a full one, and only emits fewer. source.applySyncToken: true trades that back, and is an assertion that a change to a dependent record does bump its source record — the same unverifiable kind as createUidField, and being wrong about it costs records that never arrive.

Two source records naming one value cost one call. A record returned under two driver values is emitted once, under the first — so a dependent record that belongs to several source records carries the first driver value it was seen under, not all of them. Each sync logs the values enumerated, the requests made and the filter applied; a sync that enumerates no values at all warns rather than reporting an ordinary empty sync, since zero records retires live data under a lifecycle policy.

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.

The filter is applied in one of three ways, per model. By default it is client-side, comparing each record's last-updated field against the sync token; which field that is resolved per model, as modelOverrides.<Model>.updatedField where it is set and otherwise the first of sys_updated_on, sys_updated, updated_on or updated_at the records carry. Where a model configures a server-side filter parameter with modelOverrides.<Model>.sinceParam, the connector sends the token to the API instead. And where the list endpoint declares a parameter carrying a ServiceNow encoded query, the connector sends the window through it with no configuration at all — see Encoded queries, which also covers which of the two server-side filters wins where both are available. Under either server-side filter the client-side comparison still runs behind it.

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.