# Upstream

Part of AppEngine API Documentation. Full index: https://appengine.appmint.io/documentation.md
## GET /upstream/integration-types/{type}

**Get integration types**

`operationId: UpstreamController_getIntegrationTypes`

The integration types the platform supports and what each provides. `type` narrows to one; omit it for the full catalogue.

#### Signature

```http
GET /upstream/integration-types/{type} (type: string) -> Integration types
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /upstream/integration-use-cases/{useCase}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `type` | path | string | yes | Integration type. Optional. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Integration types |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /upstream/integration-use-cases/{useCase}

**Get integrations for a use case**

`operationId: UpstreamController_getIntegrationUseCases`

Which integrations serve a given use case — the lookup for "what can send SMS" rather than "what does Twilio do".

#### Signature

```http
GET /upstream/integration-use-cases/{useCase} (useCase: string) -> Integrations for the use case
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /upstream/integration-types/{type}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `useCase` | path | string | yes | Use case name. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Integrations for the use case |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /upstream/get-config/{type}/{configId}

**Get an integration configuration**

`operationId: UpstreamController_getIntegrationConfig`

Reads an integration's configuration. Both path segments are optional — narrow by type, or fetch one configuration by id.

`create=true` **creates the configuration if it does not exist**, which makes this a write in disguise. Leave it off for a plain read.

Configuration carries credentials for the external service; treat the response as sensitive.

#### Signature

```http
GET /upstream/get-config/{type}/{configId} (type: string, configId: string, create?: boolean) -> The configuration
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- `create=true` writes.
- Response may contain credentials.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /upstream/save-integration`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `type` | path | string | yes | Integration type. Optional. |
| `configId` | path | string | yes | Configuration id. Optional. |
| `create` | query | boolean | — | Create the configuration if missing. Default false. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The configuration |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /upstream/active/{type}

**List active integrations**

`operationId: UpstreamController_listActiveIntegrationByType`

Integrations currently connected for the org, optionally narrowed to one type. The list to read before offering an integration-backed feature.

#### Signature

```http
GET /upstream/active/{type} (type: string) -> Active integrations
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /upstream/active/detail/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `type` | path | string | yes | Integration type. Optional. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | Active integrations |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /upstream/active/detail/{id}

**Get an active integration**

`operationId: UpstreamController_activeIntegrations`

Details of one active integration.

#### Signature

```http
GET /upstream/active/detail/{id} (id: string) -> The integration
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /upstream/active/{type}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Integration id. Optional. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The integration |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /upstream/get-integration

**Get an integration by id**

`operationId: UpstreamController_getIntegration`

Fetches an integration by identifier. A POST because the selector goes in the body; it is a read.

#### Signature

