diff --git a/.changeset/model-provider-deletion.md b/.changeset/model-provider-deletion.md
new file mode 100644
index 000000000..4bfb1896b
--- /dev/null
+++ b/.changeset/model-provider-deletion.md
@@ -0,0 +1,7 @@
+---
+'@truefoundry/trueforge': minor
+'@truefoundry/trueforge-sdk': patch
+'@truefoundry/trueforge-ui': patch
+---
+
+Add model-provider deletion via the settings API, SDK, and UI adapter.
diff --git a/.github/fern/openapi/openapi.json b/.github/fern/openapi/openapi.json
index 68e38c3cf..5adcc3142 100644
--- a/.github/fern/openapi/openapi.json
+++ b/.github/fern/openapi/openapi.json
@@ -1036,6 +1036,10 @@
"properties": {},
"type": "object"
},
+ "DeleteModelProviderResponse": {
+ "properties": {},
+ "type": "object"
+ },
"DynamicSubAgentsConfig": {
"default": {
"enabled": true
@@ -6663,6 +6667,55 @@
"x-fern-sdk-method-name": "create_or_update"
}
},
+ "/api/v1/settings/model-providers/{name}": {
+ "delete": {
+ "description": "Deletes a configured model provider by name.",
+ "parameters": [
+ {
+ "description": "Model provider name.",
+ "in": "path",
+ "name": "name",
+ "required": true,
+ "schema": {
+ "description": "Model provider name.",
+ "minLength": 1,
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DeleteModelProviderResponse"
+ }
+ }
+ },
+ "description": "The model provider was deleted."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Model provider not found."
+ }
+ },
+ "summary": "Delete a model provider",
+ "tags": [
+ "Models"
+ ],
+ "x-fern-sdk-group-name": [
+ "settings",
+ "modelProviders"
+ ],
+ "x-fern-sdk-method-name": "delete"
+ }
+ },
"/api/v1/settings/sandbox-providers": {
"get": {
"description": "The single configured sandbox provider for this tenant. `auth.api_key` is redacted.",
diff --git a/docs/openapi.json b/docs/openapi.json
index 68e38c3cf..5adcc3142 100644
--- a/docs/openapi.json
+++ b/docs/openapi.json
@@ -1036,6 +1036,10 @@
"properties": {},
"type": "object"
},
+ "DeleteModelProviderResponse": {
+ "properties": {},
+ "type": "object"
+ },
"DynamicSubAgentsConfig": {
"default": {
"enabled": true
@@ -6663,6 +6667,55 @@
"x-fern-sdk-method-name": "create_or_update"
}
},
+ "/api/v1/settings/model-providers/{name}": {
+ "delete": {
+ "description": "Deletes a configured model provider by name.",
+ "parameters": [
+ {
+ "description": "Model provider name.",
+ "in": "path",
+ "name": "name",
+ "required": true,
+ "schema": {
+ "description": "Model provider name.",
+ "minLength": 1,
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DeleteModelProviderResponse"
+ }
+ }
+ },
+ "description": "The model provider was deleted."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RequestErrorResponse"
+ }
+ }
+ },
+ "description": "Model provider not found."
+ }
+ },
+ "summary": "Delete a model provider",
+ "tags": [
+ "Models"
+ ],
+ "x-fern-sdk-group-name": [
+ "settings",
+ "modelProviders"
+ ],
+ "x-fern-sdk-method-name": "delete"
+ }
+ },
"/api/v1/settings/sandbox-providers": {
"get": {
"description": "The single configured sandbox provider for this tenant. `auth.api_key` is redacted.",
diff --git a/packages/trueforge-sdk/reference.md b/packages/trueforge-sdk/reference.md
index 2787f170e..fd507949d 100644
--- a/packages/trueforge-sdk/reference.md
+++ b/packages/trueforge-sdk/reference.md
@@ -2497,6 +2497,69 @@ await client.settings.modelProviders.createOrUpdate({
+
+
+
+
+client.settings.modelProviders.delete(name) -> void
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Deletes a configured model provider by name.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```typescript
+await client.settings.modelProviders.delete("name");
+
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**name:** `string` — Model provider name.
+
+
+
+
+
+-
+
+**requestOptions:** `ModelProvidersClient.RequestOptions`
+
+
+
+
+
+
+
diff --git a/packages/trueforge-sdk/src/api/resources/settings/resources/modelProviders/client/Client.ts b/packages/trueforge-sdk/src/api/resources/settings/resources/modelProviders/client/Client.ts
index a3fdeef0e..eaf840f3d 100644
--- a/packages/trueforge-sdk/src/api/resources/settings/resources/modelProviders/client/Client.ts
+++ b/packages/trueforge-sdk/src/api/resources/settings/resources/modelProviders/client/Client.ts
@@ -353,4 +353,80 @@ export class ModelProvidersClient {
"/api/v1/settings/model-providers",
);
}
+
+ /**
+ * Deletes a configured model provider by name.
+ *
+ * @param {string} name - Model provider name.
+ * @param {ModelProvidersClient.RequestOptions} requestOptions - Request-specific configuration.
+ *
+ * @throws {@link TrueForge.NotFoundError}
+ * @throws {@link errors.TrueForgeError}
+ * @throws {@link errors.TrueForgeTimeoutError}
+ *
+ * @example
+ * await client.settings.modelProviders.delete("name")
+ */
+ public delete(name: string, requestOptions?: ModelProvidersClient.RequestOptions): core.HttpResponsePromise {
+ return core.HttpResponsePromise.fromPromise(this.__delete(name, requestOptions));
+ }
+
+ private async __delete(
+ name: string,
+ requestOptions?: ModelProvidersClient.RequestOptions,
+ ): Promise> {
+ const _authRequest: core.AuthRequest = await this._options.authProvider.getAuthRequest();
+ const _headers: core.Fetcher.Args["headers"] = mergeHeaders(
+ _authRequest.headers,
+ this._options?.headers,
+ requestOptions?.headers,
+ );
+ const _response = await (this._options.fetcher ?? core.fetcher)({
+ url: core.url.join(
+ (await core.Supplier.get(this._options.baseUrl)) ??
+ (await core.Supplier.get(this._options.environment)),
+ `api/v1/settings/model-providers/${core.url.encodePathParam(name)}`,
+ ),
+ method: "DELETE",
+ headers: _headers,
+ queryString: core.url.queryBuilder().mergeAdditional(requestOptions?.queryParams).build(),
+ timeoutMs: (requestOptions?.timeoutInSeconds ?? this._options?.timeoutInSeconds ?? 60) * 1000,
+ maxRetries: requestOptions?.maxRetries ?? this._options?.maxRetries,
+ abortSignal: requestOptions?.abortSignal,
+ fetchFn: this._options?.fetch,
+ logging: this._options.logging,
+ });
+ if (_response.ok) {
+ return { data: undefined, rawResponse: _response.rawResponse };
+ }
+
+ if (_response.error.reason === "status-code") {
+ switch (_response.error.statusCode) {
+ case 404:
+ throw new TrueForge.NotFoundError(
+ serializers.RequestErrorResponse.parseOrThrow(_response.error.body, {
+ unrecognizedObjectKeys: "passthrough",
+ allowUnrecognizedUnionMembers: true,
+ allowUnrecognizedEnumValues: true,
+ skipValidation: true,
+ breadcrumbsPrefix: ["response"],
+ }),
+ _response.rawResponse,
+ );
+ default:
+ throw new errors.TrueForgeError({
+ statusCode: _response.error.statusCode,
+ body: _response.error.body,
+ rawResponse: _response.rawResponse,
+ });
+ }
+ }
+
+ return handleNonStatusCodeError(
+ _response.error,
+ _response.rawResponse,
+ "DELETE",
+ "/api/v1/settings/model-providers/{name}",
+ );
+ }
}
diff --git a/packages/trueforge-sdk/tests/tsconfig.json b/packages/trueforge-sdk/tests/tsconfig.json
index ac39744de..b2cf8f32e 100644
--- a/packages/trueforge-sdk/tests/tsconfig.json
+++ b/packages/trueforge-sdk/tests/tsconfig.json
@@ -3,7 +3,7 @@
"compilerOptions": {
"outDir": null,
"rootDir": "..",
- "types": ["vitest/globals"]
+ "types": ["node", "vitest/globals"]
},
"include": ["../src", "../tests"],
"exclude": []
diff --git a/packages/trueforge-sdk/tests/wire/settings/modelProviders.test.ts b/packages/trueforge-sdk/tests/wire/settings/modelProviders.test.ts
index 666adb3fb..37d030283 100644
--- a/packages/trueforge-sdk/tests/wire/settings/modelProviders.test.ts
+++ b/packages/trueforge-sdk/tests/wire/settings/modelProviders.test.ts
@@ -368,4 +368,33 @@ describe("ModelProvidersClient", () => {
});
}).rejects.toThrow(TrueForgeTypes.BadRequestError);
});
+
+ test("delete (1)", async () => {
+ const server = mockServerPool.createServer();
+ const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
+
+ server.mockEndpoint().delete("/api/v1/settings/model-providers/name").respondWith().statusCode(200).build();
+
+ const response = await client.settings.modelProviders.delete("name");
+ expect(response).toEqual(undefined);
+ });
+
+ test("delete (2)", async () => {
+ const server = mockServerPool.createServer();
+ const client = new TrueForge({ maxRetries: 0, token: "test", baseUrl: server.baseUrl });
+
+ const rawResponseBody = { error: { message: "message" } };
+
+ server
+ .mockEndpoint()
+ .delete("/api/v1/settings/model-providers/name")
+ .respondWith()
+ .statusCode(404)
+ .jsonBody(rawResponseBody)
+ .build();
+
+ await expect(async () => {
+ return await client.settings.modelProviders.delete("name");
+ }).rejects.toThrow(TrueForgeTypes.NotFoundError);
+ });
});
diff --git a/packages/trueforge-ui/src/containers/SettingsBuilder/ModelSettings.tsx b/packages/trueforge-ui/src/containers/SettingsBuilder/ModelSettings.tsx
index 8836278d6..dcf004504 100644
--- a/packages/trueforge-ui/src/containers/SettingsBuilder/ModelSettings.tsx
+++ b/packages/trueforge-ui/src/containers/SettingsBuilder/ModelSettings.tsx
@@ -391,7 +391,7 @@ const ModelSettings = () => {
onClick={() => {
handleUpdateModels(
provider,
- provider.models.filter(item => item.id !== model.id),
+ provider.models.filter(m => m.id !== model.id),
);
}}
>
diff --git a/packages/trueforge-ui/src/plugins/trueforge-agent-server-adapter/catalogs/modelProviderCatalog.ts b/packages/trueforge-ui/src/plugins/trueforge-agent-server-adapter/catalogs/modelProviderCatalog.ts
index eac2c6154..fb3b75b02 100644
--- a/packages/trueforge-ui/src/plugins/trueforge-agent-server-adapter/catalogs/modelProviderCatalog.ts
+++ b/packages/trueforge-ui/src/plugins/trueforge-agent-server-adapter/catalogs/modelProviderCatalog.ts
@@ -175,5 +175,8 @@ export function createModelProviderCatalog(
});
return toUiModelProvider(body.data);
},
+ deleteModelProvider: async ({ id }) => {
+ await client.settings.modelProviders.delete(id);
+ },
};
}
diff --git a/packages/trueforge-ui/test/containers/SettingsBuilder/ModelSettings.test.tsx b/packages/trueforge-ui/test/containers/SettingsBuilder/ModelSettings.test.tsx
index a7086ad6e..7c960828d 100644
--- a/packages/trueforge-ui/test/containers/SettingsBuilder/ModelSettings.test.tsx
+++ b/packages/trueforge-ui/test/containers/SettingsBuilder/ModelSettings.test.tsx
@@ -100,4 +100,57 @@ describe('ModelSettings custom provider editing', () => {
models: customProvider.models,
});
});
+
+ it('removes only the selected model when clicking model trash icon without deleting provider', async () => {
+ const multiModelProvider: ModelProviderBase = {
+ id: 'openai',
+ type: 'openai',
+ name: 'OpenAI',
+ models: [
+ { id: 'gpt-4.1', name: 'gpt-4-1' },
+ { id: 'gpt-5-mini', name: 'gpt-5-mini' },
+ ],
+ };
+ const updateModelProvider = vi.fn(async () => multiModelProvider);
+ const deleteModelProvider = vi.fn(async () => {});
+ const server = createMockAgentUIServer({
+ catalog: createMockCatalog({
+ modelCatalog: {
+ getModelProviderCatalog: async () => [
+ {
+ type: 'openai',
+ name: 'OpenAI',
+ models: [
+ { id: 'gpt-4.1', name: 'gpt-4-1' },
+ { id: 'gpt-5-mini', name: 'gpt-5-mini' },
+ ],
+ },
+ ],
+ listModelProviders: async () => [multiModelProvider],
+ createModelProvider: vi.fn(),
+ updateModelProvider,
+ deleteModelProvider,
+ },
+ }),
+ });
+
+ render(
+
+
+ ,
+ );
+
+ const removeModelButton = await screen.findByRole('button', { name: 'Remove gpt-4-1' });
+ fireEvent.click(removeModelButton);
+
+ await waitFor(() => expect(updateModelProvider).toHaveBeenCalledTimes(1));
+ expect(updateModelProvider).toHaveBeenCalledWith({
+ id: 'openai',
+ type: 'openai',
+ name: 'OpenAI',
+ apiKey: '',
+ models: [{ id: 'gpt-5-mini', name: 'gpt-5-mini' }],
+ });
+ expect(deleteModelProvider).not.toHaveBeenCalled();
+ });
});
diff --git a/packages/trueforge/src/apis/modelProviders.ts b/packages/trueforge/src/apis/modelProviders.ts
index c189ae334..a461b8b26 100644
--- a/packages/trueforge/src/apis/modelProviders.ts
+++ b/packages/trueforge/src/apis/modelProviders.ts
@@ -7,6 +7,7 @@ import {
import type { WithTransaction } from '../db/transaction';
import {
createModelProviderRoute,
+ deleteModelProviderRoute,
listModelProvidersRoute,
putModelProviderRoute,
} from '../routes/modelProviderRoutes';
@@ -64,6 +65,21 @@ function toWireProvider(record: ModelProviderRecord): ConfiguredModelProvider {
}
export function createModelProvidersRouter(deps: ModelProvidersRouterDeps) {
+ const deleteHandler: RouteHandler = async c => {
+ const { name } = c.req.valid('param');
+
+ const deleted = await deps.modelProviderStore.deleteProvider({
+ tenant_id: TENANT_ID,
+ name,
+ });
+
+ if (!deleted) {
+ return c.json({ error: { message: `Model provider "${name}" not found` } }, 404);
+ }
+
+ return c.json({}, 200);
+ };
+
const listHandler: RouteHandler = async c => {
const records = await deps.modelProviderStore.listProviders(TENANT_ID);
return c.json({ data: records.map(toWireProvider) }, 200);
@@ -120,5 +136,6 @@ export function createModelProvidersRouter(deps: ModelProvidersRou
router.openapi(listModelProvidersRoute, listHandler);
router.openapi(createModelProviderRoute, createHandler);
router.openapi(putModelProviderRoute, putHandler);
+ router.openapi(deleteModelProviderRoute, deleteHandler);
return router;
}
diff --git a/packages/trueforge/src/db/modelProviderStore.ts b/packages/trueforge/src/db/modelProviderStore.ts
index 3197ee57c..53dc27367 100644
--- a/packages/trueforge/src/db/modelProviderStore.ts
+++ b/packages/trueforge/src/db/modelProviderStore.ts
@@ -62,6 +62,8 @@ export interface IModelProviderStore {
upsertProvider(input: UpsertModelProviderInput, transaction?: TTransaction): Promise;
/** Flattens manifests into the FQN read view for GET /models. */
listModels(tenantId: string, transaction?: TTransaction): Promise;
+
+ deleteProvider(input: GetModelProviderInput, transaction?: TTransaction): Promise;
}
/** Application-side flatten shared by both store implementations. */
diff --git a/packages/trueforge/src/db/postgres/model-provider-store/PostgresModelProviderStore.ts b/packages/trueforge/src/db/postgres/model-provider-store/PostgresModelProviderStore.ts
index e27fb7b96..d75c71727 100644
--- a/packages/trueforge/src/db/postgres/model-provider-store/PostgresModelProviderStore.ts
+++ b/packages/trueforge/src/db/postgres/model-provider-store/PostgresModelProviderStore.ts
@@ -120,6 +120,18 @@ export class PostgresModelProviderStore implements IModelProviderStore): Promise {
+ const db = transaction ?? this.#db;
+
+ const result = await db
+ .deleteFrom('model_provider')
+ .where('tenant_id', '=', input.tenant_id)
+ .where('name', '=', input.name)
+ .executeTakeFirst();
+
+ return Number(result.numDeletedRows) > 0;
+ }
+
async listModels(tenantId: string, transaction?: Transaction): Promise {
return flattenProviderModels(await this.listProviders(tenantId, transaction));
}
diff --git a/packages/trueforge/src/db/sqlite/model-provider-store/SqliteModelProviderStore.ts b/packages/trueforge/src/db/sqlite/model-provider-store/SqliteModelProviderStore.ts
index d6503d4f5..2e04c7c2b 100644
--- a/packages/trueforge/src/db/sqlite/model-provider-store/SqliteModelProviderStore.ts
+++ b/packages/trueforge/src/db/sqlite/model-provider-store/SqliteModelProviderStore.ts
@@ -121,6 +121,18 @@ export class SqliteModelProviderStore implements IModelProviderStore): Promise {
+ const db = transaction ?? this.#db;
+
+ const result = await db
+ .deleteFrom('model_provider')
+ .where('tenant_id', '=', input.tenant_id)
+ .where('name', '=', input.name)
+ .executeTakeFirst();
+
+ return Number(result.numDeletedRows) > 0;
+ }
+
async listModels(tenantId: string, transaction?: Transaction): Promise {
return flattenProviderModels(await this.listProviders(tenantId, transaction));
}
diff --git a/packages/trueforge/src/routes/modelProviderRoutes.ts b/packages/trueforge/src/routes/modelProviderRoutes.ts
index 1c55fe26e..339caebca 100644
--- a/packages/trueforge/src/routes/modelProviderRoutes.ts
+++ b/packages/trueforge/src/routes/modelProviderRoutes.ts
@@ -3,16 +3,21 @@
* Discovery catalog lives at GET /api/v1/catalogs/model-providers.
* Handlers are registered in apis/modelProviders.ts.
*/
-import { createRoute } from '@hono/zod-openapi';
+import { createRoute, z } from '@hono/zod-openapi';
import { RequestErrorResponseSchema } from '../schemas/errors';
import {
CreateModelProviderRequestSchema,
+ DeleteModelProviderResponseSchema,
GetModelProviderResponseSchema,
ListModelProvidersResponseSchema,
UpdateModelProviderRequestSchema,
} from '../schemas/modelProvider';
import { OpenApiTag } from './openapiTags';
+const ModelProviderNameParamsSchema = z.object({
+ name: z.string().min(1).describe('Model provider name.'),
+});
+
export const listModelProvidersRoute = createRoute({
method: 'get',
path: '/',
@@ -96,3 +101,26 @@ export const putModelProviderRoute = createRoute({
},
},
});
+
+export const deleteModelProviderRoute = createRoute({
+ method: 'delete',
+ path: '/{name}',
+ tags: [OpenApiTag.MODELS],
+ summary: 'Delete a model provider',
+ description: 'Deletes a configured model provider by name.',
+ 'x-fern-sdk-group-name': ['settings', 'modelProviders'],
+ 'x-fern-sdk-method-name': 'delete',
+ request: {
+ params: ModelProviderNameParamsSchema,
+ },
+ responses: {
+ 200: {
+ content: { 'application/json': { schema: DeleteModelProviderResponseSchema } },
+ description: 'The model provider was deleted.',
+ },
+ 404: {
+ content: { 'application/json': { schema: RequestErrorResponseSchema } },
+ description: 'Model provider not found.',
+ },
+ },
+});
diff --git a/packages/trueforge/src/schemas/modelProvider.ts b/packages/trueforge/src/schemas/modelProvider.ts
index 56e1d6983..a78027385 100644
--- a/packages/trueforge/src/schemas/modelProvider.ts
+++ b/packages/trueforge/src/schemas/modelProvider.ts
@@ -211,6 +211,8 @@ export const ListModelProvidersResponseSchema = z
})
.openapi('ListModelProvidersResponse');
+export const DeleteModelProviderResponseSchema = z.object({}).openapi('DeleteModelProviderResponse');
+
/** Provider identity on the models list read view. */
export const AvailableModelProviderSchema = z
.object({
diff --git a/packages/trueforge/tests/db/modelProviderStoreContractSuite.ts b/packages/trueforge/tests/db/modelProviderStoreContractSuite.ts
index 47afa2839..722e20239 100644
--- a/packages/trueforge/tests/db/modelProviderStoreContractSuite.ts
+++ b/packages/trueforge/tests/db/modelProviderStoreContractSuite.ts
@@ -134,4 +134,29 @@ export function runModelProviderStoreContractSuite(getStore: () => IModelProvide
},
]);
});
+
+ it('deleteProvider deletes the provider and returns true, or false if not found', async () => {
+ const store = getStore();
+ await store.upsertProvider({ tenant_id: TENANT, name: 'anthropic', manifest: anthropic });
+
+ const deleted = await store.deleteProvider({ tenant_id: TENANT, name: 'anthropic' });
+ expect(deleted).toBe(true);
+
+ const fetched = await store.getProvider({ tenant_id: TENANT, name: 'anthropic' });
+ expect(fetched).toBeUndefined();
+
+ const deletedAgain = await store.deleteProvider({ tenant_id: TENANT, name: 'anthropic' });
+ expect(deletedAgain).toBe(false);
+ });
+
+ it('deleteProvider is tenant scoped', async () => {
+ const store = getStore();
+ await store.upsertProvider({ tenant_id: 'other-tenant', name: 'anthropic', manifest: anthropic });
+
+ const deleted = await store.deleteProvider({ tenant_id: TENANT, name: 'anthropic' });
+ expect(deleted).toBe(false);
+
+ const otherTenantProvider = await store.getProvider({ tenant_id: 'other-tenant', name: 'anthropic' });
+ expect(otherTenantProvider).toBeDefined();
+ });
}
diff --git a/packages/trueforge/tests/unit/apis/modelProviders.test.ts b/packages/trueforge/tests/unit/apis/modelProviders.test.ts
index 29247b273..cb7984d8b 100644
--- a/packages/trueforge/tests/unit/apis/modelProviders.test.ts
+++ b/packages/trueforge/tests/unit/apis/modelProviders.test.ts
@@ -421,3 +421,25 @@ describe('catalog presets are configurable', () => {
expect(json.data.manifest.auth.api_key).toBe(toRedactedSecretValue(`sk-${preset.type}`));
});
});
+
+describe('DELETE /model-providers/:name', () => {
+ it('deletes a configured provider and returns 200 with empty body', async () => {
+ const { settingsRouter, modelProviderStore } = await createRouters();
+ expect((await settingsRouter.request('/model-providers', putInit(anthropicBody))).status).toBe(200);
+
+ const deleteResponse = await settingsRouter.request('/model-providers/anthropic', { method: 'DELETE' });
+ expect(deleteResponse.status).toBe(200);
+ expect(await deleteResponse.json()).toEqual({});
+
+ const stored = await modelProviderStore.getProvider({ tenant_id: TENANT_ID, name: 'anthropic' });
+ expect(stored).toBeUndefined();
+ });
+
+ it('returns 404 when deleting a non-existent provider', async () => {
+ const { settingsRouter } = await createRouters();
+ const deleteResponse = await settingsRouter.request('/model-providers/non-existent', { method: 'DELETE' });
+ expect(deleteResponse.status).toBe(404);
+ const json = (await deleteResponse.json()) as { error: { message: string } };
+ expect(json.error.message).toContain('not found');
+ });
+});