On this page

The Egress API powers the Security tab on every Computer and Sandbox: encrypted credential vault, OAuth Connect, network allowlist with audit-only / enforce modes, and a live-tail audit log.

Base path: /api/v1/egress

For the architecture overview, walkthroughs, and white-label patterns, see the Security guides.


Secrets

Create a secret

POST /api/v1/egress/secrets

Stores an encrypted credential and optionally binds it to a resource as an environment variable. The raw value is encrypted with ChaCha20-Poly1305 and never returned again.

curl -X POST https://api.miosa.ai/api/v1/egress/secrets 
  -H "Authorization: Bearer $MIOSA_API_KEY" 
  -H "Content-Type: application/json" 
  -d '{
    "name": "openai_key",
    "value": "sk-proj-...",
    "type": "api_key",
    "scope": "user",
    "expose_as_env": "OPENAI_API_KEY"
  }'

Required body fields: name, value, type, scope.

FieldValuesNotes
typeapi_key, oauth_connect, oauth_machineOAuth flows are usually initiated via /oauth/start instead.
scopetenant, workspace, user, external_user, external_workspaceThe 5-tier scoping model.
expose_as_envshell-safe upper-snake-caseIf set, auto-creates a binding.

Optional scoping fields based on scope: workspace_id, owner_user_id, external_user_id, external_workspace_id.

Returns 201 Created with {data: <public_secret>, binding: <binding_or_null>}. The data.preview is the first-6 + last-4 of the value; the full value is never returned.

List secrets

GET /api/v1/egress/secrets

Query params: scope, workspace_id, owner_user_id, external_user_id, external_workspace_id.

Returns metadata only (no values).

Get a secret

GET /api/v1/egress/secrets/:id — metadata for one secret.

Rotate

PATCH /api/v1/egress/secrets/:id

Re-encrypts with a new value. The bound placeholder_token does not change; every sandbox using it picks up the new value on its next outbound request.

{ "value": "sk-proj-new-value", "expires_at": "2026-12-31T00:00:00Z" }

Delete

DELETE /api/v1/egress/secrets/:id


Bindings

A binding links a secret to a Computer or Sandbox and surfaces it as an environment variable (or opaque header token).

Create a binding

POST /api/v1/egress/bindings

{
  "secret_id": "<uuid>",
  "resource_id": "<computer-or-sandbox-id>",
  "resource_type": "sandbox",
  "exposure": "env_var",
  "expose_as_env": "OPENAI_API_KEY"
}

Returns the binding including its placeholder_token — the opaque miosa-tok-… value the workload sees in env.

List + delete

  • GET /api/v1/egress/bindings?resource_id=<id>&resource_type=sandbox
  • DELETE /api/v1/egress/bindings/:id

OAuth Connect

List provider catalog

GET /api/v1/egress/oauth/providers — returns providers visible to the calling tenant (platform defaults merged with tenant overrides). No client secrets exposed.

Start an OAuth flow

POST /api/v1/egress/oauth/start

{
  "provider": "github",
  "expose_as_env": "GITHUB_TOKEN",
  "scope": "user",
  "owner_user_id": "<uuid>"
}

Returns {authorize_url, state}. Open authorize_url in a browser; the user approves; the upstream redirects to /api/v1/egress/oauth/callback?code=&state=. On success the tokens are encrypted into a new oauth_connect secret + binding.

Status

GET /api/v1/egress/oauth/status?state=<state> — poll while the user is completing the flow. Returns pending, complete, or failed.

Admin: tenant-override providers (white-label)

  • POST /api/v1/egress/oauth/admin/providers — register your own OAuth app credentials so end-users see your brand on the consent screen.
  • DELETE /api/v1/egress/oauth/admin/providers/:id

Requires admin (msk_a_*) credentials.


Network policies + allowlist

Policies

  • GET /api/v1/egress/policies — list (optionally filter by resource_id)
  • POST /api/v1/egress/policies — create
  • GET /api/v1/egress/policies/:id
  • PATCH /api/v1/egress/policies/:id — set mode (audit_only / enforce), default_action (allow / deny)

Each Computer or Sandbox resolves the policy in this order: resource override → tenant default. Tenant default starts in audit_only mode.

Allowlist rules

  • GET /api/v1/egress/allowlist?policy_id=<uuid>
  • POST /api/v1/egress/allowlist
  • DELETE /api/v1/egress/allowlist/:id
{
  "policy_id": "<uuid>",
  "pattern": "*.openai.com",
  "kind": "wildcard",
  "method": "POST",
  "path_glob": "/v1/*",
  "action": "allow",
  "warn_only": false,
  "priority": 100
}

Pattern kinds: exact (literal host), wildcard (*.example.com), cidr (IP literal targets only).


Audit log

Query

GET /api/v1/egress/audit

Query params: resource_id, host, action (allow/reject/stub/error), since (relative like 1h / 7d or ISO timestamp), until, limit (default 100, max 1000), external_user_id, external_workspace_id.

Each row includes the full identity chain (tenant_id, workspace_id, owner_user_id, external_user_id, external_workspace_id), request metadata (host, method, path, sni, mode, remote_addr), outcome (action, rejected_by, status_code, duration_ms, bytes_in, bytes_out, error_message), and the transform trace JSONB (request_transforms, response_transforms, mcp).

Get a single event

GET /api/v1/egress/audit/:id

Allowlist suggestions

GET /api/v1/egress/audit/suggestions?resource_id=<id> — returns the hosts a resource has contacted recently, ranked by frequency. The basis for the one-click “Lock down with selected as allowlist” UX.

Live tail (WebSocket)

wss://api.miosa.ai/api/v1/egress/audit/resource/:resource_id?token=<jwt>

Subprotocol: miosa-egress-audit-v1. Pushes {"type":"audit_event","data":{...}} frames in real time as outbound requests are recorded.

Tenant-scoped variant (admin only): wss://api.miosa.ai/api/v1/egress/audit/tenant/:tenant_id?token=<jwt>.


Rate limits + retention

ScopeLimit
All /egress/* endpoints300 req/min per API key
Audit retention90 days for every tenant; longer on enterprise
Audit live-tail (WebSocket)unlimited; does not count against REST quota

Errors

Same envelope as the rest of the API — {"error": "...", "details": ...} with the appropriate status code.

StatusWhen
400invalid body, unknown scope, malformed env var name
401missing / invalid credential
403role does not allow the operation (e.g., creating tenant-scoped secret as a non-admin)
404secret / binding / policy / event not found in this tenant
409secret name collision within scope (unique partial index)
422scope ↔ identity column mismatch (e.g., scope=user without owner_user_id)

SDK shortcut

All endpoints above are wrapped by the Egress SDK reference with idiomatic methods in Python, TypeScript, Go, Java, and Elixir.

Was this helpful?