{"record":{"id":"c7bdc2458a085331","repo":"paperclipai/paperclip","slug":"app-slug-connectionmethod-key-tool-api-key-requires","errorCode":null,"errorMessage":"${app.slug}/${connectionMethod.key}: tool api_key requires keyPlacement","messagePattern":"(.+?)/(.+?): tool api_key requires keyPlacement","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/ingest-app-definitions.mjs","lineNumber":1586,"sourceCode":" // AI account flow; saved REST connections remain removable through Connections.\n app.methods = [...methods, ...app.methods.filter(method => method.transport !== \"rest_api\")];\n}\nconst validateApp = (app) => {\n  if (\n    app.schemaVersion !== 1 ||\n    !app.slug ||\n    !app.name ||\n    !Array.isArray(app.methods) ||\n    app.methods.length === 0\n  )\n    throw new Error(`${app.slug || \"unknown\"}: invalid AppDefinition`);\n  for (const connectionMethod of app.methods) {\n    if (\n      connectionMethod.auth === \"api_key\" &&\n      !connectionMethod.keyPlacement &&\n      (connectionMethod.purpose ?? \"tool\") !== \"channel\"\n    )\n      throw new Error(\n        `${app.slug}/${connectionMethod.key}: tool api_key requires keyPlacement`,\n      );\n    if (\n      connectionMethod.auth === \"oauth\" &&\n      connectionMethod.ownershipModes.length === 0\n    )\n      throw new Error(\n        `${app.slug}/${connectionMethod.key}: oauth requires ownershipModes`,\n      );\n    for (const connectionField of [\n      ...(connectionMethod.tenantFields ?? []),\n      ...(connectionMethod.extensionFields ?? []),\n      ...(connectionMethod.credentialFields ?? []),\n    ])\n      if (\n        connectionField.required &&\n        connectionField.type !== \"checkbox\" &&\n        !connectionField.placeholder","sourceCodeStart":1568,"sourceCodeEnd":1604,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/scripts/ingest-app-definitions.mjs#L1568-L1604","documentation":"validateApp() requires that every connection method using api_key auth and a 'tool' purpose (the default) declares keyPlacement — where the API key is placed in requests (e.g. header/query). Without it, generated connection code cannot construct authenticated requests, so the definition is rejected.","triggerScenarios":"For a method with auth === \"api_key\" and keyPlacement undefined/null, when purpose is missing (defaults to \"tool\") or explicitly \"tool\". Channel-purpose methods (purpose === \"channel\") are exempt.","commonSituations":"Adding a new API-key app and forgetting to pass keyPlacement in the method's extra options; copying an oauth method block and switching auth to \"api_key\" without adding the placement field; a template regression that drops keyPlacement for newly ingested providers.","solutions":["Set keyPlacement on the api_key connection method (e.g. keyPlacement: { in: \"header\", name: \"Authorization\", format: \"Bearer {apiKey}\" }).","If the key is not used for tool-call auth but for channel delivery, set purpose: \"channel\" to opt out of the requirement.","Check the method() helper call for the app and pass keyPlacement through the extra object."],"exampleFix":"// before\nmethod(\"acme-key\", \"http\", \"api_key\", defaults, \"medium\", guide)\n// after\nmethod(\"acme-key\", \"http\", \"api_key\", defaults, \"medium\", guide, { keyPlacement: { in: \"header\", name: \"X-Api-Key\" } })","handlingStrategy":"validation","validationCode":"for (const m of app.methods) {\n  if (m.auth === 'api_key' && !m.keyPlacement && (m.purpose ?? 'tool') === 'tool') {\n    throw new Error(`${app.slug}/${m.key}: set keyPlacement or purpose 'channel'`);\n  }\n}","typeGuard":"const hasKeyPlacement = (m) => m.auth !== 'api_key' || m.purpose === 'channel' || Boolean(m.keyPlacement);","tryCatchPattern":null,"preventionTips":["When switching a method to api_key auth, add keyPlacement in the same edit.","Document keyPlacement requirements in the method() helper JSDoc.","Review new app PRs specifically for api_key methods missing placement."],"tags":["validation","api-key","connection","ingest"],"backgroundTag":"missing-required-config-field","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}