Ga naar inhoud

Organisaties beheren via de API

Het concept achter organisaties staat in Organisaties en klanten.

Op deze pagina beheer je met een API-sleutel je eigen organisatie en de organisaties eronder: een organisatie voor een klant aanmaken, organisaties lezen en hernoemen, en sleutels aanmaken of intrekken. Je gebruikt het wanneer je klanten vanuit je eigen systeem inricht in plaats van met de hand.

Je sleutel heeft de capability management nodig (Aanzetten). Hetzelfde doe je met de muis in Organisaties beheren in de console.

De capability geeft geen toegang tot de bestanden, prompts, skills of kennis van een onderliggende organisatie. Maak daar een sleutel aan en gebruik die om met de gegevens van die organisatie te werken. Beheerrechten en toegang tot gegevens worden apart gecontroleerd.

Aanzetten

Je organisatie moet management toestaan en de sleutel moet hem aanzetten. Een beheerder doet allebei in de console (Toegestane capabilities, Capabilities); een sleutel die management al heeft, registreert een volgende sleutel ermee in de body:

{ "name": "Platformautomatisering", "capabilities": { "management": {} } }

De capability heeft geen configuratie — een leeg object is het hele verhaal. Kijk met GET /key wat je eigen sleutel heeft. Zonder management antwoordt elke route op deze pagina 404, hetzelfde antwoord als een onbekend pad (Een 404 oplossen).

De routes komen in paren. Een pad zonder organisatie erin gaat over je eigen organisatie — GET /organisation, POST /keys, GET /admins — en een pad dat er een noemt gaat over een organisatie onder je: GET /organisations/{id}, POST /organisations/{id}/keys. Je eigen id voluit geschreven antwoordt hetzelfde als het korte pad.

Beheerrechten

Je sleutel beheert zijn eigen organisatie en elke organisatie eronder, en dat zijn twee verschillende taken. De organisaties die je te beheren kreeg, bestuur je; de organisatie die je zelf bent, kun je niet opschorten, verhangen of verwijderen.

Handeling Je eigen organisatie Een organisatie onder je
lezen (GET /organisation, GET /organisations/{id}) ja ja
name, timezone, data_retention_days, trace_retention_days, memory_retention_days, tracing_enabled, routing_config_id, assist_model, analysis_model, metadata ja; routing_config_id, assist_model en analysis_model tenzij het sleutelbeheer is voorbehouden ja
inheritable_keys (welke sleutels van metadata de organisaties eronder lezen) ja ja
computer_internet (of de computers ervan internet mogen bereiken) ja; 403 zolang een organisatie erboven het uit heeft staan ja; hetzelfde
alleen haar eigen document lezen (GET /organisation/metadata, GET /organisation/metadata/effective) ja -- en heeft geen management nodig —
haar geldende document lezen (GET /organisations/{id}/metadata/effective) — ja
status, permitted_capabilities, admin_restrictions, monthly_cost_cap_eur, monthly_cost_cap_until (het einde van deze maand, voor een limiet voor alleen deze maand), monthly_cost_cap_counts (branch of own: of de limiet de organisaties eronder meetelt), monthly_cost_cap_counts_erased (true of false: of een limiet die de organisaties eronder meetelt, blijft tellen wat een verwijderde organisatie deze maand verbruikte) 403 met het veld erbij ja; een plafond mag nooit verder gaan dan dat van de organisatie waar ze direct onder hangt
parent_id, audit_retention_days, legal_hold 403 met het veld erbij 403; alleen Artific, waar de organisatie ook hangt
er een organisatie onder aanmaken ja ja
verwijderen 403 ja
haar sleutels opvragen en lezen ja ja
haar sleutels registreren, bewerken, roteren en verwijderen ja, tenzij het sleutelbeheer is voorbehouden ja
haar beheerders opvragen ja ja
beheerders uitnodigen, opnieuw uitnodigen en verwijderen 403 ja
haar policy lezen en schrijven (GET/PUT/DELETE /organisation/policy, GET/PUT/DELETE /organisations/{id}/policy) ja; schrijven tenzij het sleutelbeheer is voorbehouden ja
de gepubliceerde versies van haar policy lezen (GET /organisation/policy/versions, GET /organisation/policy/versions/{version}, en hetzelfde onder /organisations/{id}/policy/versions) ja ja
een beleidsdocument testen voordat je het schrijft (POST /organisation/policy/decisions, POST /organisations/{id}/policy/decisions; Een beleidsdocument testen) ja, tenzij het sleutelbeheer is voorbehouden ja
haar labelwoordenlijst lezen en schrijven (GET/PUT/DELETE /organisation/label-vocabulary, GET/PUT/DELETE /organisations/{id}/label-vocabulary) lezen met elke sleutel; schrijven tenzij het sleutelbeheer is voorbehouden ja
haar identity providers lezen en schrijven (GET/PUT /organisation/subject-issuers) ja; schrijven tenzij het sleutelbeheer is voorbehouden —
haar huisstijlafbeeldingen opvragen, vullen en leegmaken (GET /organisation/assets, PUT/DELETE /organisation/assets/{slot}; Huisstijlafbeeldingen) ja —
haar toegestane hosts lezen en wijzigen (GET/POST /organisation/allowed-hosts, DELETE /organisation/allowed-hosts/{host}; Toegestane bestemmingen) ja; wijzigen geeft 403 als het sleutelbeheer is voorbehouden —
haar e-maildomeinen en inlogproviders lezen en schrijven (/organisation/email-domains, /organisation/sign-in-providers) ja (Een eigen inlogprovider instellen met een sleutel) —
haar levensloop lezen (GET /control-events, GET /organisations/{id}/control-events) ja ja

