Skip to content

Authentication & credentials

How the SDK authenticates with the Edgible API: which credentials you can supply, how the SDK finds them when you supply none, where sessions are stored, and how token refresh works. You should never need to touch a bearer token yourself — the client attaches and refreshes it on every request.

If you have logged in with the edgible CLI on the same machine, the SDK picks up that session automatically:

import { createClient } from '@edgible-team/sdk';
const client = createClient(); // reuses the CLI's login session
const me = await client.auth.me();

On Node, the default credential store reads (and writes) the same config.json the CLI uses, so the CLI and SDK share one login session.

Pass credentials explicitly via createClient({ credentials }). Three input shapes exist today:

const client = createClient({
credentials: { type: 'password', email: 'you@example.com', password: process.env.EDGIBLE_PASSWORD! },
});

Login happens lazily on the first request. The resulting tokens (including a refresh token) are saved to the credential store, and the client refreshes them automatically from then on. This is the most durable option for long-running processes.

You can also log in imperatively — await client.auth.login(email, password) — which persists the session to the store immediately.

const client = createClient({
credentials: { type: 'token', accessToken: myIdToken, refreshToken: myRefreshToken },
});

A pre-obtained bearer token. Note: the platform’s bearer token is the Cognito idToken — whatever string you pass as accessToken is attached verbatim as Authorization: Bearer <token>. If you omit refreshToken, the client cannot refresh: once the token expires, requests fail with an AuthenticationError (code: 'AUTH_TOKEN_EXPIRED').

{ type: 'serviceAccount', keyId, secret } // accepted by the types, but…

Org-scoped service accounts are a planned platform feature that needs backend support which has not shipped. The SDK accepts the shape so adoption is drop-in later, but using it today throws a ConfigurationError with code: 'SERVICE_ACCOUNT_NOT_SUPPORTED' on the first request. The same applies to the EDGIBLE_API_KEY / EDGIBLE_API_SECRET environment variables.

CI today: until service accounts land, authenticate CI jobs with one of the mechanisms that does work:

// Option A — a dedicated account's password from CI secrets (self-refreshing):
import { createClient, InMemoryCredentialStore } from '@edgible-team/sdk';
const client = createClient({
credentials: { type: 'password', email: process.env.CI_EMAIL!, password: process.env.CI_PASSWORD! },
credentialStore: new InMemoryCredentialStore(), // don't persist tokens on the runner
});
Terminal window
# Option B — inject a short-lived token; no code changes needed:
export EDGIBLE_TOKEN="<idToken>"

Option B has no refresh token, so it suits jobs shorter than the token’s lifetime.

When you don’t pass credentials, the client resolves one by trying sources in order and stopping at the first that yields a credential:

  1. ExplicitcreateClient({ credentials }), when provided.
  2. EnvironmentEDGIBLE_TOKEN (a bearer token), then EDGIBLE_API_KEY + EDGIBLE_API_SECRET (service account — currently throws on use, see above).
  3. Credential store — the configured store; on Node this is the CLI’s config.json session.
  4. Instance metadata — reserved for managed devices (Node-only; currently yields nothing).

Resolution happens once, on the first request. If no source yields a credential, the request throws an AuthenticationError with code: 'AUTH_NO_CREDENTIALS'.

You can replace the whole chain with createClient({ credentialProvider }) — any object with a resolve(ctx) method returning a ResolvedCredential or null. The building blocks (chainProvider, envProvider, storeProvider, explicitProvider) are exported if you want to compose your own.

Persistence sits behind the CredentialStore interface (load / save / clear):

StoreDefault onBehaviour
FileCredentialStoreNodeReads/writes the CLI’s config.json, so the SDK and CLI share one session. Only the token fields are touched — the rest of the file is preserved. Exported from @edgible-team/sdk/node.
InMemoryCredentialStoreBrowserHolds tokens in memory only; nothing is ever written to disk. Also the right choice for CI.

The shared config.json lives in the platform user-data directory:

  • Linux: $XDG_DATA_HOME/edgible/config.json, falling back to ~/.local/share/edgible/config.json
  • macOS: ~/Library/Application Support/Edgible/config.json

Override the store with createClient({ credentialStore }); FileCredentialStore also accepts a filePath option to point at a different file.

  • Tokens are refreshed proactively, 5 minutes before the exp claim of the idToken.
  • Refresh uses the stored refresh token against the platform’s refresh endpoint; the refreshed session is saved back to the credential store.
  • If a request still gets a 401 (e.g. the token was revoked), the client performs exactly one forced refresh and replays the request once. A second 401 surfaces as an AuthenticationError — no silent retry loop.
  • A token with no refresh token fails fast on expiry with code: 'AUTH_TOKEN_EXPIRED'.

To end a session, await client.auth.logout() — this calls the backend and clears the stored credentials (for FileCredentialStore, it removes only the token fields from config.json).

Each client targets one platform stage. Resolution order: explicit env option → EDGIBLE_ENV environment variable → 'prod'.

const client = createClient({ env: 'prod' }); // dev | test | uat | prod

The base URL derives from the stage (https://api.v2.<env>.edgible.com). To point somewhere else entirely, pass baseUrl, which overrides env:

const client = createClient({ baseUrl: 'https://api.example.internal' });

An unrecognised stage value throws a ConfigurationError (code: 'CONFIG_INVALID'); an empty baseUrl throws code: 'CONFIG_MISSING_BASE_URL'.

  • Errors — the AuthenticationError / ConfigurationError types referenced above.