Menu
Data handling
Where your data goes when you call the API or use the console: what is only processed, what is stored, who can read it, how long it is kept and how to have it deleted. Use it to answer a security review, a customer questionnaire or your own privacy notice.
At a glance
The content of a request, meaning the record and the questions, is never stored. What is stored is the metadata needed to show your usage and charge for it, plus the details needed to run your account.
| Data | Stored | Details |
|---|---|---|
Records (state) and questions | No | Processed in memory while the request runs, then gone. |
| Answers | No | Returned to you in the response. No copy is kept. |
| Request metadata | Yes | Time, project, key, model, mode, status, counts, tokens, latency, cost and request id. No content. |
| Account details | Yes | Name, email, and avatar if you sign in with Google. |
| API keys | Hash and prefix | A SHA-256 hash and the first 12 characters. Never the full key. |
| Card details | No | Handled by Polar, the merchant of record. They never reach us. |
Your requests
What is processed, not stored
A request to POST /v1/decisions or POST /v1/systemone reaches the API over HTTPS. The API checks your key, your credit and your limits, then passes the body to the model. The model reads the record, answers the questions, and the answers are sent back to you in the response.
The state and questions are held in memory only for that work. They are never written to the database, and neither are the answers. That has three consequences:
- They are never used to train models. There is no stored copy to train on.
- We cannot show you, return or search a past request's record, questions or answers.
- When you ask support about a request that reached the model, we can look up its metadata by request id, but not what it contained. If the content matters, describe it in your message.
What is stored for each request
Each request that reaches the model writes one row of metadata. This is what the Usage page, the CSV export and your charges are built from. Nothing in it comes from the record, the questions or the answers.
| Field | What it holds | CSV column |
|---|---|---|
| Time | When the request completed, in UTC. | time_utc |
| Organization | The organization the key belongs to. | One organization per export |
| Project | The project the key belongs to. | project (its name) |
| API key | A reference to the key that sent it, never the key itself. | api_key (its name) |
| Endpoint | /v1/decisions or /v1/systemone. | endpoint |
| Model | The model id, for example clara-1. | model |
| Mode | fast or consistent. | mode |
| Status | 200 when answered, otherwise an error status. | status |
| Questions | How many questions were answered (0 when the model could not answer). | questions |
| Input tokens | Billed tokens, the same number as usage.input_tokens in the response. | input_tokens |
| Output tokens | Always 0 for Clara, which never writes free text. | Not exported |
| Latency | Model time in milliseconds, the same as latency_ms in the response. The CSV rounds it to whole milliseconds. | latency_ms |
| Cost | Exactly what was deducted, the same as usage.cost_usd. | cost_usd |
| Request id | The same value as the x-request-id response header. | request_id |
| Billing reference | An internal link to the credit hold the request was paid from. | Not exported |
A request the model could not answer is still recorded, with its status, 0 tokens and a cost of $0, so you can see failures in Usage. A request refused before the model runs, for example for a missing or invalid key, an invalid body, a rate limit, a spend limit or insufficient credit, does not write a usage row. Its error response still carries a request_id for your logs.
Example: what you send and what we keep
This request carries a customer's name, email and message. The first tab is what you send, the second is what you get back, and the third is the row the platform keeps about it, as it appears in the CSV export from Console > Usage. The export leaves out the organization, the output token count and the billing reference.
{
"model": "clara-1",
"state": {
"customer": "Dana Whitfield",
"email": "dana@example.com",
"message": "My order 48213 arrived broken. I would like my money back.",
"order_date": "2026-10-02",
"order_status": "delivered"
},
"questions": {
"wants_refund": {
"type": "noul",
"instructions": "Is the customer asking for a refund?"
},
"topic": {
"type": "choice",
"instructions": "What is the message mainly about?",
"criteria": {
"damaged": "The item arrived damaged or broken",
"late": "The order is late or has not arrived",
"other": "Anything else"
}
}
}
}The name, email, message and order details are not in the stored row. Neither are the question text, the options or the probabilities. Only the counts, the timing, the cost and the ids remain.
Your account
To run your account and your organizations, the platform stores:
| Data | What is stored |
|---|---|
| Profile | Your name, your email address, your avatar if you sign in with Google, and when you signed up. You can change your display name in Settings. |
| Password | Handled and stored hashed by our authentication provider. We never see it. Email sign-ups are confirmed with a 6-digit verification code. |
| Organizations | Name, who created it, its members and each member's role. |
| Projects | Name, the optional monthly spend limit, and when it was archived. |
| Invitations | The invitee's email, the role offered, who sent it, and when it was sent, accepted or revoked. |
The console uses only strictly necessary cookies: your sign-in session, and the organization and project you last selected. There are no advertising or analytics cookies.
API keys
A key is generated when you create it and shown to you once. The platform then keeps only:
- a SHA-256 hash of the key, which is how requests are matched to it;
- its first 12 characters (for example
sk_sa_Ab12Cd), so you can tell keys apart in the console; - its name, its project, who created it, when it was created, when it was last used and when it was revoked.
On each request the API hashes the key you send and looks up the hash. Lookups are cached for 60 seconds, which is why a revoked key stops working within 60 seconds. Revoked keys stay listed in the console, so past usage still shows which key sent each request.
Payments
Polar is the merchant of record. It runs checkout, collects taxes, and issues receipts and invoices. Your card details go to Polar and never reach our database.
For each payment, the platform stores the order record Polar sends when the payment completes or is refunded, including the order id, its status, the net, total and refunded amounts, the currency and who paid. It also keeps the organization's credit balance, lifetime paid amount, tier and tier history, and a credit history: every top-up and refund, and every credit hold taken for requests and returned, with the balance after each.
The company name, VAT number and address on your invoices are edited in the billing portal, which Polar runs. Open it from Console > Billing. Receipts and invoices for every payment are under Billing > Payments.
Access
Who can read what
Every table in the database is protected by row-level security. The check runs inside the database on every read, not only in the console: a signed-in member can read only the rows of organizations they belong to, and some data needs a specific role.
| Data | Who can read it |
|---|---|
| Organization name, projects, members and their roles | Every member |
| Members' names, emails and avatars | Every member of an organization you share |
| API key list: name, first 12 characters, project, creator and dates | Every member |
| Usage, per request and per day | Every member |
| Credit balance, tier and tier history | Every member |
| Invitations | Owners and admins |
| Payments, receipts, invoices and credit history | Owners, admins and billing members |
| Model list and prices | Anyone |
How changes are made
The console cannot write to these tables directly. Every change, such as creating or revoking a key, inviting a member, changing a role or setting a spend limit, goes through a database function that first checks your role in that organization and refuses the change if your role does not allow it. Payments are recorded only by the payment webhook, after Polar's signature on it is verified, and usage rows are written only by the API.
Roles in short: owners can do everything, including changing roles; admins manage projects, keys, members and billing; developers create and use keys, revoke their own and see usage; billing members manage credit and invoices and see usage; readers can only view. Organizations and projects has the full table.
Not used for training
Records and questions are processed in memory and never written to the database, so they are never used to train models. This holds for every tier, every mode and every endpoint. There is no setting to turn on or off.
Retention
| Data | How long it is kept |
|---|---|
| Records, questions and answers | Not kept. They exist only in memory while the request runs. |
| Request metadata | While the organization exists. It is your usage history and the record behind every charge. |
| Profile | While your account exists. |
| Memberships | Until you leave or are removed from the organization, or your account is deleted. |
| API keys (hash, first 12 characters, dates) | While the organization exists, including after a key is revoked. |
| Invitations | While the organization exists, with the date each was accepted or revoked. |
| Payments and credit history | As long as we need them for accounting, which can be after an account is deleted. |
In plain terms: the content you send is gone when the response is sent. Everything else is kept for as long as the account or organization it belongs to, and billing records for as long as accounting requires.
Deleting your data
There is no delete button in the console. To delete your account, or to see or correct the personal data we hold about you, write to support@saanora.com from the email address you sign in with, and say what you want done.
When we delete your account, we remove your profile and your memberships. An organization's projects, usage and billing records belong to the organization, so they stay with it while it exists. Billing records we need for accounting may be kept.
Before you write, export what you want to keep: Console > Usage > Export CSV downloads one row per request, up to 100,000 rows, for the range (7, 30 or 90 days), project and model you selected. Records and answers from past requests cannot be returned or deleted, because they were never stored.
Some things you can do yourself at any time:
- Revoke a key under API keys. Its creator, an owner or an admin can do this.
- Archive a project under Projects, as an owner or admin. This revokes all of its keys. An organization keeps at least one active project.
- In a team organization, remove a member under Members as an owner or admin, or leave it yourself. Only an owner can remove an owner, and an organization keeps at least one owner.
- Change your display name under Settings.
Keeping your own records
An audit log keyed by request id
Because the platform keeps no content, the request id is the link between your records and ours. Log it with each response. The example below appends one JSON line per request with the request id, the questions, the answers, the usage and any error. It stores a SHA-256 fingerprint of the record instead of the record, so you can match a record later without keeping a second copy. Store the full record instead if your audit needs it.
import hashlib
import json
import os
from datetime import datetime, timezone
import requests
API_URL = "https://api.saanora.com"
API_KEY = os.environ["SAANORA_API_KEY"]
def decide(state: dict, questions: dict, log_path: str = "decisions.jsonl") -> dict:
"""Ask Clara and keep your own audit record. Saanora does not store the record or the answers."""
r = requests.post(
f"{API_URL}/v1/decisions",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"model": "clara-1", "state": state, "questions": questions},
timeout=120, # generous: the first request after idle can be slow
)
body = r.json()
entry = {
"at": datetime.now(timezone.utc).isoformat(),
"request_id": r.headers.get("x-request-id"),
"status": r.status_code,
# a fingerprint lets you match a record later without keeping a second copy of it
"state_sha256": hashlib.sha256(json.dumps(state, sort_keys=True).encode()).hexdigest(),
"questions": questions,
"answers": body.get("answers"),
"usage": body.get("usage"),
"error": body.get("error"),
}
with open(log_path, "a", encoding="utf-8") as f:
f.write(json.dumps(entry) + "\n")
if r.status_code != 200:
err = body["error"]
raise RuntimeError(f"{err['code']}: {err['message']} ({entry['request_id']})")
return body["answers"]
answers = decide(
{"message": "My order 48213 arrived broken. I would like my money back.", "order_status": "delivered"},
{"wants_refund": {"type": "noul", "instructions": "Is the customer asking for a refund?"}},
)
print(answers["wants_refund"]["noul"])The request_id in your log matches the request_id column in the usage export (for requests that reached the model) and the id of the decision (dec_ followed by the same characters). Errors carry it too, in the body and in the x-request-id header.
Send only what the decision needs
Clara answers from the record alone, so a field that no question depends on adds nothing to the answer. Leaving it out keeps personal data off the wire and lowers the cost, since you are billed on input tokens and every field in the record adds to them.
ticket = {
"customer": "Dana Whitfield",
"email": "dana@example.com",
"phone": "+1 555 0100",
"message": "My order 48213 arrived broken. I would like my money back.",
"order_date": "2026-10-02",
"order_status": "delivered",
}
# The questions are about the message and the order, so send only those fields.
NEEDED = ("message", "order_date", "order_status")
state = {k: ticket[k] for k in NEEDED}Policies and contact
- The privacy policy covers the same ground in legal form, and lists the service providers that process data for the platform.
- The platform terms cover ownership: you own your records, your questions and the answers.
- This page covers the platform API and console. Saanora Chat is a separate product with its own privacy policy.
- Questions about data handling: support@saanora.com.