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_idis an out-of-box field on task tables, so no schema change is needed.insert()andupdate()both return thesys_id.- The caller can tell create (201) from update (200).
Gotchas
- Add a database index on
correlation_idfor 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.