Skip to content
← All scripts
RESTglobal scopeReview before use · not yet release-tested

Scripted REST resource: idempotent upsert by correlation ID

An inbound API that creates or updates a record using the caller’s ID, so retries and duplicate calls never create duplicate records.

October 1, 2026 · #scripted-rest #integrations #idempotency

Integrations retry. If your endpoint inserts on every call, each retry is a duplicate. Store the caller’s ID in correlation_id and upsert on it.

// Scripted REST API resource · HTTP method: POST
(function process(request, response) {
  var body = request.body.data;
  if (!body || !body.external_id) {
    response.setStatus(400);
    response.setBody({ error: 'external_id is required' });
    return;
  }

  var gr = new GlideRecord('incident');
  gr.addQuery('correlation_id', body.external_id);
  gr.setLimit(1);
  gr.query();

  var isNew = !gr.next();
  if (isNew) {
    gr.initialize();
    gr.setValue('correlation_id', body.external_id);
  }
  gr.setValue('short_description', body.summary || '');
  gr.setValue('description', body.description || '');
  var sysId = isNew ? gr.insert() : gr.update();

  response.setStatus(isNew ? 201 : 200);
  response.setBody({ sys_id: sysId, number: gr.getValue('number'), created: isNew });
})(request, response);

How it works

  • correlation_id is an out-of-box field on task tables, so no schema change is needed.
  • insert() and update() both return the sys_id.
  • The caller can tell create (201) from update (200).

Gotchas

  • Add a database index on correlation_id for large tables.
  • Two calls at exactly the same moment can still race. Serialise on the caller side if that matters.
  • Use a dedicated integration user with only the roles it needs.

MIT licence. Provided as-is, without warranty. Test in a sub-production instance first.

Related scripts