Thanks, that’s sorted it! No, since the API documentation didn’t list archetype as required (not even “required for private message” the way target_usernames is) or say anything about what it was for I wasn’t messing with it.
Anyway, this now works, and for the reference of anyone who comes across the same problem (particuarly if using POST) the JSON-encoded body elements in my working request are:
key
value
raw
the raw message text
title
the message/thread title
target_usernames
comma-separated list of usernames
archetype
the literal string private_message
This is sent as the body of a POST to /posts.json with the custom header fields