De regels over identity providers en toegestane hosts gaan over je eigen organisatie en niet over een onderliggende: de providers waarvan je een token mag meesturen (Bouwen voor personen) en de hosts die je eigen aanroepen bereiken. Wat een onderliggende organisatie daar heeft staan, lees je met een sleutel die je daar registreert. Haar beleidsdocument lees en schrijf je van bovenaf, op /organisations/{id}/policy: stuur een heel document, net als voor je eigen organisatie, en begin bij GET /organisation/policy/default. De hosts die een onderliggende organisatie mag bereiken, zijn de regels permit van de soort egress_host in dat document; de regels van dezelfde soort in je eigen document, en in de documenten boven je, begrenzen haar ook (Policy). Noem bij het schrijven de versie die je las in If-Match, zodat je geen wijziging overschrijft die iemand na je leesactie heeft opgeslagen (Sleutels uitgeven, roteren en intrekken).

Versies en concepten van de policy. Elke PUT en DELETE van een beleidsdocument wordt meteen gepubliceerd als de volgende genummerde versie, en een sleutel leest de gepubliceerde versies op /organisation/policy/versions. Een concept is van een persoon, alleen op de beheertweeling: POST /tenants/{id}/policy/versions met document en "draft": true schrijft er een, POST /tenants/{id}/policy/versions/{version}/publish zet het in werking en DELETE /tenants/{id}/policy/versions/{version} gooit het weg; het schrijven en het publiceren noemen de version van het gepubliceerde document in If-Match. POST /tenants/{id}/policy/versions/{version}/decisions test een versie met een sleutel van de organisatie en hoogstens 50 vragen, beantwoord onder die versie en onder het gepubliceerde document; het beslist niets echt en schrijft niets. Geen sleutel leest een concept, en geen verzoek wordt eronder beoordeeld (Policy). Een sleutel test in plaats daarvan een document dat hij zelf meestuurt (Een beleidsdocument testen voordat je het schrijft).

Een weigering op zo'n veld is een problem-document dat het veld noemt, bijvoorbeeld Only an operator or a parent organisation's administrator may change 'status'. Er wordt nooit een deel toegepast: een body met één veld dat je niet mag zetten, verandert er geen enkel.

Alles buiten je bereik antwoordt 404. Een organisatie die niet van jou is en niet onder je hangt, is niet te onderscheiden van een organisatie die niet bestaat, op welke route je haar ook noemt. Zo valt de boom niet af te lopen op zoek naar id's.

Het verbruik, het auditspoor en de routeconfiguraties van een organisatie onder je bereik je met dezelfde sleutel: zie Verbruik, audit en routeconfiguraties van een organisatie lezen. De prijslijst van de installatie niet: die is van Artific, en je organisatie leest een prijs en schrijft er nooit een.

Je eigen organisatie lezen en hernoemen

curl -s "$CORE_URL/organisation" -H "Authorization: Bearer $CORE_KEY"

Het antwoord is de organisatie zoals de console haar toont: haar id en naam, waar ze hangt (parent_id), hoe ze gescheiden wordt gehouden (isolation) en op welke database ze staat (database_ref), de capabilities die ze toestaat, haar limieten, haar bewaartermijnen en wat de organisatie erboven zich heeft voorbehouden.

PATCH /organisation wijzigt de velden uit de tabel hierboven die je eigen organisatie mag zetten:

curl -s -X PATCH "$CORE_URL/organisation" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"name": "Noordwind Handel", "data_retention_days": 90}'

data_retention_days is de termijn over de eigen inhoud van je organisatie — gesprekken, antwoorden, bestanden, artefacten en geheugenregels — en null bewaart die voorgoed (Gegevensbewaring). Het is de beslissing van je organisatie over haar eigen gegevens, en daarom mag je sleutel hem zetten terwijl hij de termijn over het auditspoor niet mag aanraken. routing_config_id noemt een van de fallback-configuraties van je eigen organisatie, of null voor geen (Terugvallen op een ander model).

tracing_enabled zegt of Core vastlegt wat het voor je organisatie naar een model stuurt, en trace_retention_days geeft traces een eigen, kortere termijn dan data_retention_days (Traces). memory_retention_days geeft geheugenrecords een eigen termijn, langer of korter. Daaronder telt een record vanaf zijn laatste schrijf- of bevestigingsmoment, dus een feit dat je blijft bevestigen blijft staan en een open toezegging wordt nooit verwijderd; null laat geheugen onder data_retention_days, geteld vanaf het aanmaken van elk record. assist_model noemt het model achter Beschrijf wat je nodig hebt, of null voor de standaard van de installatie (Hulpmodel). Hetzelfde model beantwoordt elke beurt van je eigen helper die geen model noemt. analysis_model noemt het model van verbeteranalyses, met dezelfde regels, en valt nooit terug op assist_model (Het analysemodel).

