Base URL: http://127.0.0.1:8023/api/v1
All authenticated endpoints require the Authorization header:
Authorization: Bearer <FORTIS_AGENT_KEY>
The API key is set via the FORTIS_AGENT_KEY environment variable when starting the agent. If the variable is not set, the agent returns 500 Internal Server Error for all authenticated requests.
Unauthenticated requests to protected endpoints return 401 Unauthorized.
Returns real-time system telemetry collected via shell commands (top, free, df, sensors).
Response:
{
"cpu": "12.5%",
"mem_total": "16384 MB",
"mem_used": "8192 MB",
"swap_total": "8192 MB",
"swap_used": "1024 MB",
"storage_total": "500G",
"storage_used": "200G",
"temp": "52.0°C",
"uptime": "up 3 hours, 42 minutes"
}Note: Temperature reading tries
sensorsoutput forTctlfirst, then falls back toCPU:label.
Returns the agent's enabled feature flags.
Response:
{
"telemetry": true,
"actions": true,
"files": true,
"vault": true,
"biometric_auth": true,
"backup": true,
"terminal": true,
"clipboard": true,
"webcam": true,
"media": true
}Copies text to the system clipboard. Uses wl-copy on Wayland or xclip -selection clipboard on X11.
Request Body: Raw text bytes (not JSON).
Response:
{
"status": "success",
"message": "Clipboard updated"
}Executes a system action.
Request Body:
{
"action": "lock_screen"
}Available Actions:
| Action | Implementation | Notes |
|---|---|---|
lock_screen |
loginctl lock-session |
|
run_backup |
Spawns fortis-backup.sh |
Path is hardcoded in source |
media_play_pause |
playerctl play-pause |
Requires playerctl |
media_next |
playerctl next |
|
media_prev |
playerctl previous |
|
volume_up |
amixer -D pulse sset Master 5%+ |
Returns current volume |
volume_down |
amixer -D pulse sset Master 5%- |
Returns current volume |
suspend |
systemctl suspend |
|
shutdown |
systemctl poweroff |
Response:
{
"status": "success",
"action": "lock_screen"
}For volume actions, the response includes the current volume level:
{
"status": "success",
"action": "volume_up",
"volume": "75%"
}Errors:
400 Bad Request— Unknown action string.
Returns currently playing media metadata via playerctl metadata.
Response:
{
"title": "Bohemian Rhapsody",
"artist": "Queen",
"album": "A Night at the Opera",
"position": "120000000",
"length": "354000000",
"status": "Playing",
"player": "spotify"
}Errors:
404 Not Found— No active media player detected.
Upload a file via multipart form data. Files are saved to ~/Downloads/FortisMobileDrop/.
Request: Content-Type: multipart/form-data with a field named file.
Response:
{
"status": "success",
"file": "document.pdf"
}Errors:
400 Bad Request— Nofilefield found in the multipart body.500 Internal Server Error— Failed to write file to disk.
List contents of a directory on the host machine.
Request Body:
{
"path": "/home/user/Documents"
}If path is null or empty, defaults to $HOME.
Response:
{
"path": "/home/user/Documents",
"files": [
{ "name": "report.pdf", "size": 1234567, "is_dir": false },
{ "name": "photos", "size": 0, "is_dir": true }
]
}Download a file from the host. Returns the file as a binary stream with a Content-Disposition: attachment header.
Query Parameter: path — Absolute path to the file.
Errors:
404 Not Found— File does not exist or is a directory.
Returns the last 50 lines of the backup mirror log at /mnt/FortisVault/Logs/mirror/mirror.log as plain text.
Errors:
500 Internal Server Error— Log file not found or unreadable.
Returns all vault entries from the SQLCipher database at ~/.local/share/fortis/vault.db.
Note: The database is opened with a hardcoded development key (
dev_master_key) in the current implementation.
Response:
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"folder_id": null,
"title": "GitHub",
"entry_type": {
"Login": {
"username": "user@example.com",
"password": "s3cur3pass",
"url": "https://github.com",
"totp_secret": null
}
},
"custom_fields": [],
"notes": "My GitHub account"
}
]Create a new vault entry.
Request Body:
{
"title": "GitHub",
"notes": "Work account",
"entry_type": "Login",
"username": "user@example.com",
"password": "s3cur3pass",
"totp_secret": "JBSWY3DPEHPK3PXP"
}Entry Types:
| Type | Additional Fields |
|---|---|
Login |
username (optional), password (optional), totp_secret (optional) |
CreditCard |
Created with empty placeholder fields |
SecureNote (default) |
notes field is used as the secure content |
Response:
{
"status": "success"
}Accept a device pairing request. Validates the 6-digit code against the currently active pairing code (generated via fortis-cli pair or the IPC PAIR_START command).
Request Body:
{
"code": "482-916",
"public_key": "hex_encoded_ed25519_public_key"
}On success, the device's public key is stored in ~/.config/fortis/paired_devices.json.
Success Response:
{
"status": "success",
"message": "Paired successfully."
}Errors:
401 Unauthorized— Code is invalid, expired, or no active pairing session.
Request biometric unlock. Used by the fortis-lock browser extension. The agent relays a biometric challenge to the paired mobile device via gRPC.
Request Body:
{
"domain": "reddit.com"
}Success Response (biometric approved):
{
"status": "success"
}Errors:
401 Unauthorized— Biometric authentication was denied or failed.
Note (current behavior): If the gRPC connection to the mobile device fails, the agent returns a mock
AUTH_SUCCESSfor development purposes. This will be removed in production.
Socket Path: /tmp/fortis_agent.sock
Permissions: 0600 (owner-only read/write)
The agent listens for text-based commands on the Unix socket. Each connection can send multiple commands.
| IPC Command | Description | Notes |
|---|---|---|
ADD <title> |
Create a SecureNote vault entry with the given title | Content: "Encrypted secret note via IPC" |
GET |
List all vault entries | Returns debug-formatted entry list |
SYNC |
Start P2P sync | Stub — returns static message |
AUTH_CHALLENGE |
Send biometric auth challenge to paired mobile | Relays via gRPC; mock fallback in dev |
PAIR_START |
Generate a 6-digit pairing code | Code format: XXX-XXX |
Note: The IPC server initializes its own database connection and schema on startup, independent of the REST server.
Example (using socat):
echo "ADD My Secret" | socat - UNIX-CONNECT:/tmp/fortis_agent.sock
echo "GET" | socat - UNIX-CONNECT:/tmp/fortis_agent.sockPort: 50051 (default in config)
⚠️ Current Status: The gRPC transport server infortis-agentis a stub — it starts but performs no operations. The gRPC service definitions exist infortis-syncand the client code exists infortis-agent/src/modules/auth.rs, but the server-side listener is not yet implemented.
The protobuf file is located at fortis-sync/proto/sync.proto within the fortis-backend repository.
service SyncService {
rpc Handshake (DeviceAuth) returns (Challenge);
rpc ExchangeClocks (VectorClock) returns (Delta);
rpc PushEntries (EncryptedBatch) returns (Ack);
rpc PullEntries (SinceTimestamp) returns (EncryptedBatch);
}Used for biometric authentication relay between agent and mobile.
service FortisAuth {
rpc RequestBiometricAuth (BiometricAuthRequest) returns (BiometricAuthResponse);
}
message BiometricAuthRequest {
string nonce = 1;
uint64 timestamp = 2;
string device_name = 3;
string requested_action = 4;
}
message BiometricAuthResponse {
bool approved = 1;
string signature = 2; // Hex-encoded Ed25519 signature
}sequenceDiagram
participant Desktop as fortis-desktop
participant Lock as fortis-lock
participant CLI as fortis-cli
participant Agent as fortis-agent
participant Mobile as fortis-mobile
participant DB as vault.db
Desktop->>Agent: GET /api/v1/vault/entries (Bearer token)
Agent->>DB: db.get_entries()
DB-->>Agent: Vec<VaultEntry>
Agent-->>Desktop: 200 JSON
CLI->>Agent: "ADD My Note" (Unix Socket)
Agent->>DB: db.add_entry(&entry)
Agent-->>CLI: "Success: Added entry"
Lock->>Agent: POST /api/v1/auth/unlock (no auth)
Agent->>Mobile: gRPC RequestBiometricAuth
Mobile-->>Agent: BiometricAuthResponse (signature)
Agent-->>Lock: 200 { status: success }