# Update a private integrator Modify a tenant-private integrator's metadata, scopes, capabilities, or rate…

`PUT /api/configuration/operator/integrators/private/{id}`

- **Success:** `200 OK`
- **Products:** Operator (`FOH`)
- **Features:** Front of House Generic Access (`FOHGENERIC`)

HTML version: [Update a private integrator Modify a tenant-private integrator's metadata, scopes, capabilities, or rate…](https://support.gladstonesoftware.cloud/reference/configuration/update-a-private-integrator-modify-a-tenant-private-integrators-metadata-scopes)

## Overview

Rewrites the configuration of a private integrator belonging to your own tenant: its display metadata, the web origin its key may be used from, the scopes and capability flags stamped onto its tokens, its rate limits, and whether it is active. A private integrator is one your tenant created for itself with [Create a private integrator](create-a-private-integrator-for-the-current-tenant-provision-a-tenant-private-in.md), as opposed to a Gladstone-curated global one.

**This is a full replacement, not a patch.** Every field in the body is written unconditionally, so a field you omit is written as its JSON default — `null` for the strings, **`false` for every boolean**, `0` for both rate limits. Sending `{"name": "Acme", "origin": "https://acme.example"}` clears `description`, `logoUrl` and `allowedScopes`, sets `canRead`, `canWrite`, `canDelete` and `isBillable` to false, removes both rate limits, and sets `isActive` to false — which stops your integration authenticating altogether. **Read the integrator first, change what you mean to change, and send the whole object back.**

The tenant that owns the integrator is taken from your token, not from the body — there is no `environmentId` field to send, and you cannot move an integrator to another tenant. An id that belongs to another tenant, or to a global integrator, is rejected with `404`, so this endpoint cannot be used to reach outside your own tenant.

Changes reach live traffic at different speeds:

- **Scopes and capability flags** are baked into the JWT when a token is minted, so a token already in your integration's hands keeps its old claims until it expires. The next token carries the new values.
- **`isActive` false** stops new tokens being issued, but can take up to five minutes to take effect. Access tokens already issued keep working until they expire.
- **Rate limits** are read on every token request, so a change applies to the next call.

The API key is untouched: nothing is revoked, nothing is reissued, and no key value is returned. To replace the key itself, use [Renew a private integrator's API key](renew-a-private-integrators-api-key-atomically-revoke-the-current-key-and-issue.md).

## Endpoint

`PUT {base_url}/api/configuration/operator/integrators/private/{id}`

`{base_url}` is the environment URL issued to your integration. See [Authenticating with Gladstone APIs](../../guides/authenticating-with-gladstone-apis.md).

## Path parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `id` | integer (int32) | Yes | The identifier of the integrator to update, from the `id` field of [List the current tenant's private integrators](list-the-current-tenants-private-integrators-get-every-tenant-private-integrator.md). It must be a non-global integrator whose owning environment matches the one in your token; anything else — an unknown id, a global integrator, another tenant's private integrator — returns `404`. |

## Request body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | Yes | The integrator's display name, shown to your operators when they pick an integration. Must be present and non-empty — omitting it, or sending `""` or `null`, fails validation with `400`. Not required to be unique. Maximum 255 characters, but a longer value fails with `500` rather than `400`. |
| `description` | string | No | Free-text explanation of what this integration does, for your own operators. Unbounded length. Omitting it or sending `null` clears the existing description — send the current value back to keep it. |
| `origin` | string | Yes | The single web origin (scheme and host, for example `https://booking.acme.example`) that browser-based use of this integrator's key is tied to. When a token request carries an `Origin` header it must equal this value — compared case-insensitively, ignoring one trailing slash — or the request is rejected with `401`. A token request with **no** `Origin` header skips the check, so this protects browser callers only, not server-to-server ones. One origin only; no list, no wildcard. Must be present and non-empty (`400` otherwise). Maximum 255 characters, but a longer value fails with `500` rather than `400`. |
| `logoUrl` | string | No | Absolute URL of a logo for the integrator. Maximum 500 characters; a longer value fails with `500` rather than `400`. The platform never fetches or validates it. Omitting it or sending `null` clears the existing value. |
| `isBillable` | boolean | No | Whether usage under this integrator is chargeable. Copied onto every new token as a `billable` claim, which is how usage is attributed. Private integrators are created with this set to `true` by default, so **omitting it here flips a billable integration to non-billable** — it does not preserve the existing value. |
| `allowedScopes` | string | No | The features this integrator may reach, as a **JSON array serialised into a string** — for example `"[\"FOH\",\"ONLINEBOOK\"]"`, not a JSON array in the body. Each entry is emitted twice on every new token, once as a features claim and once as a products claim, because gateway routes match on both. Nothing is validated at write time: if the string is not parseable as a JSON array of strings, new tokens carry no scope claims at all and every route rejects them with `403`. Omitting the field or sending `null` does the same — the integrator can still authenticate but can call nothing. Unbounded length. Entries are not checked against a list of known features, so a typo is accepted silently and simply never matches a route requirement. |
| `canRead` | boolean | No | Whether this integrator may issue `GET`, `HEAD` and `OPTIONS` requests. Emitted as a `can_read` claim on new tokens and enforced by every endpoint: a partner token without `can_read` true gets `403` with the plain-text body `Partner token lacks can_read capability.` **Omitting this field sets it to false**, which locks the integration out of every read endpoint from its next token onwards. |
| `canWrite` | boolean | No | Whether this integrator may issue `POST`, `PUT` and `PATCH` requests. Emitted as a `can_write` claim; a partner token without it true gets `403` with the body `Partner token lacks can_write capability.` **Omitting this field sets it to false.** |
| `canDelete` | boolean | No | Whether this integrator may issue `DELETE` requests. Emitted as a `can_delete` claim; a partner token without it true gets `403` with the body `Partner token lacks can_delete capability.` Only the `DELETE` method maps to this flag — a soft delete exposed as a `POST` or `PUT` is governed by `canWrite` instead. **Omitting this field sets it to false.** |
| `rateLimitPerMinute` | integer (int32) | No | Maximum token requests this integrator's API key may make in a one-minute window. Exceeding it returns `429` with a `Retry-After` header naming the seconds to wait. **`0` means unlimited** — and because omitting the field defaults it to `0`, leaving it out removes the limit rather than preserving it. Negative values are rejected with `400`. The window is a fixed calendar minute rather than a sliding one, so a burst straddling the boundary can consume two windows' worth. The limit applies to token requests only, not to the API calls you make with the token. |
| `rateLimitPerHour` | integer (int32) | No | Maximum token requests this integrator's API key may make in a one-hour window, checked only after the per-minute limit has passed. **`0` means unlimited**, which is also what omitting the field gives you. Negative values are rejected with `400`. Set above `rateLimitPerMinute` × 60 it can never bite; set below, it is the limit that actually applies. |
| `isActive` | boolean | No | Whether the integrator may be used at all. Key validation requires an active integrator, so setting this false stops the key minting tokens within five minutes, while access tokens already issued live out their term. Setting it back to true restores the key, because this endpoint never revokes anything. **Omitting this field sets it to false and disables your integration** — the most damaging mistake available on this endpoint. Note this is not the same as [Delete a private integrator](delete-a-private-integrator-revoke-the-integrators-active-api-key-and-mark-the-i.md), which also revokes the key and cannot be undone. |

## Sample request

```bash title="cURL"
curl -X PUT '{base_url}/api/configuration/operator/integrators/private/{id}' \
  -H 'Authorization: Bearer {accessToken}' \
  -H 'X-Use-SSO: true' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "string",
  "description": "string",
  "origin": "string",
  "logoUrl": "string",
  "isBillable": true,
  "allowedScopes": "string",
  "canRead": true,
  "canWrite": true,
  "canDelete": true,
  "rateLimitPerMinute": 1,
  "rateLimitPerHour": 1,
  "isActive": true
}'
```

## Response fields

| Field | Type | Description |
|-------|------|-------------|
| `id` | integer (int32) | The integrator's identifier — the same value you passed in the path. Never reassigned by an update. |
| `name` | string | The name as persisted, echoing what you sent. |
| `description` | string | The description as persisted — `null` if you omitted the field, which clears it. |
| `origin` | string | The allowed origin as persisted, echoing what you sent. |
| `logoUrl` | string | The logo URL as persisted — `null` if you omitted the field. |
| `isGlobal` | boolean | Always `false` here: the update is rejected with `404` unless the target is a private integrator, and no endpoint can change this flag. |
| `isBillable` | boolean | The billable flag as persisted — `false` if you omitted the field, even though private integrators are created billable. |
| `environmentId` | integer (int32) | The identifier of the tenant environment that owns this integrator, resolved from your token rather than from the body. It is never null for a private integrator, and it is what allows keys from this integrator to mint tokens carrying tenant claims — which is why private integrators, unlike global ones, can be used for member login and member-token refresh. |
| `allowedScopes` | string | The scope string as persisted, byte for byte as you sent it — `null` if you omitted the field. It is not parsed, so this confirms what was written, not that it is usable. |
| `canRead` | boolean | The read capability as persisted — `false` if you omitted the field, which blocks every `GET` from the next token onwards. |
| `canWrite` | boolean | The write capability as persisted — `false` if you omitted the field. |
| `canDelete` | boolean | The delete capability as persisted — `false` if you omitted the field. |
| `rateLimitPerMinute` | integer (int32) | The per-minute budget as persisted — `0`, meaning unlimited, if you omitted the field. |
| `rateLimitPerHour` | integer (int32) | The per-hour budget as persisted — `0`, meaning unlimited, if you omitted the field. |
| `isActive` | boolean | The active flag as persisted. Check it on every response: `false` means the integrator can no longer obtain tokens, and it is `false` whenever the field was omitted from the body. |
| `createdOn` | string (date-time) | UTC timestamp the integrator was created. Unchanged by this call. |
| `modifiedOn` | string (date-time) | UTC timestamp of this update, set server-side. Always populated in a successful response from this endpoint. |

## Sample response

```json title="200 OK"
{
  "id": 12345,
  "name": "string",
  "description": "string",
  "origin": "string",
  "logoUrl": "string",
  "isGlobal": true,
  "isBillable": true,
  "environmentId": 12345,
  "allowedScopes": "string",
  "canRead": true,
  "canWrite": true,
  "canDelete": true,
  "rateLimitPerMinute": 1,
  "rateLimitPerHour": 1,
  "isActive": true,
  "createdOn": "2026-08-14T19:00:00Z",
  "modifiedOn": "2026-08-14T19:00:00Z"
}
```

## Errors

| Status | Meaning | When |
|--------|---------|------|
| `400` | Bad request | Either validation failed — `name` or `origin` missing, empty or null, or a negative rate limit, returned as a validation problem document (`application/problem+json`) with an `errors` map keyed by field name — or the tenant environment could not be resolved from your token, in which case the body is the plain string `Could not resolve tenant environment.` Nothing is written in either case. |
| `404` | Not found | No integrator exists with that id, or it is a global integrator, or it belongs to a different tenant. All three are indistinguishable: the response has no body. Contrary to the endpoint summary, a global or cross-tenant target is a `404`, not a `400`. |
| `500` | Internal server error | An unexpected server-side failure, including a `name` or `origin` over 255 characters or a `logoUrl` over 500 — those limits are not validated up front, so they surface as a `500` rather than a `400`. The body is empty and nothing is written. |