computer_internet zegt of de computers van je organisatie internet mogen bereiken (Computers). Een nieuwe organisatie onder de jouwe begint met de schakelaar aan als die voor de jouwe telt, en anders uit; stuur "computer_internet": false bij het aanmaken om haar toch uit te laten beginnen. Daarna beslist je organisatie het zelf: je sleutel mag het aanzetten, en iedereen boven je ook. Het telt alleen zolang elke organisatie boven de jouwe het ook aan heeft staan, dus zet je het bij jou uit, dan staat het uit voor elke organisatie onder je, en aanzetten onder een organisatie die het uit heeft staan is 403. Uitzetten beëindigt niet wat al draait: een computer houdt zijn open netwerk tot zijn levensduur voorbij is, de tunnels die hij via de proxy van Core open heeft sluiten bij de volgende controle van de proxy (ongeveer elke 30 seconden), en DELETE /computers/{computer_id} beëindigt hem eerder. Het antwoord bevat computer_internet_in_force, of het op dit moment telt, en computer_internet_available, of deze omgeving zulke computers überhaupt aanbiedt; zolang dat false is, wordt de schakelaar bewaard en doet hij niets.

curl -s -X PATCH "$CORE_URL/organisation" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"computer_internet": true}'

Zet het alleen aan als je accepteert wat het betekent: code die een model of script daar draait, kan wat het meekreeg, bestanden inbegrepen, naar elke host op internet sturen.

timezone is een IANA-zonenaam zoals Europe/Amsterdam, waarin de ingebouwde datum- en tijdwaarden van je prompts worden gelezen als een verzoek of planning geen eigen zone noemt (Tijdzone). Een nieuwe organisatie staat op UTC; stuur "UTC" om daarheen terug te gaan, want null laat de zone staan. Een zone die Core niet kent, is 422 op timezone.

metadata is een eigen ondoorzichtig object, maximaal 16 KiB geserialiseerd -- Core bewaart het, geeft het terug en leest het nooit. Hier bewaart een platform gebouwd op Core zijn eigen huisstijl: kleuren, tekst, wat het nodig heeft; de afbeeldingen gaan naar Huisstijlafbeeldingen. Laat het veld weg om te houden wat er staat, en stuur {} om het te wissen.

Elke lezing van je organisatie geeft er metadata_version naast. Stuur die terug in If-Match bij de PATCH, en Core weigert de hele wijziging met 409 (reason_code: "stale_version") als iemand metadata na jouw lezing heeft veranderd, zonder iets te schrijven; lees opnieuw en herhaal de wijziging. Zonder de header vervangt de PATCH wat er staat:

curl -s -X PATCH "$CORE_URL/organisation" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -H 'If-Match: "3f9a..."' \
    -d '{"metadata": {"theme": {"primary": "#0a5"}}}'

Er gaat niets van naar beneden tenzij je dat zegt. inheritable_keys noemt de sleutels op het hoogste niveau van je metadata die de organisaties onder de jouwe als hun eigen mogen lezen -- hoogstens 32, elk één keer, als geheel vervangen, [] voor geen. Een sleutel die je niet noemt blijft van jou alleen:

curl -s -X PATCH "$CORE_URL/organisation" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"metadata": {"theme": {"primary": "#0a5"}, "billing_note": "intern"},
         "inheritable_keys": ["theme"]}'

GET /organisation/metadata/effective geeft in één aanroep het geldende document van een organisatie: haar eigen sleutels, plus elke sleutel die ze mist van de dichtstbijzijnde organisatie boven haar die die sleutel noemt én heeft. Een organisatie erboven die een sleutel noemt maar niet heeft, wordt overgeslagen, en de organisatie daarboven wordt gevraagd.

{"metadata": {"logo_text": "Northwind", "theme": {"primary": "#0a5"}},
 "inherited_from": {"theme": null}}

inherited_from noemt voor elke sleutel die van boven kwam de organisatie waar hij vandaan kwam -- of null als je credential die organisatie niet bereikt. Een sleutel van je eigen organisatie bereikt nooit wat erboven ligt, dus op deze route is elke waarde null; lees GET /organisations/{id}/metadata/effective met een sleutel hoger in de boom om de id's te zien. Een geschorste organisatie erboven leent haar sleutels nog steeds uit; een verwijderde leent niets uit, en wat erboven ligt ook niet.

Anders dan al het andere in dit hoofdstuk hebben GET /organisation/metadata en GET /organisation/metadata/effective geen management nodig: elke sleutel van je organisatie leest ze, en een subject-token dat tot één persoon is versmald ook, zodat een browsergerichte frontend zijn eigen huisstijl kan lezen met welke credential hij ook heeft gekregen (Bouwen voor mensen).

Een organisatie onder je aanmaken

De naam van een organisatie is uniek binnen de organisaties onder dezelfde bovenliggende; aanmaken, hernoemen of verplaatsen naar een naam die een zusterorganisatie al heeft, wordt met 409 geweigerd. Dubbele namen die al bestaan blijven staan totdat iemand ze hernoemt.

POST /organisations noemt de bovenliggende organisatie, en dat is elke organisatie die je al bereikt — je eigen, of een die verder naar beneden hangt:

curl -s -X POST "$CORE_URL/organisations" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"name": "Noordwind Retail", "parent_id": "ten_4Kd9...",
         "permitted_capabilities": ["threads", "knowledge", "management"]}'
