Create an action
https://api.granska.cloud/v1/actionsCreates a follow-up action this organisation owns, as a draft. Not POST /v1/action, which runs one: /v1/actions with an s writes the action, /v1/action without one runs it.
POST /v1/actions creates an action. POST /v1/action, without the s,
runs one. The two paths differ by one character and do opposite things: this one writes your
organisation's configuration and costs no run, the other generates a document and spends one.
What you are creating
An action turns a finished audit into a document — a request for completion, the grounds for an
appeal, a plain-language letter. This endpoint creates one your organisation owns: its name, the
brief for the whole document (instructions), and the sections the document is written in. It is
the same action the admin panel creates, saved through the same rules.
A new action is always a draft. A draft is visible to this API — you can read it back with
GET /v1/actions/:id — but it is not in your organisation's menu, and
nobody can run it from the app, until you publish it with
PATCH /v1/actions/:id and { "status": "PUBLISHED" }. Sending
status here is a 400 rather than a silent downgrade: a 201 after you asked for PUBLISHED
would leave you believing you had published something.
curl -X POST https://api.granska.cloud/v1/actions \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Begäran om komplettering",
"instructions": "Write a request that the investigation be completed, from the flaws the analysis found. Ask only for what the flaws show is missing.",
"sections": [
{ "key": "missing", "label": "Det som saknas", "type": "FLAW_MAPPED_LIST", "instructions": "One item per flaw, naming what the investigation must add." },
{ "key": "request", "label": "Begäran", "type": "TEXT", "instructions": "State what is requested, and by when, in one paragraph." }
],
"fitsProfileIds": ["lss_utredning"]
}'{
"success": true,
"actionId": "action_begaran_om_komplettering_k4m2",
"status": "DRAFT"
}The id that comes back is the one you run
actionId is the action's id as GET /v1/actions lists it and as
POST /v1/action takes it in actionType, and the one
PATCH /v1/actions/:id edits. Store it.
Sections
sections is the document's structure, in order. Each entry is
{ key, label, type, instructions, variant? }:
keynames the section, unique within the action.labelis the heading the document shows.typeisTEXT(prose),LIST(a list of points), orFLAW_MAPPED_LIST(one item per flaw the audit found).instructionsis the brief for that section alone.variant, optional, is how the section is set:STANDARD,QUOTE,SUCCESS_BOXorWARNING_BOX.
An entry with any other key, or a type or variant outside those lists, is a 400 naming the
entry by its position.
How long the brief may be
instructions may be at most 20,000 characters, counted as Unicode code points. It goes into
the model's prompt every time the action runs. A longer brief is refused with a 400 whose
details a client can branch on, and nothing is stored:
{ "refusal": "action-instructions-too-long", "field": "instructions", "length": 20001, "limit": 20000 }
Which profiles it fits
fitsProfileIds names the profiles the action is written for, as
GET /v1/profiles returns their ids.
GET /v1/actions?profileId= then lists it under each of them. Leave it
out and nobody has decided: the action is listed for every profile.
How many you may hold
Your organisation may hold a fixed number of actions of its own. Creating one past that is a 409
saying how many you hold and how many you may; GET /v1/quotas reports the
same two numbers under actions at any time. There is no delete endpoint, so an action is removed
in the admin panel. Actions you inherit from the platform do not count against your number.
Request body
| Parameter | Description |
|---|---|
namestring·body·required | The action's name, as your organisation's menu shows it. |
instructionsstring·body·required·maxLength 20000 | The brief for the whole document: what the action is for and how it should read, at most 20000 characters. It goes into the model's prompt on every run.Longer than 20000 characters is a 400 with details.refusal "action-instructions-too-long". |
sectionsActionSection[]·body·required | The sections the document is written in, in order. Each is { key, label, type, instructions, variant? }: key a non-empty string unique to this action, label and instructions strings, type one of TEXT, LIST, FLAW_MAPPED_LIST, and variant, optional, one of STANDARD, QUOTE, SUCCESS_BOX, WARNING_BOX. |
taglinestring·body | One line under the name in the menu. |
descriptionstring·body | A short description of what the action produces. |
longDescstring·body | A longer description, shown where the action is explained in full. |
warningMessagestring·body | A reservation the reader sees before running the action, such as what it does not do. |
iconstring·body | The name of the icon the menu draws beside the action. |
categorystring·body | The heading the action is filed under in your organisation's menu. |
sortOrdernumber·body | Where the action sorts within its category, lowest first. |
jurisdictionsstring[]·body | The legal orders the action is written for, as codes such as SE. Each must be open on this platform; when sent, at least one. |
practiceAreasstring[]·body | The practice areas the action fits, each one of CHILD_WELFARE, FAMILY_LAW, DISABILITY_SUPPORT, CRIMINAL_LAW, SOCIAL_INSURANCE, HEALTHCARE, EMPLOYMENT, PLANNING_AND_BUILDING, STATE_LIABILITY, ASSOCIATION_LAW, INFORMATION_SECURITY, GENERAL; when sent, at least one. |
fitsProfileIdsstring[]·body | The profiles the action is written for, as the ids GET /v1/profiles returns. GET /v1/actions?profileId= lists it under each of them; left out, nobody has decided and it is listed for every profile. |
outputLanguagestring·body | The language the document is written in — one of Swedish, Norwegian (Bokmål), Danish and English. Left out, the document follows the language of the profile the analysis ran with, then your organisation's. |
What will be refused
- A field the server owns.
id,tenantId,status,publishedAt,offerable,instructionsUpdatedAtand the authorship fields are each a400naming the field. An action is created for the organisation the credential belongs to, and the server mints its identity. - A field this endpoint does not have. Sent at all, it is a
400listing what is accepted — never a201that quietly dropped it. - A missing
name,instructionsorsections, or one of the wrong shape. - A jurisdiction that is malformed or not open on this platform, a practice area or
outputLanguagethis API does not carry. - A credential belonging to the platform's own workspace, which does not author through this
API: a
403.
Errors
| Error | When |
|---|---|
400BAD_REQUEST | A required field is missing or malformed, a field the server owns was sent (id, tenantId, status, authorship, publishedAt, offerable, instructionsUpdatedAt), a field this route does not accept was sent at all, a section is malformed, a jurisdiction is malformed or not open on this platform, a practice area or outputLanguage is not one this API carries, or instructions is longer than 20000 characters — that one carries details { refusal: "action-instructions-too-long", field: "instructions", length, limit }. Each refusal names the field. |
401UNAUTHORIZED | The Authorization header is missing, is not a readable bearer token, or names no tenant. |
401TOKEN_EXPIRED | The access token was issued by this gateway and has since expired. Not probed: it needs a token older than its own lifetime. |
403FORBIDDEN | The credential belongs to the platform's own workspace, whose shared catalogue is not written through this API. Not probed: it needs a platform credential. |
409CONFLICT | The organisation already holds as many actions as it may — GET /v1/quotas says how many under actions, and they are removed in the admin panel. Not probed: it needs a tenant put in that state on purpose. |
429TOO_MANY_REQUESTS | The organisation has spent its hourly configuration-write floor, which this route shares with the profile and legal-source writes. This is an abuse floor and not a plan limit; no analysis quota is consumed. Not probed: reaching it would mean sending sixty writes. |
500INTERNAL_ERROR | An unexpected server-side failure. Not probable from outside — reaching it means something is wrong. |
This costs no runs, but it is metered
Writing configuration never spends an analysis from your quota. It does tick the same hourly floor
the profile and legal-source writes tick, reported by the X-RateLimit-* headers on this route and
by GET /v1/quotas. It is set where no human and no scheduled integration
reaches it; a 429 here means something of yours is looping.