The integration partner upgraded their consumer and started getting 500 errors at random. The endpoint had been “working fine” for a year — until traffic shape changed and the lack of input validation, version pinning, and rate handling all surfaced at once. Scripted REST APIs accumulate technical debt invisibly until a consumer change forces all of it into view. The patterns below prevent the surprise.
Why Scripted REST
The out-of-box Table API covers CRUD on tables and is sufficient for most simple integration needs. Scripted REST is for custom endpoints — business logic that crosses tables, multi-record operations, downstream integrations that need a specific contract. Build them when Table API is not shaped right for the consumer; do not build them when Table API would suffice, because every Scripted REST endpoint is a piece of code you maintain forever.
Versioning Up Front
Include a version in the URL path (/api/acme/v1/myendpoint). Once consumers integrate, breaking changes hurt everyone. Version from day one and treat the version as a stable contract: backward-compatible additions only inside a version, breaking changes require a new version with a deprecation timeline for the old one. Track which consumers use which version via the User Agent header so you know who to notify before retiring a version.
Version path:
/api/acme/v1/incidents (stable, deprecated 2026-12-31)
/api/acme/v2/incidents (current)
/api/acme/v3/incidents (preview, unstable)
Auth Options
OAuth 2.0 for third-party integrations — proper token lifecycle, revocable, auditable. Basic auth for internal-only when network controls already restrict access. Mutual TLS for high-security or regulated integrations. Whichever you pick, document it in the endpoint metadata and enforce consistently — mixing auth types within an API surface is operationally painful and audit-unfriendly.
Error Contract
Return consistent error shapes. 4xx for client errors with an error code, a human message, and a correlation_id the client can include in support tickets. 5xx for server errors with the correlation ID but without exposing internal details. Clients that cannot parse your errors will retry blindly and hammer the endpoint; a good error contract is a load protection feature.
// Helper for consistent error responses
function errorResponse(response, status, code, message) {
response.setStatus(status);
response.setContentType('application/json');
response.setBody({
error: {
code: code,
message: message,
correlation_id: gs.generateGUID(),
timestamp: new GlideDateTime().getDisplayValue()
}
});
}
Rate Limiting
Set explicit per-consumer rate limits — requests per minute, requests per day. Return 429 with Retry-After when exceeded; do not silently throttle, which produces consumer confusion and retry storms. The platform’s IntegrationHub spokes implement this for outbound; for inbound, build it as a Scripted REST resource decorator that checks a rate-limit table before delegating to the handler.
Testing
Each endpoint gets an ATF test that exercises happy path, validation failure, and auth failure. Call the endpoint, check status code, check body shape, check at least one field value. For integrations with external partners, also maintain a Postman or Bruno collection that the partner imports and uses for their own contract tests. Contract-first, integration-second — the contract is the API; the implementation is the detail.
Common Failure Modes
Endpoints that return GlideRecord objects directly via JSON.stringify — the serialization is fragile and exposes internal field structure. Map to a stable response shape explicitly. Endpoints that perform unbounded queries — a missing pagination parameter scans the entire table; default to a reasonable page size and cap the maximum. Endpoints that bypass ACL by running as a privileged user — easy to write, dangerous if the input is not validated; treat the endpoint as the trust boundary and validate every input.
What Changed in 2026
The platform now supports OpenAPI 3.1 spec generation for Scripted REST APIs on Zurich and later. Maintain the spec as a first-class artifact for consumer documentation and validation. Older releases require manual documentation, which drifts from implementation rapidly.
Implementation Sequence
Define the contract (request, response, error, auth) in OpenAPI before writing handler code. Build a stub that returns canned responses, hand it to the consumer team for early integration. Implement the handler against the stub’s contract. Write ATF tests against the contract, not against the implementation. The code-first approach is faster initially and produces APIs that drift from any documentation within months.
What to do this week: list every active Scripted REST endpoint in your instance and identify which have version paths and which do not — the unversioned ones are your debt.