The stock executor server (pnpm --filter @eyeball/executor start) resolves real provider credentials from configuration alone. You select a credential source with the EYEBALL_CREDENTIALS environment variable; you do not write a custom server entry point or call createExecutorApp yourself.

Choose a credential mode

EYEBALL_CREDENTIALS selects the credential provider the stock server constructs at boot. It must be one of four values:

ModeEYEBALL_CREDENTIALSCredentials live inScopeDurable
Deterministic mocks (default)mockNowhere (empty)
EnvironmentenvProcess environmentOne project + one userRe-supplied at boot
Local encrypted vaultlocal-vaultAn AES-256-GCM fileOne project, many usersYes, on disk
CloudcloudHosted vault over HTTPMulti-tenantYes (Cloud)

This page covers env and local-vault, the two OSS self-host paths. mock is covered by the local quickstart; cloud requires eyeball Cloud and is described in the cloud boundary.

Before you start

Build the workspace once:

Bash
pnpm install
pnpm build

Pick one identity and reuse it for every command on this page. The examples use:

  • Project id proj_local
  • User id local_user
  • Executor API key eyeball_local_key

The project id must be identical everywhere a credential is written and read. Credentials are partitioned by project, so a mismatch between the id you connect under and the id the executor runs under resolves to "no credential".

Option A — Environment credentials

EYEBALL_CREDENTIALS=env maps deterministic per-toolkit environment variables into credentials for exactly one project and one user. It needs no CLI and no files; it is the simplest way to give a single service identity real access.

Set the identity and the per-toolkit secrets, then start the stock server. The variable names are derived from each toolkit's auth class:

Auth classRequired variablesExample toolkit
oauth2EYEBALL_CRED_<TOOLKIT>_ACCESS_TOKEN (optional _EXPIRES_AT, _SCOPES)EYEBALL_CRED_GMAIL_ACCESS_TOKEN
api_keyEYEBALL_CRED_<TOOLKIT>_<FIELD>EYEBALL_CRED_STRIPE_API_KEY
basicEYEBALL_CRED_<TOOLKIT>_USERNAME + _PASSWORD (twilio uses _ACCOUNT_SID/_AUTH_TOKEN)EYEBALL_CRED_TWILIO_ACCOUNT_SID

<TOOLKIT> is the toolkit slug uppercased with hyphens replaced by underscores (google-calendar becomes GOOGLE_CALENDAR). A long-lived personal access token for an OAuth toolkit (GitHub, Notion) is supplied through its _ACCESS_TOKEN variable.

Bash
export EYEBALL_CREDENTIALS=env
export EYEBALL_PROJECT_ID=proj_local
export EYEBALL_USER_ID=local_user

# api_key toolkit
export EYEBALL_CRED_STRIPE_API_KEY='<stripe-secret-key>'

# oauth2 toolkit supplied with a personal access token
export EYEBALL_CRED_GITHUB_ACCESS_TOKEN='<github-personal-access-token>'

# oauth2 toolkit that enforces scopes (Gmail requires gmail.modify)
export EYEBALL_CRED_GMAIL_ACCESS_TOKEN='<gmail-access-token>'
export EYEBALL_CRED_GMAIL_SCOPES='https://www.googleapis.com/auth/gmail.modify'

EYEBALL_API_KEYS="eyeball_local_key:proj_local" \
  PORT=3000 \
  pnpm --filter @eyeball/executor start

Option B — Local encrypted vault

EYEBALL_CREDENTIALS=local-vault reads credentials from an AES-256-GCM encrypted file. It stores many users under one project, survives restarts, and runs OAuth refresh for records that carry an expiry. Use the eyeball-auth CLI to write records, then start the stock server against the same file and key.

1. Initialize the vault

Bash
pnpm eyeball-auth init --vault .eyeball/vault.json

This creates an empty vault and prints the two export lines you need — the encryption key and the file path:

Text
Initialized empty local vault at .eyeball/vault.json.
Store this key in your secret manager; it is not recoverable from the vault:
export EYEBALL_VAULT_KEY='<base64-32-byte-key>'
export EYEBALL_VAULT_PATH='.eyeball/vault.json'

Export the key, path, and the shared project id so every CLI command and the executor agree:

