Reference
Batteries
Three services the platform runs so a small app does not have to.
They are HTTP APIs on batteries.saasie.io, reached with
a token that identifies your Saasie — not you.
Analytics
Register the events you care about, log them, and count them back. Types are declared up front so a typo is an error at the call that made it rather than a dimension that reads zero forever.
3 endpoints →
One endpoint. Your Saasie sends from its own address on saasie.email, assigned by the platform. Outbound only — there is no inbox.
1 endpoints →
Inference
Two models, named for what they are for rather than what they are. Nothing you send is stored; only the token count is.
3 endpoints →
Getting the credential
Declare the battery in saasie.yaml
and the platform injects its address and token. Declare nothing and
you get no credential at all — which is the difference between a
platform that hands out secrets and one that answers requests.
BATTERIES
Three services the platform runs so your app does not have to: analytics,
outbound email, and inference. All are HTTP APIs, reached with a token that
identifies your Saasie.
Neither is on by default. Declare what you use, and the platform injects the
address and the token for it — a blueprint that declares nothing gets no
credential at all.
version: 1
batteries:
analytics:
mail:
inference:
services:
backend:
dockerfile: Dockerfile
context: .
port: 3000
health:
path: /health
env:
SAASIE_ANALYTICS_URL: { battery: analytics, property: url }
SAASIE_ANALYTICS_TOKEN: { battery: analytics, property: token }
SAASIE_MAIL_URL: { battery: mail, property: url }
SAASIE_MAIL_TOKEN: { battery: mail, property: token }
SAASIE_MAIL_FROM: { battery: mail, property: from }
SAASIE_INFERENCE_URL: { battery: inference, property: url }
SAASIE_INFERENCE_TOKEN: { battery: inference, property: token }
The env var names are yours; only the references are fixed. "token" is the
same value whichever battery you ask — it identifies the Saasie, not the
battery — so one variable is enough if you would rather.
Send it as: Authorization: Bearer <token>
The URLs point at the batteries service, which is a different host from the
one this CLI talks to. Read them from the environment; do not hardcode them.
ANALYTICS
Register a type before you log events of it. Registration is idempotent, so
call it at startup and do not track whether you have.
POST $SAASIE_ANALYTICS_URL/types
{ "name": "signup", "properties": { "plan": "string" } }
POST $SAASIE_ANALYTICS_URL/events
{ "events": [ { "type": "signup", "properties": { "plan": "pro" } } ] }
POST $SAASIE_ANALYTICS_URL/query
{ "type": "signup", "since": 1754438400000, "interval": "day",
"groupBy": "plan" }
An event whose type is not registered is refused, and so is a property the
type did not declare. That is the point: a typo in an event name is caught at
the call that made it, rather than becoming a second dimension that reads as
zero forever while the real one looks like it dropped.
"at" is optional — epoch milliseconds or ISO — and defaults to now.
Property values may be strings, numbers or booleans; all are stored as
strings.
Up to 100 events per request. A batch is stored whole or not at all.
MAIL — SENDING
POST $SAASIE_MAIL_URL/send
{ "to": "someone@example.com", "subject": "Your receipt",
"text": "Thanks!" }
It is sent from $SAASIE_MAIL_FROM — your Saasie's own address, assigned by
the platform. You cannot choose it. It looks like:
acme-x7k2p9@saasie.email
The random half is not decoration. Names can be claimed again after a Saasie
is deleted, and without it the next "acme" would receive mail meant for the
last one.
MAIL — RECEIVING
There is none. The address sends only; mail sent *to* it is discarded, and
nothing bounces. If your app needs to hear back from someone, put a form in
the app rather than asking them to reply.
INFERENCE
Two models, named by what they are for rather than by what they are:
POST $SAASIE_INFERENCE_URL/generate
{ "prompt": "Summarise this in one line: ..." }
POST $SAASIE_INFERENCE_URL/embed
{ "input": ["first passage", "second passage"] }
GET $SAASIE_INFERENCE_URL/models
"saasie/fast" generates and is the default for /generate. "saasie/embed"
embeds and is the default for /embed. You do not get to choose the model
behind either, and that is deliberate: it means we can move to a better one
without breaking your app.
Send "messages" instead of "prompt" for a conversation:
[ { "role": "user", "content": "..." } ]
"maxTokens" caps the answer. Ask for more than the platform allows and you
get the platform's ceiling — check "finishReason": "length" tells you the
answer was cut off.
/embed takes one string or a batch. Vectors come back in the order you
sent them; pair them with your own ids by position. Read "dimensions"
rather than hardcoding it.
Nothing you send is stored. We record how many tokens a call used, and never
the prompt, the answer, or the vectors.
WHAT YOU GET, AND WHAT STOPS YOU
5 emails per day, to any address
100,000 events per day
events kept 90 days
50,000 generation tokens per day
500,000 embedding tokens per day
Every count resets at midnight UTC. Past any of them, the API answers 429 and
says when it resets — handle that rather than retrying, because retrying will
not help until the next day.
Inference is counted in tokens rather than in calls, because that is what it
costs. Roughly: a token is about four characters, and a request reserves its
whole "maxTokens" up front — so a large "maxTokens" you do not use still
takes up room in the day's budget. Ask for what you need.
All of it is covered by the subscription. That is what the caps are for: a
capped service can be included, an uncapped one has to be metered.
THE TOKEN
It identifies your Saasie and nothing else — it cannot claim a name, read
another Saasie's data, or act on your account. Treat it as a secret anyway:
anything holding it can send mail as you, and there are only five a day.
It is not printed by any command and not shown in any console, because the
only thing that needs it is your container. It stops working while the Saasie
is suspended, and starts again when it resumes.
Read what your app has been doing:
saasie app <name> analytics types
saasie app <name> analytics count --type signup --since 30d
saasie app <name> mail
The same text, on your machine: saasie docs batteries.
The machine-readable contract is
openapi.json.