Veld Wat je stuurt
name hoe de organisatie heet; het enige verplichte veld
parent_id waaronder ze komt te hangen. Laat je het weg of stuur je null, dan vraag je om een organisatie die onder niets hangt, en die maakt alleen Artific aan (403)
permitted_capabilities het plafond voor elke sleutel die deze organisatie krijgt. Het moet binnen het plafond blijven van de organisatie die je als bovenliggende noemde; de eerste naam erbuiten wordt geweigerd met '<name>' is outside the capability ceiling of the organisation this one sits under.
isolation shared, tenzij je iets anders is verteld. schema geeft de nieuwe organisatie een eigen databaseschema en -rol, en die keuze is van jou, wat je eigen organisatie ook gebruikt
database_ref stuur het niet. Een database noemen is alleen aan een operator, en een sleutel is nooit een operator; de nieuwe organisatie staat standaard op dezelfde fysieke database als de organisatie die je als bovenliggende noemde. Het veld meesturen wordt geweigerd met 403, ook als het diezelfde database noemt

Het antwoord is 201 met de nieuwe organisatie en een Location-header. Haar status is provisioning zolang Core haar inricht; vraag GET /organisations/{id} op tot er active staat. Je kunt er meteen sleutels in registreren; ze beantwoorden aanroepen zodra ze actief is (wat de statussen betekenen). Tot die tijd antwoordt een aanroep met een van haar sleutels 409 problem+json met de titel Conflict en de detail The tenant is not ready., zonder header Retry-After (de modelroutes geven dezelfde melding in het foutobject van de leverancier): probeer het even later opnieuw.

Wat je zojuist hebt aangemaakt, beheer je vanaf het moment dat deze aanroep antwoordt.

status is altijd die van de organisatie zelf. Een organisatie opschorten legt ook elke organisatie eronder stil, en zo'n organisatie houdt status: "active": lees suspended_by_ancestor ernaast. true betekent dat een organisatie erboven is opgeschort, zodat haar sleutels 403 antwoorden en haar runs, schema's en kenniswerk stilliggen tot die organisatie weer geactiveerd is (Opschorten). Het veld zegt niet welke organisatie het is.

permitted_capabilities is op dezelfde manier het eigen opgeslagen plafond van de organisatie, de waarde die jij schrijft. Een versmalling erboven verandert het niet: lees capabilities_in_force ernaast, de capabilities die haar sleutels en beheerders nu mogen gebruiken, zonder wat een organisatie erboven niet meer toestaat, pseudonymisation en trust_envelope inbegrepen. Het veld is alleen te lezen en zegt niet welke organisatie een capability tegenhoudt.

DELETE /organisations/{id} wist een organisatie onder je en alles wat ze bezit, op de achtergrond, met status op deleting tot ze weg is. Zolang ze wordt verwijderd, antwoorden haar sleutels dezelfde 409; is ze weg, dan zijn haar sleutels mee verdwenen en antwoorden ze 401. Een organisatie waar nog levende organisaties onder hangen wordt geweigerd met 409: wissen werkt nooit door naar beneden, dus die wis je eerst. Je eigen organisatie levert 403 op — een organisatie wist zichzelf niet.

Werken in een organisatie onder je

Deze routes beheren organisaties, nooit hun inhoud. Wil je de kennis van een onderliggende organisatie lezen, er een prompt opslaan of namens haar een beurt versturen, registreer dan een sleutel in die organisatie en roep aan als die sleutel:

curl -s -X POST "$CORE_URL/organisations/ten_9Fb2.../keys" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"name": "Retail-ingest", "capabilities": {"knowledge": {}}}'

De secret staat in dat antwoord en nergens anders. Een sleutel van de onderliggende organisatie bereikt haar eigen gegevens en niets daarboven: GET /key noemt die organisatie, en vraag je die sleutel naar de organisatie die hem aanmaakte, dan is het antwoord 404. Geef je hem ook management, dan beheert hij die organisatie en alles wat er wéér onder hangt; zo zakt een applicatie een niveau dieper.

Sleutels uitgeven, roteren en intrekken

/keys gaat over je eigen organisatie en /organisations/{id}/keys over een organisatie eronder. Op allebei zitten dezelfde zeven handelingen: opvragen, registreren, lezen, bewerken, roteren, het callbackgeheim aanmaken of vervangen (POST …/keys/{key_id}/callback-secret) en verwijderen.

