paperclipai/paperclip · error · Error

: invalid AppDefinition

Error message

${app.slug || "unknown"}: invalid AppDefinition

What it means

validateApp() enforces the minimal AppDefinition contract before writing app JSON into packages/shared/src/app-definitions. An app must have schemaVersion 1, a non-empty slug and name, and at least one connection method; anything else is structurally invalid and would break consumers of the generated definitions.

Solutions

  1. Set schemaVersion to exactly 1 on the app definition.
  2. Ensure slug and name are non-empty strings (slug must be kebab, matching the branding manifest).
  3. Populate methods with at least one connection method built via the method() helper (transport, auth, defaults, riskTier, guidanceMd).
  4. If the provider genuinely supports nothing, remove the app from generation rather than emitting an empty methods array.

Example fix

// before
{ name: "Acme" }
// after
{ schemaVersion: 1, slug: "acme", name: "Acme", methods: [method("acme-mcp", "mcp_remote", "oauth", ...)] }
Defensive patterns

Strategy: validation

Validate before calling

const valid = app && app.schemaVersion === 1 && !!app.slug && !!app.name && Array.isArray(app.methods) && app.methods.length > 0;
if (!valid) throw new Error('AppDefinition fails minimal contract');

Type guard

function isValidAppDefinition(app) {
  return typeof app === 'object' && app !== null &&
    app.schemaVersion === 1 && typeof app.slug === 'string' && app.slug.length > 0 &&
    typeof app.name === 'string' && app.name.length > 0 &&
    Array.isArray(app.methods) && app.methods.length > 0;
}

Try / catch

try { validateApp(app); writeOutput(app); } catch (e) { if (e.message.includes('invalid AppDefinition')) { console.error(`Definition for ${app?.slug ?? 'unknown'} is incomplete: check schemaVersion/slug/name/methods.`); } else throw e; }

Prevention

When it happens

Trigger: validateApp(app) is called for each generated app when: app.schemaVersion !== 1, app.slug is falsy, app.name is falsy, app.methods is not an Array, or app.methods.length === 0. The label falls back to "unknown" when app.slug itself is missing.

Common situations: A hand-edited or partially generated definition JSON where methods were stripped; bumping or forgetting schemaVersion after a schema change; a factory/helper change that returns an object without methods; an upstream template that produces an empty methods array for a provider with no supported connections.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/0ed0fd747b192197. Report an issue: GitHub.

Appendix: source

Thrown at scripts/ingest-app-definitions.mjs:1579

};
// Runtime credentials share the provider catalog, but never expose tool actions.
for (const [slug, name, subscription, envKey] of [["anthropic", "Claude", true, "ANTHROPIC_API_KEY"], ["openai", "OpenAI", true, "OPENAI_API_KEY"], ["openrouter", "OpenRouter", false, "OPENROUTER_API_KEY"], ["xai", "Grok", true, "XAI_API_KEY"]]) {
 let app=apps.find(a=>a.slug===slug);
 if(!app){app={schemaVersion:1,slug,name,description:`Connect ${name} accounts for your agents.`,categories:["ai"],branding:brandingFor(slug),urlPatterns:[{"openai":"https://api.openai.com/*","openrouter":"https://openrouter.ai/api/*","xai":"https://api.x.ai/*"}[slug]],methods:[]};apps.push(app);}
 const methods=(subscription?["subscription","api_key"]:["api_key"]).map(authMethod=>({key:`ai-${authMethod}`,label:authMethod==="subscription"?`${name} subscription`:`${name} API key`,purpose:"ai",transport:"runtime_auth",auth:authMethod==="subscription"?"oauth":"api_key",ai:{provider:slug,method:authMethod},grantKinds:["user","organization"],ownershipModes:["customer"],whenToUse:"Authenticate an agent with this account.",guidanceMd:"Use your personal account or an explicitly shared company account.",riskTier:"S3",...(authMethod==="api_key"?{credentialFields:[field("apiKey","API key","Enter API key")],keyPlacement:{location:"env",name:envKey}}:{})}));
 // Legacy REST entries have no tool execution adapter. Only offer the supported
 // AI account flow; saved REST connections remain removable through Connections.
 app.methods = [...methods, ...app.methods.filter(method => method.transport !== "rest_api")];
}
const validateApp = (app) => {
  if (
    app.schemaVersion !== 1 ||
    !app.slug ||
    !app.name ||
    !Array.isArray(app.methods) ||
    app.methods.length === 0
  )
    throw new Error(`${app.slug || "unknown"}: invalid AppDefinition`);
  for (const connectionMethod of app.methods) {
    if (
      connectionMethod.auth === "api_key" &&
      !connectionMethod.keyPlacement &&
      (connectionMethod.purpose ?? "tool") !== "channel"
    )
      throw new Error(
        `${app.slug}/${connectionMethod.key}: tool api_key requires keyPlacement`,
      );
    if (
      connectionMethod.auth === "oauth" &&
      connectionMethod.ownershipModes.length === 0
    )
      throw new Error(
        `${app.slug}/${connectionMethod.key}: oauth requires ownershipModes`,
      );
    for (const connectionField of [
      ...(connectionMethod.tenantFields ?? []),

View on GitHub (pinned to 3f1d897a7c)