Docs Back to website
← All docs Back to website
Developers

Any language

On this page

Outlet's server side is six calls over HTTPS. Any language can make them. The npm and Python packages wrap these same calls.

Direct needs none of them. The user's Direct API key goes from your app to the provider.

App ID
From useoutlet.dev/register.
App secret
From useoutlet.dev/register.
Return address
The address Outlet sends the user back to. Registered with the app.
Base URL
https://api.useoutlet.dev

Start

Create a Vault connection request and send the user to its grant URL.

curl -X POST https://api.useoutlet.dev/v0/grants \
  -H "content-type: application/json" \
  -H "x-outlet-app-secret: apps_yourappsecret" \
  -d '{
    "app_id": "app_yourapp",
    "providers": ["openai"],
    "requested_cap_usd": 10
  }'
{
  "grantRequestId": "grant_7cef7de3250245d1a88c6c9e2b41023e",
  "grantUrl": "https://useoutlet.dev/grant/grant_7cef7de3250245d1a88c6c9e2b41023e"
}

Public client, no app secret:

curl -X POST https://api.useoutlet.dev/v0/grants \
  -H "content-type: application/json" \
  -d '{
    "app_id": "app_yourapp",
    "providers": ["openai"],
    "requested_cap_usd": 10,
    "redirect_uri": "https://yourapp.com/outlet/return",
    "code_challenge": "Fl4GxMhZXnSPlTxaLWt77AvuJC6j1W7TsnixwlRjAdw",
    "code_challenge_method": "S256",
    "state": "YHRerIAgPKqREIx4KwaPCQ"
  }'
{
  "grant_request_id": "grant_7cef7de3250245d1a88c6c9e2b41023e",
  "grant_url": "https://useoutlet.dev/grant/grant_7cef7de3250245d1a88c6c9e2b41023e"
}

Wait

Poll the request until the user approves. The answer holds the Vault App key.

curl https://api.useoutlet.dev/v0/grants/grant_7cef7de3250245d1a88c6c9e2b41023e \
  -H "x-outlet-app-secret: apps_yourappsecret"

While pending:

{
  "status": "pending"
}

After approval:

{
  "status": "complete",
  "grantId": "grant_7cef7de3250245d1a88c6c9e2b41023e",
  "keys": {
    "openai": "sk-svcacct-…"
  },
  "capUsd": 10,
  "expiresAt": "2026-09-26T21:15:00.000Z"
}

Exchange

Public clients without an app secret exchange the code and the PKCE verifier instead.

curl -X POST https://api.useoutlet.dev/v0/grants/token \
  -H "content-type: application/json" \
  -d '{
    "grant_request_id": "grant_7cef7de3250245d1a88c6c9e2b41023e",
    "code": "kP9XbiMwt569W7g01d8ufYbpfGvfswvvoZ1VmF10DIA",
    "code_verifier": "nfOUVZs_vIiRQ_d2Smi3CYcx-D1-kTeTAdxwcarmUuE"
  }'
{
  "status": "complete",
  "grantId": "grant_7cef7de3250245d1a88c6c9e2b41023e",
  "keys": {
    "openai": "sk-svcacct-…"
  },
  "capUsd": 10,
  "expiresAt": "2026-09-26T21:15:00.000Z",
  "refresh_token": "rt_WPdTGLgzWnYqEksm-Kh_APx28Rkq1qovKzFOjCr1nVQ"
}

Refresh

Get the current key again. The refresh token rotates; keep the new one.

curl -X POST https://api.useoutlet.dev/v0/grants/grant_7cef7de3250245d1a88c6c9e2b41023e/refresh \
  -H "x-outlet-app-secret: apps_yourappsecret"
{
  "status": "complete",
  "grantId": "grant_7cef7de3250245d1a88c6c9e2b41023e",
  "keys": {
    "openai": "sk-svcacct-…"
  },
  "capUsd": 10,
  "expiresAt": "2026-09-26T21:30:00.000Z"
}

Public client, no app secret:

curl -X POST https://api.useoutlet.dev/v0/grants/grant_7cef7de3250245d1a88c6c9e2b41023e/refresh \
  -H "authorization: Bearer rt_WPdTGLgzWnYqEksm-Kh_APx28Rkq1qovKzFOjCr1nVQ"
{
  "status": "complete",
  "grantId": "grant_7cef7de3250245d1a88c6c9e2b41023e",
  "keys": {
    "openai": "sk-svcacct-…"
  },
  "capUsd": 10,
  "expiresAt": "2026-09-26T21:30:00.000Z",
  "refresh_token": "rt_SNQl9c3K-g8kteF9SQMjH1bUNw8fE_uy_4CP81DZ4kA"
}

Status

A pure read. active, capped, revoked or pending, and the month's spend. Never a key.

curl https://api.useoutlet.dev/v0/grants/grant_7cef7de3250245d1a88c6c9e2b41023e/status \
  -H "x-outlet-app-secret: apps_yourappsecret"
{
  "grantId": "grant_7cef7de3250245d1a88c6c9e2b41023e",
  "status": "active",
  "providers": [
    "openai"
  ],
  "capUsd": 10,
  "spendUsd": 0.42
}

Public client, no app secret:

curl https://api.useoutlet.dev/v0/grants/grant_7cef7de3250245d1a88c6c9e2b41023e/status \
  -H "authorization: Bearer rt_SNQl9c3K-g8kteF9SQMjH1bUNw8fE_uy_4CP81DZ4kA"
{
  "grantId": "grant_7cef7de3250245d1a88c6c9e2b41023e",
  "status": "active",
  "providers": [
    "openai"
  ],
  "capUsd": 10,
  "spendUsd": 0.42
}

Revoke

End the connection from the app's side.

curl -X DELETE https://api.useoutlet.dev/v0/grants/grant_7cef7de3250245d1a88c6c9e2b41023e \
  -H "x-outlet-app-secret: apps_yourappsecret"
{
  "ok": true
}

Public client, no app secret:

curl -X DELETE https://api.useoutlet.dev/v0/grants/grant_7cef7de3250245d1a88c6c9e2b41023e \
  -H "authorization: Bearer rt_SNQl9c3K-g8kteF9SQMjH1bUNw8fE_uy_4CP81DZ4kA"
{
  "ok": true
}

When a connection ends

Status answers capped or revoked. A capped connection comes back when the user raises the Vault cap; call Refresh then. A revoked one needs the user to connect again.

npm: @useoutlet/sdk · Python: useoutlet