curl -s -X POST "$CORE_URL/keys" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"name": "Afrekenservice", "capabilities": {"threads": {}}, "monthly_cost_cap_eur": "250.00"}'
  • De secret verschijnt één keer. secret staat in het antwoord op de registratie en op de rotatie en in geen enkel ander antwoord; Core bewaart de hash en de twaalf tekens van secret_prefix. Raak je hem kwijt, roteer dan.
  • Wat je mag aanzetten is het plafond van je organisatie, management inbegrepen. Een capability daarbuiten antwoordt 409 met capability '<name>' is not permitted for this tenant, en een capability waarvan de afhankelijkheid er niet naast aanstaat ook: memory vraagt knowledge, runs vraagt threads, artifacts vraagt files, evaluations vraagt agents.
  • Roteren beëindigt de vorige secret meteen. POST /keys/{key_id}/rotate antwoordt met de nieuwe; er is geen periode waarin allebei werken. De aanroepende sleutel mag zichzelf roteren, en daarmee beëindigt hij de sleutel die de aanroep deed: het antwoord is het laatste wat de oude secret oplevert.
  • Je mag de secret zelf meebrengen. Registratie en rotatie nemen een optionele secret in de body ({"secret": "ack_..."} op de rotatieroute), met precies de vorm die Core zelf uitgeeft: ack_ en 43 URL-veilige base64-tekens, 32 willekeurige bytes uit een veilige generator. Een andere vorm antwoordt 422 op secret, en een secret die een sleutel al heeft ook. Het antwoord bevat hem één keer, net als een gegenereerde; laat het veld weg en Core genereert hem. Terraform gebruikt dit, zodat de secret van een sleutel nooit in zijn state belandt.
  • PATCH /keys/{key_id} versmalt een sleutel, ook zichzelf — zo neemt een applicatie zichzelf capabilities af voordat ze een beurt iets riskants aanbiedt. Zijn eigen maandlimiet is de uitzondering: monthly_cost_cap_eur voor de aanroepende sleutel zelf antwoordt 403 met het veld erbij, ook met null, want een sleutel bepaalt nooit zijn eigen plafond. Een andere sleutel met management, of een beheerder in de console, stelt hem in.
  • Een schrijfactie op een policy kan de versie noemen die ze las. Een beleidsdocument wordt in zijn geheel vervangen, dus van twee schrijvers die het allebei lazen en allebei schrijven, verwijdert de tweede ongemerkt de wijziging van de eerste. Elke leesactie van een sleutel antwoordt policy_version, en elke leesactie van de policy van een organisatie antwoordt version; ook een organisatie zonder document heeft een version. Stuur die waarde mee in de header If-Match bij het schrijven (PATCH van een sleutel, PUT of DELETE van de policy van een organisatie) en Core weigert met 409 als het opgeslagen document intussen is gewijzigd. Er wordt niets geschreven: lees opnieuw en herhaal je wijziging. Zonder de header vervangt de schrijfactie wat er staat.
    • De body van die weigering draagt reason_code: "stale_version". Schakel op dat veld en niet op de status: dezelfde routes antwoorden ook om andere redenen 409, en die bodies dragen geen reason_code.
    • Een PATCH van een sleutel met If-Match wordt in zijn geheel geweigerd als het document van de sleutel is gewijzigd, ook als de body geen policy bevat.
    • If-Match: * komt overeen met wat er ook is opgeslagen.
    • Een toegestane host toevoegen of verwijderen wijzigt de version van de organisatie: de lijst met hosts is een deel van hetzelfde document.
  • expires_at laat een sleutel verlopen zonder haar te verwijderen. Zet hem bij registratie of via een PATCH, altijd in de toekomst — een moment in het verleden antwoordt 422. Stuur null op een PATCH om een bestaande vervaldatum op te heffen; laat je het veld weg, dan blijft staan wat de sleutel al had. Voorbij dat moment wordt de sleutel geweigerd zoals een onbekende sleutel — 401, hetzelfde antwoord — of ze nu als secret komt of als subject-token dat erop is gemunt. Roteren zet dit nooit terug: roteren vervangt de secret, niet de levensduur.
  • Een sleutel overleeft nooit de sleutel die haar registreert of bewerkt. Zodra je eigen sleutel een expires_at draagt, moet elke sleutel die ze aanmaakt of bewerkt — zichzelf inbegrepen — er ook een dragen, nooit later dan die van haarzelf: je mag de vervaldatum van je eigen sleutel verkorten, of er een zetten waar er nog geen was, maar niet verlengen of opheffen, en dat geldt net zo goed voor elke sleutel die je uitgeeft. Een ruimere waarde versturen antwoordt 403, niet 422 — de waarde is niet onjuist, een operator of beheerder mag dezelfde waarde zetten zonder geweigerd te worden. Een sleutel zonder eigen vervaldatum legt niemand een plafond op.
  • DELETE stopt verdere aanroepen met die sleutel en beëindigt meteen elk subject-token dat ermee is gemaakt (Een token voor de browser). Wat ermee is aangemaakt, blijft van de organisatie.

De console toont en doet hetzelfde (Sleutels); een sleutel die je vanuit de API registreert, is daar een gewone rij.

Verbruik, audit en routeconfiguraties van een organisatie lezen

Zeven routes onder /organisations/{id}/... beantwoorden voor een organisatie precies wat /tenants/{id}/... voor een beheerder beantwoordt: GET .../usage, .../usage/summary, .../usage/caps, .../usage/by-subject, .../usage/by-key, .../audit/events en .../audit/events/export. Op .../usage/by-key na nemen ze dezelfde parameters (key_id, from, to, interval=day op de samenvatting; limit op de by-subject-lezing) en antwoorden ze dezelfde vorm als de routes in Gebruik en audit via de API. Er is geen los pad voor je eigen organisatie: /organisations/{je eigen id}/usage werkt net zo goed, want je sleutel bereikt haar eigen organisatie als een van haar eigen wortels (Beheerrechten).

