GRANSKA

Create a legal source

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

Creates a legal source this organisation owns — its own rules, guidance or statutes — and mints the key a granskare cites it by.

Bearer tokenSpends no quotaAnswers with the rate-limit headers

What you are creating

A legal source is one provision as a reviewer sees it: what it is called, which instrument it comes from, what it says, and what kind of instrument that is. A reviewer holds a list of them and may cite them in its findings.

Two kinds of source exist side by side, and this endpoint creates the second. The first is the published law we fetch and keep — Swedish SFS and Norwegian Lovdata — which your reviewers reach through rules, by naming a provision. The second is everything else: your own regulations, your own guidance, an association's own statutes, an instrument we do not carry. That is what you author here, and it belongs to your organisation alone. Nobody else's analyses can see it.

We deliberately place no restriction on what you write or what you call it. Every authorityType is accepted, and no field marks a source as authored by you rather than fetched by us. What we carry stops at published binding law in two countries, so a rule you cannot author is a rule your audits cannot apply.

Request
curl -X POST https://api.granska.cloud/v1/snippets \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "§ 12 Ordningsregler",
    "source": "Stadgar för Brf Almen (2024)",
    "authorityType": "LOCAL_REGULATION",
    "content": "Störande arbeten i lägenheten får utföras vardagar 08–18 och lördagar 10–16.",
    "validFromYear": 2024,
    "validToYear": null
  }'
Response
{
  "success": true,
  "id": "tenant_9f3a_snippet_12_ordningsregler_k4m2",
  "snippetKey": "tenant_9f3a_snippet_12_ordningsregler_k4m2"
}

You send content. The server compiles text.

content is the provision as a person reads it. Send exactly that — the prose, nothing around it.

From it the server compiles text: the element the model actually receives, carrying the source name, the instrument type and the identifier a finding is tied back to. That element is written in the same request, so a source you create is citable by the very next analysis; there is no window in which it is stored but not yet compiled.

text is therefore not a field this endpoint accepts, and sending it is a 400. The reason is the identifier rather than the prose: the element carries the id findings are resolved against, and one forged or colliding with another source's breaks the tie between a finding and its source silently — the report still looks complete, and nothing warns anybody. Send content, and read text back from GET /v1/snippets/:id if you want to see what the model was given.

content is required and may not be empty. An empty source cannot be compiled into anything a model can cite, so it would be stored as a legal source that silently contributes nothing.

Two identifiers come back, and they are not interchangeable

The response carries both, because they answer different questions:

They may look alike on a source you have just created and they are not the same string in general. A reviewer given the document id where snippetKey belongs resolves no source at all, holds no legal framework, and says so nowhere — that is a real incident, not a caution.

Store both.

The instrument type decides how much a finding weighs

authorityType is required, and one of:

CONSTITUTIONAL_LAW, STATUTE, ORDINANCE, AGENCY_REGULATION, LOCAL_REGULATION, EU_TREATY, EU_REGULATION, EU_DIRECTIVE, EU_DECISION, CASE_LAW, SUPERVISORY_DECISION, PREPARATORY_WORKS, GENERAL_ADVICE, GUIDANCE.

A value outside the list is a 400 naming the field and listing them. That is a closed set rather than a judgement about your source: the engine ranks findings by how heavily the instrument behind them binds, and a value it has never heard of has no rank and no ordering. Nothing in the list is reserved for text we fetched — a source of your own may be a STATUTE or CASE_LAW if that is what it is.

Anchors tie your source to published law

anchors is optional and holds provisions of law we carry, each one { jurisdiction, work, pinpoint }. Use it when the rule you are authoring restates, implements or interprets a provision of published law.

Every anchor is checked when you send it. One naming a provision the library does not hold is a 400 listing it, rather than a stored source with a reference that quietly points at nothing. pinpoint is the address form — par_7§, kap_6_par_1§ — never the printed citation 7 §. Take one from a response and send it back unchanged.

How many you may hold

Your organisation may hold a fixed number of legal sources 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 snippets at any time.

There is no delete endpoint yet, so a source is removed in the admin panel. Sources you inherit — ours, and the shared catalogue — do not count against your number.

Request body

ParameterDescription
name
string·body·required
The provision label this source is cited as, e.g. "4 kap. 1 §" or "§ 12 Ordningsregler".
source
string·body·required
The instrument the provision belongs to, e.g. "Socialtjänstlag (2001:453)" or "Stadgar för Brf Almen". It is what the model is told the text is from.
content
string·body·required
The text itself, as a person would read it. The server compiles it into the citable element the model receives — you do not send that element, and text is not a field this route accepts.Required and non-empty. A source with no content cannot be compiled, so it would be stored as a legal source the model can never cite (#923).
authorityType
string·body·required
What kind of instrument this is — one of CONSTITUTIONAL_LAW, STATUTE, ORDINANCE, AGENCY_REGULATION, LOCAL_REGULATION, EU_TREATY, EU_REGULATION, EU_DIRECTIVE, EU_DECISION, CASE_LAW, SUPERVISORY_DECISION, PREPARATORY_WORKS, GENERAL_ADVICE or GUIDANCE. It decides how heavily a finding citing this source weighs. A value outside the list is a 400 naming them.Every value is accepted for a source you author yourself: nothing here is reserved for text we fetched from an official register.
validFromYear
number·body
The year the provision took effect, or null when it does not apply. Defaults to null.
validToYear
number·body
The year the provision ceased to apply, or null while it still does. Defaults to null.
anchors
LegalReference[]·body
The provisions of published law this source is tied to, each { jurisdiction, work, pinpoint }. An anchor addressing a provision the corpus does not hold is a 400 naming it.

What will be refused

  • A field the server owns. id, tenantId, snippetKey, text and the authorship fields are each a 400 naming the field and saying what to send instead. A source is created for the organisation the credential belongs to, and it is the server that 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 or empty name, source or content.
  • An authorityType outside the list above.
  • An anchor addressing a provision the library does not hold.
  • 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 empty, a field the server owns was sent (id, tenantId, snippetKey, text, authorship), a field this route does not accept was sent at all, authorityType is outside the published list, or an anchor names a provision the corpus does not hold.
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 legal sources as it may — GET /v1/quotas says how many, 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 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 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.