{"record":{"id":"537e091298f0807f","repo":"Budibase/budibase","slug":"revision-is-required-for-updates","errorCode":null,"errorMessage":"Revision is required for updates","messagePattern":"Revision is required for updates","errorType":"http","errorClass":"HTTPError","httpStatus":400,"severity":"error","filePath":"packages/server/src/api/controllers/ai/configs.ts","lineNumber":117,"sourceCode":"    isDefault: body.isDefault,\n  }\n\n  const newConfig = await sdk.ai.configs.create(createRequest)\n\n  ctx.body = await sanitizeConfig(newConfig)\n}\n\nexport const updateAIConfig = async (\n  ctx: UserCtx<UpdateAIConfigRequest, AIConfigResponse>\n) => {\n  const body = ctx.request.body\n\n  if (!body._id) {\n    throw new HTTPError(\"Config ID is required for updates\", 400)\n  }\n\n  if (!body._rev) {\n    throw new HTTPError(\"Revision is required for updates\", 400)\n  }\n\n  if (!body.name) {\n    throw new HTTPError(\"Config name is required\", 400)\n  }\n\n  const configType = body.configType ?? AIConfigType.COMPLETIONS\n\n  const updateRequest: RequiredKeys<\n    RequiredKeys<Parameters<typeof sdk.ai.configs.update>[0]>\n  > = {\n    _id: body._id,\n    _rev: body._rev,\n    name: body.name,\n    provider: body.provider,\n    credentialsFields: body.credentialsFields,\n    model: body.model,\n","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/api/controllers/ai/configs.ts#L99-L135","documentation":"updateAIConfig requires _rev (CouchDB revision) alongside _id to perform an optimistic-concurrency update. Missing _rev would force blind overwrites and break conflict detection, so the endpoint returns 400.","triggerScenarios":"PUT /api/ai/configs with body containing _id but no _rev; body built from a partial projection of the config that dropped document metadata; consumer built the payload from the create API response shape (no _rev).","commonSituations":"Client-side state stripped of _rev by a sanitizer/serializer; constructing update payloads by hand from an earlier list response cached before the doc was updated.","solutions":["Fetch the current config (GET) to obtain the latest _rev, then include both _id and _rev in the PUT body.","Do not strip document metadata fields before sending updates.","If conflicts occur (412/409), re-fetch the doc and retry the update with the fresh _rev.","Treat _rev as required in client-side update types so TypeScript surfaces its absence."],"exampleFix":"// before\nawait api.put(\"/api/ai/configs\", { _id: config._id, name: \"Renamed\" })\n// after\nconst fresh = (await api.get(`/api/ai/configs/${config._id}`)).data\nawait api.put(\"/api/ai/configs\", { ...fresh, name: \"Renamed\" })","handlingStrategy":"validation","validationCode":"if (!config._rev) throw new Error(\"Config _rev is required for updates; fetch the current document first\")","typeGuard":"const hasRev = (c: { _rev?: string }): c is { _rev: string } =>\n  typeof c._rev === \"string\" && c._rev.length > 0","tryCatchPattern":"try {\n  await api.put(\"/api/ai/configs\", body)\n} catch (e) {\n  if (e.status === 400 && e.message.includes(\"Revision is required\")) {\n    const fresh = await api.get(`/api/ai/configs/${body._id}`)\n    await api.put(\"/api/ai/configs\", { ...body, _rev: fresh.data._rev })\n  }\n}","preventionTips":["Treat _rev as mandatory in update types","Re-fetch the document before each update to get a fresh revision","Avoid caching documents across long-lived editing sessions","Do not sanitize away CouchDB metadata fields"],"tags":["http-400","validation","couchdb","optimistic-concurrency"],"backgroundTag":"missing-required-field","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}