GET .../usage/by-key heeft geen tegenhanger onder /usage: het zegt per sleutel wat die kostte. Het neemt alleen from en to; noem je geen van beide, dan is het venster de lopende kalendermaand in UTC. items heeft één regel per sleutel met verbruik in het venster, met key_id, rows, cost_eur en unpriced, gesorteerd op kosten aflopend; een sleutel die niets verbruikte, staat er niet in. keyless is dezelfde regel voor wat zonder sleutel binnenkwam, zoals wat iemand vanuit de console startte, met key_id null, en staat er altijd, op nul als er niets was. cost_eur bovenaan is het totaal van alle regels samen. Een unpriced boven nul betekent dat het bedrag een ondergrens is.

curl -s "$CORE_URL/organisations/ten_9Fb2.../usage/summary?interval=day&from=2026-09-01T00:00:00Z&to=2026-10-01T00:00:00Z" \
    -H "Authorization: Bearer $CORE_KEY"

Routeconfiguraties werken op dezelfde twee paden: /routing-configs is je eigen organisatie, /organisations/{id}/routing-configs een organisatie eronder — opvragen, aanmaken, lezen, bewerken en verwijderen, en de gepubliceerde versies van een configuratie opvragen en lezen (.../{config_id}/versions), zoals /tenants/{id}/routing-configs een beheerder biedt, waar aanmaken en bewerken ook draft: true nemen en alleen de beheerder een concept publiceert of weggooit (Terugvallen op een ander model):

curl -s -X POST "$CORE_URL/organisations/ten_9Fb2.../routing-configs" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"name": "default", "chains": [{"primary": "gpt-5.6-sol", "fallbacks": ["claude-sonnet-5"]}]}'

Een beleidsdocument testen voordat je het schrijft

Vraag, voordat je een beleidsdocument met PUT schrijft, hoe het zou beslissen. Stuur het document dat je wilt schrijven mee, het id van een sleutel van die organisatie en hoogstens 50 vragen: een soort, een naam, een action waar de soort er een kent, de labels die het ding draagt en, voor een persoon, haar attributes. Het document is een heel document, net als bij een PUT: een soort waarover het geen regel schrijft, wordt geweigerd, dus begin bij GET /organisation/policy/default. Het voorbeeld hieronder is om die reden ingekort.

curl -s -X POST "$CORE_URL/organisation/policy/decisions" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"key_id": "key_01K...",
         "document": {"rules": [
             {"effect": "permit", "kind": "prompt", "match": {"names": ["private-*"]}},
             {"effect": "permit", "kind": "egress_host", "match": {"names": ["api.jansen.nl"]}}]},
         "questions": [{"kind": "prompt", "name": "private-welcome", "action": "use"},
                       {"kind": "egress_host", "name": "api.jansen.nl"}]}'

Elk antwoord zegt onder draft wat het meegestuurde document zou beslissen, en onder published wat het document dat nu geldt beslist, met allowed, de reason, welk document besliste (organisation, key, ancestor of default) en de rule. Een host wordt beantwoord zoals een uitgaande aanroep hem zou beoordelen, en niemand belt hem. Voor een organisatie onder je gaat dezelfde body naar POST /organisations/{id}/policy/decisions, met een sleutel van die organisatie als key_id.

De test weigert wat de PUT zou weigeren, met hetzelfde antwoord: een label buiten de woordenlijst van de organisatie is 422 met hetzelfde veld, net als een live check die een beoordelingsmodel noemt dat de organisatie niet kan gebruiken, en voorbehouden sleutelbeheer is dezelfde 403. Hij schrijft niets: geen versie, geen gebeurtenis, geen concept. Een opgeslagen concept is van een persoon en alleen de beheerconsole test er een.

Voorbehouden sleutelbeheer

De organisatie boven je kan het sleutelbeheer aan zichzelf voorbehouden (Voorbehouden). Dat bindt je sleutel net zo goed als je beheerders: registreren, bewerken, roteren en verwijderen op je eigen organisatie antwoorden 403 met This organisation's parent has reserved key management to itself., en het opvragen en lezen van je sleutels blijft gewoon van jou. routing_config_id zetten wordt om dezelfde reden geweigerd, en zo ook elke schrijfactie op /routing-configs van je eigen organisatie: dat is allemaal sleutelconfiguratie.

Onder je wordt niets ingehouden, want daar bén jij de organisatie erboven.

Verbruikscijfers of het auditspoor voorbehouden bindt je sleutel op dezelfde manier: GET /organisations/{je eigen id}/usage en de andere vijf antwoorden 403 zodra je eigen organisatie het is voorbehouden, en gewoon 200 voor een organisatie eronder. GET /usage, GET /audit/events en GET /key — je sleutel die naar haar eigen gegevens vraagt zonder een organisatie te noemen — raakt een voorbehoud nooit.

Wie een organisatie beheert

GET /admins geeft de mensen die je eigen organisatie beheren, met hun status. Meer biedt je eigen organisatie niet: wie haar beheert, wordt boven haar beslist, dus een sleutel kan niemand uitnodigen in de organisatie die hem heeft uitgegeven.

Onder je is de hele set van jou:

Aanroep Wat hij doet
GET /organisations/{id}/admins wie die organisatie beheert. Een lege pagina is een echt antwoord
POST /organisations/{id}/admins iemand uitnodigen op e-mailadres. created zegt of er een account is gemaakt, invite_sent of de mail eruit ging
POST /organisations/{id}/admins/{user_id}/invite de uitnodiging opnieuw versturen, en herstellen wat er halverwege bleef steken
DELETE /organisations/{id}/admins/{user_id} die persoon het beheer over die organisatie afnemen. Het account zelf blijft ongemoeid

