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
- Set schemaVersion to exactly 1 on the app definition.
- Ensure slug and name are non-empty strings (slug must be kebab, matching the branding manifest).
- Populate methods with at least one connection method built via the method() helper (transport, auth, defaults, riskTier, guidanceMd).
- 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
- Always construct definitions via the method()/field() helpers rather than raw object literals.
- Never hand-edit generated definition JSON; edit the generator.
- Run the ingest script locally before committing generator changes.
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
- does not carry submitted form data
- : capture has no states
- / / : required field needs placeholder
- / : oauth requires ownershipModes
- / : tool api_key requires keyPlacement
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)