ISMS Copilot Docs

Multi-tenant integration for platforms

Architecture for compliance platforms that serve many customers through the ISMS Copilot API: keys by customer country, pins on every request, and tenant isolation that stays in your platform.

This guide is for platforms that put the ISMS Copilot API behind their own product and serve many customers with it. It covers where keys live, how to pin each customer's frameworks, and what your platform keeps responsible for. The request-level details (catalog ids, logging, the changelog) are in Serve customers in many countries.

What the API does and does not hold

The API is stateless per request. It keeps no conversation history, no per-customer memory and no tenant object. Each request is answered from what is in that request: your messages, the framework modules you pin, and the server prompt. Prompts and answers are not stored as records (Zero Data Retention).

That gives you two properties to build on:

  • One customer's request never sees another customer's data or modules, unless your platform puts them in the same request.
  • Tenant isolation is therefore your platform's job, the way it already is for your database. The API has no tenant sub-accounts, no per-key default pins and no webhooks today.

The shape

Customer A (Netherlands) ─┐
Customer B (Netherlands) ─┼─► your backend ── key "NL"  ── pins NIS_2_NL + tenant frameworks ─┐
Customer C (Italy)       ─┼─► your backend ── key "IT"  ── pins NIS_2_IT + tenant frameworks ─┼─► api.ismscopilot.com/v1
Customer D (Belgium)     ─┘─► your backend ── key "BE"  ── pins NIS_2_BE + tenant frameworks ─┘
  1. Keys live on your server. Never ship an sk-isms- key to a browser or a customer's machine. Store keys in your secret manager.
  2. One key per customer country. Group customers by the country whose law they are under, and give each group its own key. That gives you usage and spend per country in the console, a spend cap per country, and a smaller blast radius if a key leaks. A user can hold up to 20 active keys; if you serve more countries than that, group the smaller ones on a shared key. When you need per-customer spend for a large customer, give that customer its own key.
  3. Pins on every request, from your tenant record. Build ismscopilot.frameworks server-side from the customer's stored country and frameworks. Never take the pin list from end-user input.
  4. Region per customer. For customers who need AI processing in the EU, use the -eu aliases (isms-fast-eu, isms-thinking-eu). The other aliases, including isms-mini, use the global path. See Models and regions.
import os
from openai import OpenAI

KEYS = {"NL": os.environ["ISMS_API_KEY_NL"], "IT": os.environ["ISMS_API_KEY_IT"], "BE": os.environ["ISMS_API_KEY_BE"]}
NIS2 = {"NL": "NIS_2_NL", "IT": "NIS_2_IT", "BE": "NIS_2_BE"}
clients = {c: OpenAI(base_url="https://api.ismscopilot.com/v1", api_key=k) for c, k in KEYS.items()}

def answer(tenant, messages):
    frameworks = [NIS2[tenant.country], *tenant.frameworks][:8]   # from your tenant record
    model = "isms-fast-eu" if tenant.eu_processing else "isms-fast"
    raw = clients[tenant.country].chat.completions.with_raw_response.create(
        model=model,
        messages=messages,  # this tenant's conversation only
        extra_body={"ismscopilot": {"system": "none", "frameworks": frameworks}},
    )
    completion = raw.parse()
    record(tenant.id, frameworks=raw.headers.get("x-isms-frameworks"), usage=completion.usage)
    return completion.choices[0].message.content

record is your own audit log: the module ids on each answer are your evidence of which law it stood on. We do not store which modules were injected.

Your side of tenant isolation

RiskWhat your platform does
Another tenant's text in the promptAssemble conversation history per tenant and per conversation. Never mix histories, shared examples or cached snippets from one customer into another's request.
A cache that crosses tenantsIf you cache answers, key the cache by tenant and by the pinned module list, and keep it short-lived so a module update is not hidden behind an old answer.
Wrong law for a tenantMap country to catalog ids in code, from the live catalog (GET /v1/frameworks). An unknown id fails with framework_not_found, so a typo is loud.
Leaked keyPer-key spend caps (hour, day, week, month) and revocation in the console. One key per country limits what one leak can spend.
Your own logsIf you log prompts or answers, that store is yours and inside your own DPA with your customers.

Operating it

  • Credits. All keys of one user draw on that user's prepaid balance. An empty balance returns 429 insufficient_quota on every key at once: alert on it as an operations event and do not show it to customers as their error. Top up from the console, or ask us about volume pricing.
  • Rate limit. 120 requests per minute per key on chat completions. If your traffic for one key needs more, contact us and we can raise it for that key.
  • Errors. Branch on error.code. Full list: Errors and error codes.
  • Your voice. ismscopilot.system: "none" removes our persona; your system prompt stays in charge and the modules are still injected. See The system prompt, in full.
  • Law changes. Poll the public changelog daily with If-None-Match and re-run the questions that matter for the affected countries. See Serve customers in many countries.

Volume pricing is available on request for platforms with steady monthly volume. Write to us at chat.ismscopilot.com/support. For security and legal review, see Procurement and security review.

On this page