Iemand met active: false heeft nog niets geaccepteerd of is halverwege gestrand; de uitnodiging opnieuw versturen is de reparatie (Beheerders).

Beheergebeurtenissen

GET /control-events geeft wat er met je eigen organisatie is gebeurd: haar eigen aanmaak, haar hernoemingen en gewijzigde bewaartermijnen, haar sleutels die zijn geregistreerd, gewijzigd, geroteerd en verwijderd, haar beheerders die zijn uitgenodigd en verwijderd, en haar exports die zijn aangevraagd en opgehaald. Elke gebeurtenis draagt event, created_at, request_id en actor — het id van je eigen sleutel als een sleutel het deed, of operator:… als een persoon het deed.

Een gebeurtenis over een organisatie onder je hoort bij háár spoor en niet bij het jouwe: de organisatie die je aanmaakte, vindt haar eigen aanmaak op haar eigen spoor. Dat spoor lees je van bovenaf met GET /organisations/{id}/control-events, met dezelfde limit, cursor en order en hetzelfde antwoord; een organisatie buiten je bereik is 404.

Een organisatie erboven die je auditspoor heeft voorbehouden, heeft dit niet voorbehouden: haar eigen levensloop is het ene overzicht dat een organisatie altijd houdt. Dezelfde gebeurtenissen zijn de Recente gebeurtenissen van de console en haar beheerauditgeschiedenis (Audit).

Huisstijlafbeeldingen

Je organisatie kan tot 250 afbeeldingen in Core bewaren — een logo, een favicon, een afbeelding voor een inlogpagina, een licht en een donker logo voor elk thema van een ingebedde assistent — en ze overal laten zien, ook aan mensen die geen sleutel hebben. Elke afbeelding staat in een slot, een naam die je zelf kiest: een kleine letter, dan tot 31 kleine letters, cijfers of streepjes (logo, favicon, sign-in). Core leest de naam nooit.

Zet een afbeelding in een slot door het bestand zelf als body te sturen. Core accepteert PNG, JPEG en WebP tot 512 KiB, en bepaalt het type aan de bytes van het bestand, niet aan de Content-Type die je meestuurt. SVG wordt niet geaccepteerd.

curl -s -X PUT "$CORE_URL/organisation/assets/logo" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: image/png" \
    --data-binary @logo.png
{"object": "organisation_asset", "slot": "logo", "content_type": "image/png",
 "size_bytes": 18342, "sha256": "9f2c...e41a", "url": "/api/v1/assets/9f2c...e41a",
 "created_at": "2026-10-17T09:12:00Z", "updated_at": "2026-10-17T09:12:00Z"}

Het antwoord is 200, of het slot nu leeg was of al een afbeelding had, die de nieuwe dan vervangt. GET /organisation/assets geeft elk slot met dezelfde velden en zonder de bytes, en DELETE /organisation/assets/{slot} maakt er een leeg; dat antwoordt 204, ook als het slot al leeg was.

Antwoord Betekenis Wat je doet
413 de afbeelding is groter dan 512 KiB maak hem kleiner
415 de bytes zijn geen PNG, JPEG of WebP zet het bestand om; een SVG moet je eerst als PNG of WebP opslaan
422 de body is leeg, of de slotnaam past niet in het patroon stuur het bestand mee, of gebruik DELETE om een slot leeg te maken
409 de organisatie vult al 250 slots, of wordt nog aangemaakt of verwijderd maak eerst een slot leeg, of wacht tot de organisatie klaar is

Het openbare adres

url is waar iedereen de afbeelding leest, zonder sleutel en zonder token. Zet je Core-host ervoor, zonder de /api/v1 uit CORE_URL, want die zit al in url: https://core.example.com/api/v1/assets/9f2c...e41a. Dat adres gebruik je zo in een <img> op een inlogpagina, in een mail of in je eigen frontend, op elk domein.

Het adres is genoemd naar de inhoud van de afbeelding, niet naar je organisatie, dus het zegt niets over wie hem uploadde. Daardoor verandert het ook zodra de afbeelding verandert: vervang je het logo, lees dan de nieuwe url en gebruik die. Browsers en andere caches mogen een afbeelding een jaar bewaren (Cache-Control: public, max-age=31536000, immutable), dus gebruik nooit een oud adres voor een nieuwe afbeelding; een nieuwe afbeelding krijgt altijd een nieuw adres. Een lezer die de ETag die hij kreeg in If-None-Match meestuurt, krijgt 304 en geen bytes.

Een adres antwoordt 404 zodra geen organisatie die afbeelding nog heeft: nadat je het slot leeg hebt gemaakt of de afbeelding hebt vervangen, tenzij een andere organisatie toevallig hetzelfde bestand bewaart.

Elke afbeelding is van je eigen organisatie: een organisatie onder de jouwe erft er geen en vult haar eigen slots met een eigen sleutel (Werken in een organisatie onder je). Een slot vullen en leegmaken staan in je beheergebeurtenissen als tenant.asset_set en tenant.asset_deleted. Het archief dat je meeneemt bevat de afbeeldingen, en de slots opvragen en leegmaken zijn tools op het MCP-endpoint (list_own_assets, delete_own_asset); een slot vullen niet, want de afbeelding is bytes en geen toolargument. Een subject-token krijgt 403 op deze routes.

