GRANSKA

Create an action

POSThttps://api.granska.cloud/v1/actions

Creates 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.

Bearer tokenSpends no quotaAnswers with the rate-limit headers

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.

Request
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"]
  }'
Response
{
  "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? }:

  • key names the section, unique within the action.
  • label is the heading the document shows.
  • type is TEXT (prose), LIST (a list of points), or FLAW_MAPPED_LIST (one item per flaw the audit found).
  • instructions is the brief for that section alone.
  • variant, optional, is how the section is set: STANDARD, QUOTE, SUCCESS_BOX or WARNING_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

ParameterDescription
name
string·body·required
The action's name, as your organisation's menu shows it.
instructions
string·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".
sections
ActionSection[]·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.
tagline
string·body
One line under the name in the menu.
description
string·body
A short description of what the action produces.
longDesc
string·body
A longer description, shown where the action is explained in full.
warningMessage
string·body
A reservation the reader sees before running the action, such as what it does not do.
icon
string·body
The name of the icon the menu draws beside the action.
category
string·body
The heading the action is filed under in your organisation's menu.
sortOrder
number·body
Where the action sorts within its category, lowest first.
jurisdictions
string[]·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.
practiceAreas
string[]·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.
fitsProfileIds
string[]·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.
outputLanguage
string·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, instructionsUpdatedAt and the authorship fields are each a 400 naming 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 400 listing what is accepted — never a 201 that quietly dropped it.
  • A missing name, instructions or sections, or one of the wrong shape.
  • A jurisdiction that is malformed or not open on this platform, a practice area or outputLanguage this API does not carry.
  • A credential belonging to the platform's own workspace, which does not author through this API: a 403.

Errors

ErrorWhen
400
BAD_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.
401
UNAUTHORIZED
The Authorization header is missing, is not a readable bearer token, or names no tenant.
401
TOKEN_EXPIRED
The access token was issued by this gateway and has since expired. Not probed: it needs a token older than its own lifetime.
403
FORBIDDEN
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.
409
CONFLICT
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.
429
TOO_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.
500
INTERNAL_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.