Bash
export EYEBALL_VAULT_KEY='<base64-32-byte-key>'   # from the init output
export EYEBALL_VAULT_PATH='.eyeball/vault.json'
export EYEBALL_PROJECT_ID='proj_local'

The CLI writes records under EYEBALL_PROJECT_ID (default local). Set it explicitly to the same id your executor will run under.

2a. API-key providers

For toolkits whose auth class is api_key (Stripe, SendGrid, Resend, Telegram), add the secret directly. The secret is read from the flag value and never printed:

Bash
pnpm eyeball-auth add stripe --user local_user --secret 'apiKey=<stripe-secret-key>'

2b. OAuth providers

For toolkits whose auth class is oauth2 and where you want a real, scope-carrying grant (Gmail, Google Calendar, Google Drive, Slack), register an OAuth application with the provider, then run the connect flow. The CLI opens a loopback callback (default http://127.0.0.1:53682/callback) and exchanges the code:

Bash
export EYEBALL_OAUTH_GMAIL_CLIENT_ID='<gmail-oauth-client-id>'
export EYEBALL_OAUTH_GMAIL_CLIENT_SECRET='<gmail-oauth-client-secret>'

pnpm eyeball-auth add gmail --user local_user

The grant carries the scopes the provider returns, so scope-enforcing providers (Gmail requires gmail.modify) pass. Use --manual for a copy/paste code exchange, --redirect-uri to override the callback, and --public-client for apps without a client secret.

2c. Personal access tokens as OAuth bearers

GitHub, Notion, Linear, and Airtable declare oauth2 auth, but each also accepts a long-lived bearer token (a GitHub PAT, a Notion internal-integration token, a Linear or Airtable PAT). eyeball-auth add always runs a full OAuth flow for an oauth2 toolkit, so use the token injector instead. The secret is read only from an environment variable, never from argv:

Bash
EYEBALL_STORE_ACCESS_TOKEN='<github-personal-access-token>' \
  pnpm eyeball-vault-put-token --user local_user --toolkit github

This stores an access-token-only oauth2 record with no scopes. That is correct for GitHub, Notion, Linear, and Airtable because their operations declare no required scopes. It is not sufficient for scope-enforcing providers such as Gmail — connect those with eyeball-auth add (2b) so the grant carries scopes.

3. Start the executor against the vault

Run the stock server with the same file, key, and project id you connected under:

Bash
EYEBALL_CREDENTIALS=local-vault \
EYEBALL_PROJECT_ID=proj_local \
EYEBALL_VAULT_PATH=.eyeball/vault.json \
EYEBALL_VAULT_KEY="$EYEBALL_VAULT_KEY" \
EYEBALL_API_KEYS="eyeball_local_key:proj_local" \
PORT=3000 \
  pnpm --filter @eyeball/executor start

Verify

Before starting the server, a read-only probe confirms a stored credential resolves and reaches the provider (it runs only tools annotated read-only):

Bash
pnpm eyeball-auth test github --user local_user \
  --tool github.list_issues --input '{"projectId":"owner/repo"}'

End to end, call the running executor's POST /v1/execute with your project key. Use a read-only operation so nothing is mutated:

Bash
curl -s http://127.0.0.1:3000/v1/execute \
  -H "Authorization: Bearer eyeball_local_key" \
  -H "Content-Type: application/json" \
  -d '{"tool":"gmail.search_emails","userId":"local_user","mode":"sync","input":{"query":"in:inbox","pageSize":5}}'

You can also drive the same executor from the SDK or an MCP client.

Provider base URLs

Most providers have a fixed public base URL and need no configuration. Four providers are tenant- or instance-scoped and ship a placeholder host, so a real deployment must point them at your instance with the toolkit's base-URL override:

ToolkitRequired override
odooEYEBALL_ODOO_BASE_URL
shopifyEYEBALL_SHOPIFY_BASE_URL
smtpEYEBALL_SMTP_BASE_URL
zendeskEYEBALL_ZENDESK_BASE_URL

Every other toolkit also exposes an EYEBALL_<TOOLKIT>_BASE_URL override, but only for testing or proxying — for example pointing a toolkit at Mockhouse. Manifest endpoint.baseUrlOverrideEnv is the only trusted endpoint override; a request cannot supply an arbitrary origin.

Local vault limitations