Create a legal source
https://api.granska.cloud/v1/snippetsCreates a legal source this organisation owns — its own rules, guidance or statutes — and mints the key a granskare cites it by.
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.
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
}'{
"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:
snippetKeyis what a reviewer cites. Put this — and only this — inworkers[].snippetIdswhen you create or edit a profile withPOST /v1/profiles.idis the document address. It is whatGET /v1/snippetslists, whatGET /v1/snippets/:idreads, and whatPATCH /v1/snippets/:idedits.
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
| Parameter | Description |
|---|---|
namestring·body·required | The provision label this source is cited as, e.g. "4 kap. 1 §" or "§ 12 Ordningsregler". |
sourcestring·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. |
contentstring·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). |
authorityTypestring·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. |
validFromYearnumber·body | The year the provision took effect, or null when it does not apply. Defaults to null. |
validToYearnumber·body | The year the provision ceased to apply, or null while it still does. Defaults to null. |
anchorsLegalReference[]·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,textand the authorship fields are each a400naming 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
400listing what is accepted — never a201that quietly dropped it. - A missing or empty
name,sourceorcontent. - An
authorityTypeoutside 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
| Error | When |
|---|---|
400BAD_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. |
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 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. |
429TOO_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. |
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 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.