{"record":{"id":"91108bce9e45d01d","repo":"paperclipai/paperclip","slug":"the-api-client-constructor-or-http-transport-is-un","errorCode":null,"errorMessage":"the API client constructor or HTTP transport is unavailable","messagePattern":"the API client constructor or HTTP transport is unavailable","errorType":"exception","errorClass":"TeamsAdapterCompatibilityError","httpStatus":null,"severity":"error","filePath":"server/src/services/chat-sdk-runtime.ts","lineNumber":1039,"sourceCode":"  if (typeof teams.cacheUserContext !== \"function\") {\n    throw new TeamsAdapterCompatibilityError(\"cacheUserContext is unavailable\");\n  }\n  if (typeof teams.getIncomingUser !== \"function\") {\n    throw new TeamsAdapterCompatibilityError(\"getIncomingUser is unavailable\");\n  }\n  if (typeof teams.getUser !== \"function\") {\n    throw new TeamsAdapterCompatibilityError(\"getUser is unavailable\");\n  }\n  for (const methodName of TEAMS_THREAD_SCOPED_METHODS) {\n    if (typeof teams[methodName] !== \"function\") {\n      throw new TeamsAdapterCompatibilityError(`${methodName} is unavailable`);\n    }\n  }\n  if (\n    typeof teams.app.api.constructor !== \"function\" ||\n    !(\"http\" in teams.app.api)\n  ) {\n    throw new TeamsAdapterCompatibilityError(\n      \"the API client constructor or HTTP transport is unavailable\",\n    );\n  }\n  const apiDescriptor = Object.getOwnPropertyDescriptor(teams.app, \"api\");\n  if (apiDescriptor && apiDescriptor.configurable === false) {\n    throw new TeamsAdapterCompatibilityError(\n      \"app.api cannot be scoped per asynchronous conversation\",\n    );\n  }\n  const trustedConfiguredApiUrl = configuredApiUrl\n    ? normalizedTeamsServiceUrl(configuredApiUrl)\n    : null;\n\n  // The pinned Teams adapter caches activity/user metadata and may query the\n  // members or Graph APIs before Chat dispatches to Paperclip's reach and\n  // tenant checks. Keep authenticated but unadmitted events observationally\n  // inert. Accepted activities explicitly persist the minimum routing context\n  // through paperclipRecordAcceptedActivity below.","sourceCodeStart":1021,"sourceCodeEnd":1057,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-sdk-runtime.ts#L1021-L1057","documentation":"The wrapper rebuilds a per-conversation API client by invoking defaultApi.constructor as (serviceUrl, http, settings) and reusing defaultApi.http. Before doing so it verifies that the API client has a usable constructor and an 'http' transport property. If either is absent, scoping egress is impossible and the compatibility error is thrown.","triggerScenarios":"teams.app.api is not a real Bot Framework API client instance — e.g. it is a plain object mock without an 'http' property, or its constructor is not a function (Object.create(null)-based mock, arrow-function-based factory output).","commonSituations":"Test doubles that stub app.api with a hand-made object lacking http; a different bot SDK version where the client shape changed; dependency-injected fake API clients in unit tests of the Teams runtime.","solutions":["Ensure app.api is an instance of the real Bot Framework ConnectorClient/API client, which has an http transport and a callable constructor.","Update test doubles to include an http property and construct via the real client class.","Pin the bot framework SDK version so the client class retains constructor(serviceUrl, http, settings) semantics.","Log typeof api.constructor and 'http' in api at startup to catch shape drift early."],"exampleFix":"// before\napp.api = { get: fn } as any; // no http, no constructor\n// after\nimport { ConnectorClient } from 'botframework-connector';\napp.api = new ConnectorClient(credentials, { baseUri: serviceUrl }); // real client with http + constructor","handlingStrategy":"type-guard","validationCode":"const api = adapter.app?.api;\nif (!api || typeof api.constructor !== 'function' || !('http' in api)) {\n  throw new Error('app.api must be a real Bot Framework API client with http transport');\n}","typeGuard":"function isScopedApiCandidate(api: unknown): api is { constructor: new (...a: unknown[]) => object; http: unknown } {\n  return typeof api === 'object' && api !== null && typeof (api as any).constructor === 'function' && 'http' in (api as any);\n}","tryCatchPattern":"try {\n  scoped = scopeMicrosoftTeamsEgress(adapter);\n} catch (err) {\n  if (err instanceof TeamsAdapterCompatibilityError && /API client constructor/.test(err.message)) {\n    logger.error('app.api is not a real API client instance — replace mocks with ConnectorClient');\n  }\n  throw err;\n}","preventionTips":["Always construct app.api from the SDK's client class, never from plain-object literals.","In tests, instantiate the real client with fake credentials instead of mocking its shape.","Pin bot framework SDK versions and re-verify the client's constructor signature on upgrade."],"tags":["teams","adapter-compatibility","api-client"],"backgroundTag":"incompatible-source-type","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}