> ## Documentation Index > Fetch the complete documentation index at: https://docs.lobstersoftware.com/llms.txt > Use this file to discover all available pages before exploring further. # Patch partner > Changes a partner with a JSON merge patch: members not sent keep their stored value. ## OpenAPI ````json PATCH /platform/api/partners/preview/{id} { "openapi": "3.1.0", "info": { "title": "Lobster Platform API", "description": "**PREVIEW:** This API is in preview and under active development. Endpoints and schemas are subject to breaking changes, and backward compatibility is not guaranteed until the API is promoted to a stable version.\n\nREST API of the Lobster Platform for headless clients. Every operation, schema and member of this document carries a description, and the create operations carry complete examples. Each tag links the page of the Lobster documentation that explains its resources.\n\n## Authentication\nFour authentication flows are available. All of them produce a bearer token that must be sent as `Authorization: Bearer ` on every request.\n\n1. **Authorization code with PKCE** (acting as a user; works with Microsoft and other SSO): the steps are in the `OAuth2AuthorizationCode` security scheme.\n2. **OAuth2 Client Credentials** (machine-to-machine): exchange a client id/secret provisioned in the Lobster administration UI at `POST /dw/register/oauth/token` with `grant_type=client_credentials&client_id=&client_secret=`.\n3. **UI Session Bearer Token**: log in via the Lobster web UI, then exchange the session cookie for a token at `GET /auth/sessionBearerToken`.\n4. **Guest User Login Token**: append a GuestUser `loginToken` as a URL query parameter (`?loginToken=`) to establish a session without a password, then retrieve the bearer token the same way.", "version": "preview" }, "servers": [ { "url": "https://platform-beta-test.lobster-cloud.com" } ], "security": [ { "OAuth2": [] }, { "OAuth2AuthorizationCode": [] }, { "SessionBearer": [] } ], "tags": [ { "name": "Channels", "description": "The channels of a partner, the concrete connections a profile uses to receive or send.", "externalDocs": { "description": "Lobster documentation", "url": "https://docs.lobstersoftware.com/docs/en/manage-partners-channels" } }, { "name": "Functions", "description": "The catalogue of mapping functions a profile's mapping can call.", "externalDocs": { "description": "Lobster documentation", "url": "https://docs.lobstersoftware.com/docs/en/functions" } }, { "name": "Partners", "description": "Integration partners, the business parties profiles exchange data with.", "externalDocs": { "description": "Lobster documentation", "url": "https://docs.lobstersoftware.com/docs/en/manage-partners" } }, { "name": "Profiles", "description": "Integration profiles: what data comes in, how it is mapped and where the result goes.", "externalDocs": { "description": "Lobster documentation", "url": "https://docs.lobstersoftware.com/docs/en/profiles" } } ], "externalDocs": { "description": "Lobster documentation", "url": "https://docs.lobstersoftware.com/docs/en/profiles" }, "paths": { "/platform/api/partners/preview/{id}": { "patch": { "tags": [ "Partners" ], "summary": "Patch partner", "description": "Changes a partner with a JSON merge patch: members not sent keep their stored value.", "operationId": "patchPartner", "parameters": [ { "name": "id", "in": "path", "description": "The partner's `id`, as returned by list and create.", "required": true, "schema": { "type": "integer", "format": "int64" } } ], "requestBody": { "description": "Partial update following JSON Merge Patch (RFC 7396). Send only the fields to change; omitted fields are left unchanged and an explicit null clears a field. What a clear reaches depends on the resource. Where the value is carried onto an underlying configuration, the field is reset to what a freshly built configuration holds for it rather than to absent, since that configuration has no representation for unset; where the absence cannot be stored at all, the null is accepted and the stored value is kept. A field the resource fills by resolving a reference keeps what is stored whenever the reference cannot be read back, a null being indistinguishable there from a lookup the session was not allowed to make. Requires Content-Type: application/merge-patch+json (sending application/json is rejected with HTTP 415). Control keys steer the merge and are spelled as a namespace prefix, a $ and the marker — the JSON form of the import vocabulary's core:mode. core$mode=overwrite, on the body or on any object inside it, resets the members that object did not send; a read-only member is not reset, since a marker cannot clear what a value could not set. An overwrite therefore has to carry every member the resource requires — its name among them — because omitting one asks for a record the store cannot accept. A reset is bound by the same limits an explicit null has, and those differ by resource: where the value is carried onto an underlying configuration the member goes back to what a freshly built one holds, and where the absence cannot be stored at all the member is kept rather than reset — so an overwrite is not a way to clear what a null could not. Nor does it remove a nested object the resource does not store separately: omitting a profile's inputAgentSettings under an overwrite does not remove the input agent, because a profile without one is not a state the store can hold. Send the nested object to reset inside it. core$mode=noResolve, likewise inherited by everything nested in it, matches nothing against the stored record, so the collections it covers are replaced, and a core$index or core$delete anywhere below it is rejected with 400 rather than passed over. core$index is a zero-based position in the stored list, sent as a number or its string form: the array element carrying it is merged into that stored element instead of replacing the list, and core$delete=true on the same element removes that position instead. Both steer an element of a list and are rejected with 400 anywhere else — on the body root, or on a member that holds a single object rather than a collection. Positions are counted in the list as it stood before the patch, so a core$delete earlier in the same array does not shift the entries a later core$index names, and the order of the elements does not matter. A core$index naming a position the list does not have, or whose stored element is of another type, is not honoured; the array then reads as an ordinary send. An element carrying core$delete=true is the exception: a delete steers the collection whether or not it matched anything, so an unhonoured one leaves the stored list exactly as it was rather than replacing it. An array no element of which carries an honoured marker replaces the stored collection, and [] clears it. An array that mixes steered and unmarked elements keeps the stored collection and adds the unmarked ones, so re-sending such a body adds them again — only an array whose every element is steered is idempotent. Outside a free-form map or property bag, whose keys are the caller's own and are stored verbatim however they are spelled, any other member spelling a $ is rejected with 400 — including a misspelled marker, a core$index that is not a whole number, a core$delete that is not true or false, a core$mode other than overwrite, noResolve or inherited, and any of the three carrying an object or an array as its value.", "content": { "application/merge-patch+json": { "schema": { "$ref": "#/components/schemas/core.IntegrationPartnerPatch", "title": "IntegrationPartner" }, "examples": { "fields": { "summary": "Rename the partner", "description": "An ordinary merge patch: every member not sent keeps its stored value.", "value": { "name": "ACME Logistics" } }, "overwrite": { "summary": "Reset the members this body omits", "description": "Every member this body omits goes back to its default, as far as the record can store its absence — a member the API does not accept as input is never reset.", "value": { "core$mode": "overwrite", "name": "ACME Logistics" } } } } } }, "responses": { "200": { "description": "Partner updated.", "content": { "application/json": { "schema": { "type": "object", "description": "Standard Platform API success envelope. Every response carries exactly one of `data` (success) or `error` (failure) at top level.", "properties": { "data": { "$ref": "#/components/schemas/core.IntegrationPartner", "title": "IntegrationPartner" } }, "required": [ "data" ] } } } }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" }, "409": { "$ref": "#/components/responses/Conflict" }, "422": { "$ref": "#/components/responses/UnprocessableEntity" }, "500": { "$ref": "#/components/responses/ServerError" } } } } }, "components": { "schemas": { "core.IntegrationPartnerPatch": { "type": "object", "description": "Merge-patch form of core.IntegrationPartner: every member is optional; an overwrite (core$mode=overwrite) must still carry name.", "properties": { "class": { "type": "string", "const": "core.IntegrationPartner", "description": "The type of this object, always `core.IntegrationPartner`. Required where the member it is sent in accepts several types." }, "contacts": { "type": "array", "description": "Contacts of the partner (people, not logins). `null` or left out keeps the stored contacts; a list replaces them: contacts not in the list are deleted, entries with a known `id` are updated, entries without one are created. Not carried on the partner embedded in a channel body.", "items": { "$ref": "#/components/schemas/core.IntegrationPartnerContact", "title": "IntegrationPartnerContact" } }, "id": { "type": "integer", "format": "int64", "description": "Numeric id of the partner, assigned by the server on create; it is the `{partnerId}` in the partner's URLs and its channels' URLs. A value sent in a body is ignored.", "readOnly": true }, "isManagedByTpm": { "type": "boolean", "description": "`true` when the partner is managed by the connected Trading Partner Manager (TPM). Such a partner and its channels cannot be deleted through this API. Set only by the TPM synchronisation; a value sent here is ignored.", "readOnly": true }, "name": { "type": "string", "description": "Display name of the partner. Must be unique among all partners that are not deleted.", "examples": [ "ACME Logistics" ], "maxLength": 250, "minLength": 1 }, "policy": { "type": "string", "description": "Name of the rate-limit policy for requests this partner sends to the platform's REST API and MCP endpoints through its HTTP channels; it applies when the HTTP channel itself names no policy. `Global` is built in; another name must reference an existing policy. Left out on create or blank, the partner has no policy; an explicit `null` keeps the stored value.", "examples": [ "Global" ] }, "status": { "$ref": "#/components/schemas/core.PartnerStatus", "description": "Whether the partner is in use. An explicit `null` keeps the stored value. `DELETED` cannot be written - delete the partner with DELETE instead.", "title": "PartnerStatus" } }, "title": "IntegrationPartner" }, "core.IntegrationPartner": { "type": "object", "description": "A business party the platform exchanges data with. The partner itself holds only its name, status and rate-limit policy; its connections (logins, addresses, identifiers) are channels, managed under `/platform/api/partners/preview/{partnerId}/channels`, which profiles reference instead of the partner. `{\"name\": \"ACME Logistics\"}` is enough to create one; deleting a partner also deletes all its channels.", "properties": { "class": { "type": "string", "const": "core.IntegrationPartner", "description": "The type of this object, always `core.IntegrationPartner`. Required where the member it is sent in accepts several types." }, "contacts": { "type": "array", "description": "Contacts of the partner (people, not logins). `null` or left out keeps the stored contacts; a list replaces them: contacts not in the list are deleted, entries with a known `id` are updated, entries without one are created. Not carried on the partner embedded in a channel body.", "items": { "$ref": "#/components/schemas/core.IntegrationPartnerContact", "title": "IntegrationPartnerContact" } }, "id": { "type": "integer", "format": "int64", "description": "Numeric id of the partner, assigned by the server on create; it is the `{partnerId}` in the partner's URLs and its channels' URLs. A value sent in a body is ignored.", "readOnly": true }, "isManagedByTpm": { "type": "boolean", "description": "`true` when the partner is managed by the connected Trading Partner Manager (TPM). Such a partner and its channels cannot be deleted through this API. Set only by the TPM synchronisation; a value sent here is ignored.", "readOnly": true }, "name": { "type": "string", "description": "Display name of the partner. Must be unique among all partners that are not deleted.", "examples": [ "ACME Logistics" ], "maxLength": 250, "minLength": 1 }, "policy": { "type": "string", "description": "Name of the rate-limit policy for requests this partner sends to the platform's REST API and MCP endpoints through its HTTP channels; it applies when the HTTP channel itself names no policy. `Global` is built in; another name must reference an existing policy. Left out on create or blank, the partner has no policy; an explicit `null` keeps the stored value.", "examples": [ "Global" ] }, "status": { "$ref": "#/components/schemas/core.PartnerStatus", "default": "ACTIVE", "description": "Whether the partner is in use. An explicit `null` keeps the stored value. `DELETED` cannot be written - delete the partner with DELETE instead.", "title": "PartnerStatus" } }, "required": [ "name" ], "title": "IntegrationPartner" }, "core.IntegrationPartnerContact": { "type": "object", "description": "A contact person (a person to reach, not a login): a partner's contacts are held in the partner's `contacts` member, the contact of a single channel in that channel's `contact` member.", "properties": { "class": { "type": "string", "const": "core.IntegrationPartnerContact", "description": "The type of this object, always `core.IntegrationPartnerContact`. Required where the member it is sent in accepts several types." }, "emailAddresses": { "type": "array", "description": "E-mail addresses of the contact, one entry per address; in the stored form (comma-separated, display names quoted) they must fit 250 characters.", "examples": [ "[\"erika.mustermann@acme.example\"]" ], "items": { "type": "string" } }, "fax": { "type": "string", "description": "Fax number of the contact.", "examples": [ "+49 30 1234568" ], "maxLength": 20 }, "id": { "type": "integer", "format": "int64", "description": "Id of the contact, assigned by the server. Ignored on create. On a partner update an id must be one of this partner's stored contact ids and updates that contact, left out creates a new one; on a channel update it must be the id of the channel's own contact, left out updates that contact." }, "language": { "type": "string", "description": "Language of the contact, as stored by the platform; left out or blank, none is set.", "examples": [ "de" ], "maxLength": 10 }, "name": { "type": "string", "description": "Name of the contact person.", "examples": [ "Erika Mustermann" ], "maxLength": 250 }, "note": { "type": "string", "description": "Free-text note about the contact." }, "phone": { "type": "string", "description": "Phone number of the contact.", "examples": [ "+49 30 1234567" ], "maxLength": 20 } }, "title": "IntegrationPartnerContact" }, "core.PartnerStatus": { "type": "string", "description": "Lifecycle state of a partner (its `status`). It switches all of the partner's channels on or off at once, on top of each channel's own `active` flag.\n\n- `ACTIVE`: The partner is in use; each of its channels works when its own `active` is true. The default.\n- `INACTIVE`: The partner is switched off: the platform treats all its channels as inactive and the channel list shows them with `active: false`, while their stored flags stay unchanged and return with `ACTIVE`.\n- `DELETED`: Soft-deleted by DELETE. Cannot be written, and a read normally never shows a deleted partner.", "enum": [ "ACTIVE", "INACTIVE", "DELETED" ], "title": "PartnerStatus" }, "ErrorResponse": { "type": "object", "description": "Standard Platform API error envelope: every failed request carries a single `error` object. Every response carries exactly one of `data` (success) or `error` (failure) at top level.", "properties": { "error": { "$ref": "#/components/schemas/Error", "description": "What went wrong: branch on its `code`, show its `message`, and read `details` for one entry per failed member." } }, "required": [ "error" ] }, "Error": { "type": "object", "description": "The error that made the request fail.", "properties": { "code": { "type": "string", "description": "Stable machine-readable `UPPER_SNAKE` error code for programmatic handling, e.g. `PARTNER_NOT_FOUND`, `VALIDATION_FAILED` or `FORBIDDEN`. New codes may be added over time; treat an unknown code according to the HTTP status class. The HTTP status itself is carried by the status line only, never duplicated here." }, "message": { "type": "string", "description": "Human-readable, non-localized explanation. Branch on `code`, never on this text." }, "details": { "type": "array", "description": "Individual failures within the request, e.g. one entry per violated field.", "items": { "$ref": "#/components/schemas/ErrorDetail" } } }, "required": [ "code", "message" ] }, "ErrorDetail": { "type": "object", "description": "A single failure within the request.", "properties": { "code": { "type": "string", "description": "Stable machine-readable `UPPER_SNAKE` code of this individual failure, e.g. `FIELD_REQUIRED`, `FIELD_INVALID` or `VALUE_OUT_OF_RANGE`." }, "message": { "type": "string", "description": "Human-readable, non-localized explanation of this individual failure." }, "target": { "type": "string", "description": "What this failure refers to, e.g. the violated field." } }, "required": [ "code", "message" ] } }, "responses": { "BadRequest": { "description": "The request could not be understood. `BAD_REQUEST` for a body that does not decode — its `message` names the property at fault — and `VALIDATION_FAILED` with one `details` entry per offending parameter for a malformed request target (an unknown query parameter or sort key). Also `BAD_REQUEST` for a response unit naming a channel this client cannot reference.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" }, "example": { "error": { "code": "BAD_REQUEST", "message": "The request is invalid." } } } } }, "Unauthorized": { "description": "Authentication is required or the supplied credentials are invalid.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" }, "example": { "error": { "code": "UNAUTHORIZED", "message": "Authentication is required to access this resource." } } } } }, "Forbidden": { "description": "The caller lacks the permission for this operation on this resource (`FORBIDDEN`).", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" }, "example": { "error": { "code": "FORBIDDEN", "message": "Access to this resource is denied." } } } } }, "NotFound": { "description": "The requested resource does not exist. The code is resource-specific, e.g. `PARTNER_NOT_FOUND`, `PROFILE_NOT_FOUND` or `CHANNEL_NOT_FOUND`.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" }, "example": { "error": { "code": "PARTNER_NOT_FOUND", "message": "Partner not found: 42" } } } } }, "Conflict": { "description": "The request collides with the current state of the resource: a name already taken by another record, a `lastModified` that no longer matches because the record changed since it was read, or a delete the record's state refuses (it is open in the Lobster UI, still referenced by other objects, or managed by the Transport Manager). A field-level collision carries one `details` entry naming it.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" }, "example": { "error": { "code": "CONFLICT", "message": "The request conflicts with the current state of the resource.", "details": [ { "code": "FIELD_INVALID", "message": "A partner with the name 'ACME' already exists.", "target": "name" } ] } } } } }, "UnprocessableEntity": { "description": "The body decoded but breaks a rule of the resource: a required member is missing, a value is not one of the accepted values or violates its pattern, length or range, a referenced id (partner, certificate, profile) does not exist, or two members contradict each other. One `details` entry per violation: `FIELD_REQUIRED` or `FIELD_INVALID`, with `target` naming the member path and `message` the rule, including the accepted values where there is a fixed set.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" }, "example": { "error": { "code": "VALIDATION_FAILED", "message": "Request validation failed", "details": [ { "code": "FIELD_REQUIRED", "message": "Field 'name' must not be empty.", "target": "name" } ] } } } } }, "ServerError": { "description": "An unexpected server error occurred.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" }, "example": { "error": { "code": "INTERNAL_ERROR", "message": "An unexpected error occurred." } } } } } }, "securitySchemes": { "OAuth2": { "type": "oauth2", "description": "OAuth2 client-credentials flow for machine-to-machine access.\nExchange a client id/secret at `/dw/register/oauth/token` for a bearer token, then send it\nas `Authorization: Bearer `. Client credentials are provisioned in the\nLobster administration UI.\n\nThis is the client-credentials token endpoint of the Platform API. `/oauth/token`, the\n`token_endpoint` advertised at `/.well-known/oauth-authorization-server`, serves the authorization\ncode flow of the `OAuth2AuthorizationCode` scheme.", "flows": { "clientCredentials": { "tokenUrl": "/dw/register/oauth/token", "refreshUrl": "/dw/register/oauth/token", "scopes": {} } } }, "OAuth2AuthorizationCode": { "type": "oauth2", "description": "OAuth 2.1 authorization code flow with PKCE, for acting as a signed-in user. It works with Microsoft and other single sign-on providers: the provider only confirms the user's identity to the platform, which then issues its own token.\n\n1. `POST /oauth/register` (no authentication) with the JSON body `{\"redirect_uris\": [\"\"]}` returns a `client_id`. The user's browser has to reach the redirect URI.\n2. Open `/oauth/authorize` in the user's browser with `response_type=code`, `client_id`, `redirect_uri`, `state` and a `code_challenge` with `code_challenge_method=S256`. `state` is a fresh unguessable value per request, and `code_challenge` is the S256 hash of a `code_verifier` freshly generated per request from a cryptographically secure random source (43 to 128 characters, RFC 7636). The user signs in on the platform's login page, and the browser is redirected to the redirect URI with `code` and `state`; a callback whose `state` differs from the one sent is to be rejected.\n3. The code is valid for 30 seconds. Exchange it at `POST /oauth/token`, form-encoded, with `grant_type=authorization_code`, `code`, `redirect_uri`, `code_verifier` and `client_id`.\n4. The answer carries `access_token` (valid 30 minutes), `refresh_token` (valid 48 hours) and `expires_in` **in milliseconds**. Refresh with `grant_type=refresh_token`, `refresh_token` and `client_id`.", "flows": { "authorizationCode": { "authorizationUrl": "/oauth/authorize", "tokenUrl": "/oauth/token", "refreshUrl": "/oauth/token", "scopes": {} } } }, "SessionBearer": { "type": "http", "description": "Bearer token obtained from an authenticated browser or guest-user session.\n\n**Flow 1 — UI login:** Log in via the Lobster web UI at `/`, then call\n`GET /auth/sessionBearerToken` to exchange the session cookie for a bearer token.\n\n**Flow 2 — Guest user login token:** Append a GuestUser `loginToken` as a URL\nquery parameter to the base URL to establish a password-free session:\n```\nGET ?loginToken=\n```\nThen call `GET /auth/sessionBearerToken` to obtain the bearer token.", "scheme": "bearer" } } } } ````