INVALID_MESSAGE when updating a page

G’day,

I’m having problems creating/updating pages in Confluence through the REST v2 API, more specifically an INVALID_MESSAGE error. I’m working on an app which does encryption on a per-page basis, so I want to run some code every time a page is updated/created to ensure any new content is encrypted. I’ve got an event trigger hooked up which catches any of the page creation/modification events - this part seems to work.

What doesn’t work is updating pages (or creating new ones) from the app - no matter what I try, I can’t seem to get any sensible response beyond “INVALID_MESSAGE” and a 400 status code. I’ve got the write:page:confluence permission in my manifest, as instructed by the API documentation, and as far as I can tell, the argument to the call looks like the example code. The ID is present in the path and the request body has that same ID plus a title, status, version (+1 from the previous version), and body (as a string, not an object).

Below is a minimal test app that reproduces the issue. It will attempt to run when a page containing the magic string !update is saved. Attempting to create a new page also fails in the same way, so I assume that there’s some small thing I’m missing with both. The manifest contains a bunch of scopes - {read,write,delete}:page:confluence, read:confluence-content.{summary,all}, write:confluence-content but I think only the read/write:page:confluence ones are necessary for this example.

import api, { route } from '@forge/api';

export async function run(event, context) {
	console.log(`Event ${event.eventType} on page '${event.content?.title}'`)

	const pageId = event.content.id;
	const pageContents = await getPageContents(pageId);

	const version = pageContents.version?.number;
	const body = pageContents.body?.atlas_doc_format?.value;

	const newTitle = pageContents.title.endsWith(" For Dummies") ? pageContents.title : pageContents.title + " For Dummies";
	const newBody = body.replace("!update", ""); // Don't want an infinite loop!

	if (body.includes("!update")) {
		console.log("Performing update...");
		const result = await updatePage(pageId, newTitle, version + 1, newBody);
		console.log("Update result:", result);
	}
}

const getPageContents = async (pageId) => {
	try {
		const response = await api.asApp().requestConfluence(route`/wiki/api/v2/pages/${pageId}?body-format=atlas_doc_format`, {
			headers: {
				"Accept": "application/json",
			}
		});
		if (response.ok) {
			return await response.json();
		} else {
			console.error("Unexpected response status in getPageContents(): ", response.status, response.statusText);
			console.error(await response.json());
			return {};
		}
	} catch (err) {
		console.error("Error loading page contents:", err)
	}
}

const updatePage = async (pageId, title, version, contents) => {
	try {
		const args = {
			"method": "PUT",
			"headers": {
				"Accept": "application/json",
				"Content-Type": "application/json"
			},
			"body": {
				"id": pageId,
				"status": "current",
				"title": title,
				"version": {
					"number": version,
					"message": "MODIFIED"
				},
				"body": {
					"representation": "atlas_doc_format",
					"value": contents
				}
			}
		};
		console.log(`Calling /wiki/api/v2/pages/${pageId} with args ${JSON.stringify(args)}`)
		const response = await api.asApp().requestConfluence(route`/wiki/api/v2/pages/${pageId}`, args);
		if (response.ok) {
			return await response.json();
		} else {
			console.error("Unexpected response status in updatePage(): ", response.status, response.statusText);
			console.error(await response.json());
			return {};
		}
	} catch (err) {
		console.error("Error updating page:", err)
	}
}

I have had luck with DELETE, and while I will eventually need to use that method, it’s not helpful by itself.

Hoping I’ve made a silly mistake somewhere that’ll be an easy fix :slight_smile:

Hi @RhiannonGray,

I think you have to stringify the body before passing it to requestConfluence:

const args = {
  method: "PUT",
  headers: {
    Accept: "application/json",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ // ← `requestConfluence` expects a string body
    id: pageId,
    status: "current",
    title: title,
    version: {
      number: version,
      message: "MODIFIED"
    },
    body: {
      representation: "atlas_doc_format",
      value: contents
    }
  })
};
2 Likes

That did the trick!

I knew that args.body.body was expected to be a string, but didn’t realise that the whole args.body was too. Of course, looking at the example code now, it’s obvious. Woops!

Thank you so much!

1 Like