```http
POST /upstream/get-integration (body) -> The integration
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Read-only despite being a POST.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /upstream/active/detail/{id}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

Which integration.

```json
{
  "id": "INT-12"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The integration |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /upstream/shutdown/{id}

**Shut down an integration**

`operationId: UpstreamController_shutdown`

Disconnects an integration. Everything depending on it stops working immediately — payments, messaging, sync — so check what uses it first.

#### Signature

```http
POST /upstream/shutdown/{id} (id: string) -> The result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Breaks every feature that depends on it.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /upstream/active/{type}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `id` | path | string | yes | Integration id. |

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The result |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## GET /upstream/call/{integration}/{operation}

**Call an integration operation (GET)**

`operationId: UpstreamController_callGet`

The GET form of the proxy, for operations whose parameters fit in a query string. Same passthrough semantics — and note that being a GET does not make the operation safe; that depends on the external service.

#### Signature

```http
GET /upstream/call/{integration}/{operation} (integration: string, operation: string) -> The operation result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- A GET here can still have side effects upstream.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /upstream/call/{integration}/{operation}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `integration` | path | string | yes | Integration name. |
| `operation` | path | string | yes | Operation name. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The operation result |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /upstream/call/{integration}/{operation}

**Call an integration operation**

`operationId: UpstreamController_callPost`

Forwards a call to an external service. The body is passed through as the operation's parameters, and the response is whatever the service returns — this API does not interpret either.

Because it is a generic proxy, the operation can have any effect the external service supports, including charging money or sending messages. Know what the operation does before calling it.

### User account provisioning

The `MicrosoftProvider`, `GoogleProvider` and `SlackProvider` integrations create, suspend and remove staff accounts with one shape, so onboarding and offboarding call any of them the same way. Operation parameters go in `data`.

**Common operations** (all three): `createUser`, `getUser`, `updateUser`, `suspendUser`, `resumeUser`, `deleteUser`, `addUserToGroup`, `removeUserFromGroup`, `listGroups`, `provisioningCapabilities`.
**Microsoft only:** `assignLicense` (`skuId` or skuPartNumber; `removeSkuId`), `listLicenses`, `revokeSignInSessions` (alias `signOutUser`).
**Google only:** `listOrgUnits`, `signOutUser`.
**Slack only:** `deactivateUser` / `reactivateUser` (what `suspendUser` / `resumeUser` do), `inviteUser` (Enterprise Grid).

**Input** to `createUser`: `{ email, firstName, lastName, displayName?, password?, groups?, license?, orgUnit?, attributes? }`. `groups` are ids, emails or names. `license` is Microsoft only, `orgUnit` Google only; `attributes` are provider-native fields merged into the request. Other operations identify the account with `{ externalId }` or `{ email }`; group operations add `groupId`.

**Output:** `{ provider, externalId, email, status: "active" | "suspended" | "deleted", alreadyExisted?, temporaryPassword?, warnings?, raw? }`.

**Behaviour**
- `createUser` is idempotent by email: an existing account is returned untouched with `alreadyExisted: true`.
- Without `password`, Microsoft and Google get a generated temporary password that must be changed at first sign-in; it is returned once as `temporaryPassword` and never stored or logged. Slack sets no password.
- A failed licence or group step does not undo the account; it is reported in `warnings`.
- `deleteUser` requires `confirm: true`. Microsoft keeps deleted users restorable for 30 days, Google for 20; Slack cannot delete, so the account is deactivated.
- `suspendUser` with `signOut: true` also ends sessions (Microsoft revokeSignInSessions, Google signOut).
- `provisioningCapabilities` reports, per operation, what the connected credentials and plan allow, and what is missing.

**Setup and permissions**
- Microsoft (Graph v1.0): `tenantId`, `clientId`, `clientSecret` of an Entra app with admin-consented *Application* permissions User.ReadWrite.All, GroupMember.ReadWrite.All (or Group.ReadWrite.All), Group.Read.All, LicenseAssignment.ReadWrite.All, Organization.Read.All, User.RevokeSessions.All. Directory.ReadWrite.All does not allow deleting users. Optional `defaultUsageLocation` (needed before a licence); `directoryAuth: "delegated"` uses the connected admin token instead.
- Google (Admin SDK Directory API): `workspaceServiceAccountJson` and `workspaceAdminEmail` (a super admin to impersonate); optional `workspaceCustomerId`, `workspaceDomain`, `workspaceDefaultOrgUnit`. Authorise the service account client id for domain-wide delegation with admin.directory.user, admin.directory.group, admin.directory.orgunit and admin.directory.user.security. Alternatively an admin reconnects Google through `getAuthUrl` with `{ workspaceDirectory: true }`.
- Slack (SCIM v2): Business+ or Enterprise Grid plan; `scimToken` is a user token (xoxp-) with the `admin` scope from a Workspace Owner/Admin (Org Owner on Grid). `inviteUser` (admin.users.invite) is Enterprise Grid only: `adminToken` with admin.users:write, `inviteTeamId`, `defaultInviteChannelIds`. A plan or token that does not allow SCIM fails with a message saying so.

### E-Verify (`EverifyProvider`)

Save a config with `provider: "EverifyProvider"` through `POST /upstream/save-integration`. Fields: `environment` (`stage` = E-Verify test account, `production` = live), `companyId` (the employer's E-Verify company ID), and the Web Services credentials E-Verify issued — `username` + `password`, or `clientId` + `clientSecret`. Optional: `baseUrl` (override the standard URL for the environment), `caseCreatorName` / `caseCreatorEmail` / `caseCreatorPhone` (used when no user is signed in), `autoCloseAuthorized` (default true). A config in the shared org with `employerAgent: true` lends its credentials to an org whose own config has only `companyId`.

Saving the config sends every E-Verify case that was waiting for it. Cases are worked through `/business-made/everify/*`, not through `call/*`; the operations (`authenticate`, `createCase`, `submitCase`, `getCase`, `closeCase`, `confirmEmployeeNotified`, `referCase`, `confirmPhotoMatch`, `test`) are available on the proxy for diagnosis — `test` only signs in.

#### Signature

```http
POST /upstream/call/{integration}/{operation} (integration: string, operation: string, body) -> The operation result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Passthrough — effects are defined by the external service.
- Provisioning operations create, suspend and delete real accounts at the provider.

#### Errors

| Status | Code | Message | When | What to do |
| --- | --- | --- | --- | --- |
| `400` | — | Microsoft 365: deleteUser requires "confirm": true — this removes the account. | `deleteUser` without `confirm: true`. | Send `confirm: true` once the removal is intended. |

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /upstream/test/{integration}/{operation}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `integration` | path | string | yes | Integration name. |
| `operation` | path | string | yes | Operation name. |

### Request body

Operation parameters, passed through unchanged. Provisioning operations take them under `data`.

```json
{
  "limit": 10
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The operation result |
| `400` | Microsoft 365: deleteUser requires "confirm": true — this removes the account. — `deleteUser` without `confirm: true`. |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

Example response:

```json
{
  "provider": "google",
  "externalId": "104512345678901234567",
  "email": "ana.ng@acme.com",
  "status": "active",
  "alreadyExisted": false,
  "temporaryPassword": "<returned once>"
}
```

## GET /upstream/service/{serviceName}/{operation}

**Call a service operation (GET)**

`operationId: UpstreamController_serviceGet`

Calls an operation by **service name** rather than integration id — the platform resolves which configured integration provides that service. Use it when the caller cares about the capability, not the vendor.

#### Signature

```http
GET /upstream/service/{serviceName}/{operation} (serviceName: string, operation: string) -> The operation result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /upstream/service/{serviceName}/{operation}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `serviceName` | path | string | yes | Service name. |
| `operation` | path | string | yes | Operation name. |

### Responses

| Status | Meaning |
| --- | --- |
| `200` | The operation result |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /upstream/service/{serviceName}/{operation}

**Call a service operation**

`operationId: UpstreamController_servicePost`

The POST form of the service-name call. Same resolution: the platform picks the integration providing the named service.

**Note:** the handler declares its path parameters as `integration` and `operation` while the route declares `serviceName` and `operation` — the first segment binds to whichever name the route uses, so pass the service name in the first position.

#### Signature

```http
POST /upstream/service/{serviceName}/{operation} (serviceName: string, operation: string, body) -> The operation result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Passthrough — effects are defined by the external service.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `GET /upstream/service/{serviceName}/{operation}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `integration` | path | string | yes |  |
| `operation` | path | string | yes | Operation name. |
| `serviceName` | path | string | yes | Service name — the first path segment. |

### Request body

Operation parameters.

```json
{
  "to": "+15551234567",
  "body": "Hello"
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The operation result |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /upstream/test/{integration}/{operation}

**Test an integration operation**

`operationId: UpstreamController_test`

Runs an operation as a connectivity and credential check. The call still reaches the external service — "test" means it is run for diagnosis, not that it is simulated, so pick a read-only operation.

#### Signature

```http
POST /upstream/test/{integration}/{operation} (integration: string, operation: string, body) -> The test result
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Really calls the external service.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /upstream/call/{integration}/{operation}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |
| `integration` | path | string | yes | Integration name. |
| `operation` | path | string | yes | Operation name. |

### Request body

Operation parameters.

```json
{}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The test result |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

## POST /upstream/save-integration

**Save an integration**

`operationId: UpstreamController_saveIntegration`

Creates or updates an integration configuration, including its credentials. **Never log this request body.** Saving a bad credential does not fail here — it fails on the next call through the integration, so test afterwards.

#### Signature

```http
POST /upstream/save-integration (body) -> The saved integration
```

#### Access

Requires a bearer JWT (`Authorization: Bearer <token>`).

#### Notes

- Carries credentials.

#### Errors

Plus the standard platform errors: `401`, `403`, `429`, `500`.

#### See also

- `POST /upstream/test/{integration}/{operation}`

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `orgid` | header | string | yes | Organization (tenant) identifier. Every request is scoped to this org; data from other orgs is never visible. Issued with your API credentials. |

### Request body

The integration configuration.

```json
{
  "type": "payment",
  "name": "stripe",
  "config": {
    "apiKey": "<secret>"
  }
}
```

### Responses

| Status | Meaning |
| --- | --- |
| `201` | The saved integration |
| `401` | Authentication failed: Invalid or expired token — The `Authorization` header is missing, malformed, or the JWT has expired. |
| `403` | You do not have permission to perform this action — The caller is authenticated but lacks the role required by the endpoint, or is acting on another org. |
| `429` | Too Many Requests — More than 100,000 requests from one IP within 5 minutes (configurable per deployment). CORS preflights and requests from inside the platform cluster are not counted. The limiter answers before the error filter, so the body is `{ statusCode, error, message }` with no `path`, `method` or `timeStamp`; the `RateLimit-*` response headers say when the window resets. |
| `500` | An unexpected error occurred. Our team has been notified. — An unhandled server-side failure. |

