🔑 API Access
An application can drive ODAC itself (list apps, send mail, add a domain) by calling the same API the CLI uses. odac app api decides whether an app may do that, and which actions it may call. Nothing is granted by default: an app with no grant has neither a key nor a socket to reach.
Scope: This Server Only
[!IMPORTANT]
This API is local. It talks to the ODAC server running on this machine and nothing else. It never connects to ODAC Cloud, and it carries no notion of your other servers. An app you grant access to can manage this server's apps, domains and mailboxes; it cannot reach or manage any other server registered in ODAC Cloud.
The key follows from that: ODAC_API_KEY is minted by your local ODAC server, signed with a secret it generated for itself on first start, and injected into the container automatically. There is no ODAC Cloud account, login, or connection involved. This works exactly the same on a machine that has never talked to the Cloud, and on one that is fully offline.
How an App Reaches the API
When an app has a grant, ODAC injects two things at container start:
| Variable | Value | What it is |
|---|---|---|
ODAC_API_SOCKET |
/odac/api.sock |
The API's unix socket. ODAC's run directory is bind-mounted read-only at /odac. |
ODAC_API_KEY |
signed token | Identifies the calling app on every request. |
The socket is a local file, not a network service, so this works for apps that are isolated with no outbound access at all, and for apps on the shared bridge or in host network mode alike.
A request is one JSON object written to the socket:
{ "auth": "<ODAC_API_KEY>", "action": "app.list", "data": [] }
Action names are the ones in the CLI Reference with the space replaced by a dot: odac app list is app.list, odac mail send is mail.send.
Available Actions
These are the actions available to applications. An app may call the ones you granted it, and --all covers the whole table.
| Action | data |
What it does |
|---|---|---|
app.list |
[] or [true] for detail |
List apps |
app.create |
[config] |
Create an app (same object the CLI builds) |
app.delete |
[app], or [app, {"purge": false}] to keep its data |
Delete an app |
app.start |
[app] |
Start a stopped app |
app.stop |
[app] |
Stop a running app |
app.restart |
[app] |
Restart an app |
app.network |
[app, "bridge"|"host"] |
Set the network mode |
app.isolate |
[app, true|false] |
Cut off or restore outbound access |
app.device.add |
[app, hostPath, containerPath] |
Connect a host device |
app.device.delete |
[app, hostPath] |
Disconnect a host device |
domain.list |
[], or [app] to filter |
List domains |
domain.add |
[domain, app] |
Route a domain to an app |
domain.delete |
[domain] |
Remove a domain |
dns.list |
[domain] |
List a domain's DNS records |
ssl.renew |
[domain] |
Force an SSL certificate renewal |
mail.send |
[message] |
Send mail from one of your domains |
mail.list |
[domain] |
List mailboxes |
mail.create |
[email, password, passwordAgain] |
Create a mailbox |
mail.password |
[email, password, passwordAgain] |
Change a mailbox password |
mail.delete |
[email] |
Delete a mailbox |
app is an App ID or name, exactly like the CLI's -i argument.
mail.send is the one action whose argument is a message object rather than plain strings:
{
"auth": "<ODAC_API_KEY>",
"action": "mail.send",
"data": [{
"from": "no-reply@example.com",
"to": "someone@example.com",
"subject": "Welcome",
"header": { "Content-Type": "text/html; charset=utf-8" },
"html": "<p>Hello</p>",
"text": "Hello"
}]
}
from must belong to a domain configured on this server. header is required; supply at least a Content-Type, and provide html, text, or both.
Usage
# Grant exactly what the app needs
odac app api my-app --allow app.list,mail.send
# Grant every action, present and future (asks you to confirm)
odac app api my-app --all
# Revoke
odac app api my-app --off
# Interactive: prompts for the action list
odac app api my-app
Available Prefixes
-i,--id: The App ID or Name--allow: Comma-separated action names to permit (*means all)--all: Permit every action, same as--allow "*"--off: Revoke API access
Action names are validated against the server's registered actions when you grant them, so a typo is rejected at the prompt instead of turning into a permission_denied on every call the app makes.
Grant and Revoke Are Not Symmetric
⚠️ Granting takes a restart. The key and the socket mount are handed to the container when it starts, so a new grant reaches the app on its next start:
odac app restart my-app
Revoking does not. Permissions are read from the configuration on every request, so --off (or a narrowed --allow) refuses the app's very next call. The running container keeps its now-worthless key until you restart it. If you are revoking because an app misbehaved, the access is gone the moment the command returns.
Choosing Permissions
Grant the narrowest list that works. --all still means an app can create and delete other apps, and add or remove domains and mailboxes, from inside a container with no further check. An app that needs to send mail wants --allow mail.send. Treat a full grant the way you would treat privileged access.