So I had the same issue. In our case all state transitions are explicit, so main resources only support GET. What we then did was to have each resource emit a JSON Schema which lists all possible attributes plus all possible state transitions as links in the resource, with inline schema. Example:
{
“$schema”: “http://json-schema.org/draft-07/schema#”,
“type”: “object”,
“definitions”: {
“resource”: {
“type”: “object”,
“required”: [
“type”,
“id”
],
“additionalProperties”: false,
“properties”: {
“type”: {
“type”: “string”,
“const”: “thread”
},
“id”: {
“type”: “string”
},
“attributes”: {
“$ref”: “#/definitions/attributes”
}
}
},
“attributes”: {
“type”: “object”,
“additionalProperties”: false,
“required”: [
…stuff…
],
“properties”: {
…stuff…
}
}
},
“properties”: {
“data”: {
“$ref”: “#/definitions/resource”
}
},
“links”: [
{
“rel”: “likevideo”,
“href”: “{+command_href}”,
“templateRequired”: [
“command_href”
],
“submissionMediaType”: “application/vnd.api+json”,
“submissionSchema”: {
“type”: “object”,
“properties”: {
“data”: {
“type”: “object”,
“properties”: {
“attributes”: {
“type”: “object”,
“required”: [
],
“properties”: {
}
}
}
}
}
},
“targetPointers”: {
“command_href”: “/data/links/likevideo”
}
}, … more actions …
]
}
The trick is that the action href needs to be resolved against the actual JSON API document. So the schema shows all possible actions, including developer notes and such documentation (not included above), and is designed to be highly cacheable, but the actual actions available will be determined by the links section in the resource object. The href for the action will be to the same resource URL but with an additional ?profile= parameter. For POSTs (submissions) the client just post the data there, given that it knows what attributes should be in there based on the schema, and for PATCHes (updates) the client would do a GET first to see current state, update that, then PATCH it in.
With this approach we get explicit state transitions, client always knows exactly what is doable given current state, and what to submit, and we have a clear separation between the static schema (what is possible) and the individual current resource state (current attributes and links to available actions). At this point I don’t see us using the resource relationships feature in JSON API at all, and instead rely on explicit state transitions to model those. For example, if a resource had e.g. “…/relationships/comments” as a relationships resource, there are still tons of rules for whether a user can actually post a comment to that, which needs to be surfaced, and so we would just do that on “…/comments” resource instead. No implicit state transitions allowed basically.
For our purposes this is precisely what we need. The main trick was to figure out how to get JSON API and JSON Schema to each provide their different strengths between static and dynamic data.