Verwijderen haalt kopieën elders niet terug. Als je een slot leegmaakt, of je organisatie wordt verwijderd, haalt Core zijn eigen kopie weg en antwoordt het adres niet meer. Een browser, een maildienst of een CDN die de afbeelding al ophaalde, kan zijn eigen kopie nog tot een jaar laten zien, en Core kan daar niet bij. Zet in een huisstijlafbeelding niets wat je misschien snel moet kunnen intrekken.

Je gegevens meenemen

Een organisatie die vertrekt, neemt een archief mee van alles wat Core voor haar bewaart. Vraag het aan, wacht tot het klaar is en haal het op:

curl -X POST "$CORE/api/v1/exports" -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" -d '{}'
# 202, met Location: /api/v1/exports/exp_…
curl "$CORE/api/v1/exports/exp_…" -H "Authorization: Bearer $KEY"
# status: queued, running, dan ready
curl -o archief.zip "$CORE/api/v1/exports/exp_…/download" -H "Authorization: Bearer $KEY"

Het archief is een zip. manifest.json noemt het formaat en de versie, het aantal rijen per bestand en wat er bewust niet in zit; organisation.json zijn de instellingen en het beleidsdocument van je organisatie; tables/ heeft per soort gegeven één JSON Lines-bestand — gesprekken met hun berichten, antwoorden, bestanden en artifacts, kennis, geheugen, prompts, skills, agents, tools, evaluaties, schema's, het auditspoor en het verbruik — en blobs/ de bytes van elk bestand. Je huisstijlafbeeldingen zitten er ook in, hun gegevens onder control/ en de afbeeldingen zelf onder files/. Geheimen zitten er nooit in, versleuteld of niet: de referenties van je connectors en MCP-servers moet je bij je nieuwe leverancier opnieuw invoeren.

Stuur {"subject": "anna"} mee en het archief bevat alleen wat voor die ene persoon is geschreven: zo beantwoord je een inzage- of overdrachtsverzoek (AVG artikel 15 en 20). Onder je werkt hetzelfde op /organisations/{id}/exports. Een subject-token krijgt 403 op al deze aanroepen: het archief vraag je aan vanuit de backend die de sleutel heeft.

Het archief is export_retention_hours lang op te halen, standaard 72 uur, en zo vaak als nodig; daarna antwoordt de download 409 en vraag je een nieuw aan. Elke download is een gebeurtenis tenant.export_downloaded, met wie hem ophaalde en hoeveel traces het archief bevatte, in de beheergebeurtenissen van je organisatie (GET /control-events). Heeft de organisatie boven je het auditspoor of het verbruik voorbehouden, dan laat jouw archief die rijen weg en zegt dat in withheld.

Een download die werd afgebroken, gaat verder waar hij stopte: stuur Range: bytes=<n>-, waarbij <n> het aantal bytes is dat je al hebt (curl -C - -o archief.zip ... doet dat), en het antwoord is 206 met de rest. Elk hervat verzoek is een download op zich, met een eigen gebeurtenis tenant.export_downloaded. Een bereik dat op of voorbij het einde van het archief begint, krijgt 416 en wordt niet vastgelegd; curl -C - op een bestand dat al volledig binnen is, krijgt dat antwoord.

Aanvragen, opvragen en de lijst zijn ook tools op het MCP-endpoint (create_own_export, get_own_export, list_own_exports); de download niet, want een archief is bytes en geen toolresultaat.

Beheren via MCP

Elke handeling op deze pagina is ook een tool op het sleutelendpoint, zonder dat je iets instelt (Core gebruiken vanuit een MCP-client). De tool heet naar de handeling: get_own_organisation, update_own_organisation, create_organisation, register_organisation_key, rotate_own_organisation_key, invite_organisation_admin, list_organisation_control_events. Een sleutel zonder management krijgt er geen enkele van te zien.

Binnen een beurt die op Core draait, zijn het dezelfde tools met het voorvoegsel core__ — core__create_organisation, core__register_organisation_key — zodat een agent een organisatie in één beurt kan inrichten (Een agent het platform geven). Een registratie antwoordt met de nieuwe secret, en een toolresultaat hoort bij de beurt: de secret komt langs het model en blijft bewaard bij wat de beurt heeft opgeslagen. Registreer sleutels vanuit je eigen applicatie als dat uitmaakt.

Geef management aan weinig sleutels

Een sleutel met management geeft weg wat een beheerder weggeeft: elke capability binnen het plafond van je organisatie, management daarbij. Zo'n sleutel kan dus een tweede sleutel registreren die evenveel beheert als hij. Die tweede sleutel is een gewone rij in de console en zijn registratie staat in het spoor, dus er blijft niets verborgen — maar wie de eerste secret heeft, beslist erover. Behandel die secret zoals je het wachtwoord van een beheerder behandelt.

In de praktijk betekent dat: één sleutel voor het beheer in plaats van management erbij op een sleutel die ook beurten bedient, een maandlimiet erop zoals op elke andere sleutel, en roteren zodra hij ergens is geweest waar hij niet hoorde. Elke handeling ervan staat in het spoor hierboven, onder zijn eigen sleutel-id.