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 ─┘- 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. - 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.
- Pins on every request, from your tenant record. Build
ismscopilot.frameworksserver-side from the customer's stored country and frameworks. Never take the pin list from end-user input. - Region per customer. For customers who need AI processing in the EU, use the
-eualiases (isms-fast-eu,isms-thinking-eu). The other aliases, includingisms-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.contentrecord 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
| Risk | What your platform does |
|---|---|
| Another tenant's text in the prompt | Assemble 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 tenants | If 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 tenant | Map 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 key | Per-key spend caps (hour, day, week, month) and revocation in the console. One key per country limits what one leak can spend. |
| Your own logs | If 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
429insufficient_quotaon 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-Matchand 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.
Related
- Serve customers in many countries
- Procurement and security review
- Credits, pricing, and spend limits
- Product page for compliance platforms: ismscopilot.com/for/compliance-platforms