A credential is a login or API key that nodes sign their requests with: a bearer token, an API key header, an OAuth2 sign-in to Google, a Telegram bot token, an SMTP server. You save it once in Dataset › Credentials, pick it in a node, and the run gets it ready to use. The values never travel back to the page.
Add a credential
- Go to Dataset › Credentials and click Add credential.
- Pick the Type. The form shows that type's fields.
- Give it a Name and fill the fields. Passwords and tokens are saved encrypted.
- Set Who can use it, like every dataset.
- Click Add.
When you edit a credential later, its password fields start blank and show a hint (Saved (sk-…a1b2)). Leave a field blank to keep the saved value.
Types
| Type | type | Signs a request with | Test |
|---|---|---|---|
| Header auth | http/header | one header (name: value) | — |
| Query auth | http/query | one query parameter | — |
| Basic auth | http/basic | Authorization: Basic … | — |
| Bearer auth | http/bearer | Authorization: Bearer <token> | — |
| JWT | http/jwt | none: it checks tokens that call in (a Webhook's auth), with a secret (HS256/384/512) or a PEM public key (RS256, ES256) | — |
| Custom auth (JSON) | http/custom | { "header": { … }, "query": { … } } | — |
| OAuth2 | http/oauth2 | Authorization: Bearer <access token> | connected, token fresh |
google/oauth2 | Authorization: Bearer <access token> | tokeninfo | |
| Telegram bot | telegram/bot | its values (the token goes in the URL) | getMe |
| Discord bot | discord/bot | Authorization: Bot <token> | users/@me |
| Discord webhook | discord/webhook | its values (the URL) | the webhook |
| Slack | slack/token | Authorization: Bearer <token> | auth.test |
| Notion | notion/token | Authorization: Bearer … + Notion-Version | users/me |
| Airtable | airtable/token | Authorization: Bearer <token> | meta/whoami |
| Qdrant | qdrant/key | api-key header | collections |
| Pinecone | pinecone/key | Api-Key header | indexes |
| Cohere | cohere/key | Authorization: Bearer <key> | check-api-key |
| MCP server | mcp/server | Authorization: Bearer <token> | — |
| SMTP | email/smtp | its values (host, port, security, user, password, from) | — |
| IMAP | email/imap | its values (host, port, security, user, password) | — |
The types without HTTP auth (Telegram, Discord webhook, SMTP, IMAP) are for the made for them. HTTP request offers only the types that sign a request.
External secrets
On Enterprise, a credential's value can live in your own secret manager and be read at run time:
- Add a HashiCorp Vault (address, token, KV mount, namespace), AWS Secrets Manager (region, access key; Endpoint only for a VPC endpoint or LocalStack) or Google Secret Manager (service account key JSON, project) credential.
- In any other credential, type a value as
secret://<that credential's name>/<path>#<key>, for examplesecret://Vault/shop/api#token. No#key: the whole secret.
| Store | <path> | The secret |
|---|---|---|
| Vault (KV v2) | the path under the mount, shop/api | its data JSON |
| AWS | the secret's id or ARN | its SecretString (JSON when it is) |
| the secret's name | its latest version (JSON when it is) |
Values are fetched every time the credential is resolved (each run, each test) and never stored in Mooflair.
Connect an OAuth2 credential
An OAuth2 credential (OAuth2, Google) holds your OAuth app's Client ID and Client secret, and signs in once:
- In your OAuth app at the service (for Google, the Google Cloud console), register this callback URL:
<your mooflair URL>/api/credential/oauth/callback. The dialog shows it. - Save the credential, then choose Connect in its ⋯ menu.
- Sign in at the service and allow access. You come back to Credentials with Connected.
How it works:
GET /api/credential/oauth/start?id=<credential>&workspace=<slug>sends you to the service with a signedstate(10 minutes), plus PKCE when the type asks for it (Google does; for OAuth2 set PKCE toyes).GET /api/credential/oauth/callbacktrades the code for tokens and saves them sealed.- Changing the client, the URLs or the scopes disconnects the credential, so connect it again after.
- An access token with a minute or less left is refreshed when a run starts (mooflair resolves it for the engine). A node that gets
401mid-run can ask for a fresh one (see ).
Test and usage
- Test sends the type's test request with the credential and checks the answer: a
2xxstatus, and for some types a field that must be set (ok,id). A type without a test request only checks that the credential resolves, which for OAuth2 means it's connected and its token can be refreshed. A passing test sets Tested. - Flows using it lists the flows whose nodes pick it. Removing a credential that's in use stops those flows from running until you pick another.
- Removing a credential sends it to the for 30 days.
In a run
A node field of kind dataset.credential holds the credential's id. When a run starts, mooflair:
- checks that the person behind the run may use it, and that its type fits the field;
- refreshes an OAuth2 token that's about to expire;
- puts it in the node's secrets, resolved:
type TCredentialResolved = {
id: string;
type: string; // "http/bearer"
value: Record<string, string>; // its fields, passwords opened
auth: { header: Record<string, string>; query: Record<string, string> }; // add these to a request
oauth: { token: string } | null; // the OAuth2 access token
};
A node reads it from ctx.field.<field id>. Its values are masked in every log line, like every other secret.
// ** In a script node: call an API with a credential field "credential"
const credential = ctx.field.credential;
const url = new URL("https://api.example.com/items");
for (const [key, value] of Object.entries(credential.auth.query)) url.searchParams.set(key, value);
const response = await fetch(url, { headers: credential.auth.header });
Where it's kept
| What | Where |
|---|---|
| Field values | value: one JSON string, sealed with AES-256-GCM (ENCRYPTION_KEY_<version>) |
| OAuth2 tokens | oauth.token: { access, refresh } as JSON, sealed |
| PKCE verifier while connecting | oauth.verifier, sealed, removed once connected |
The re-seals all three. Exports leave out the values unless Include passwords and keys is on, and never include OAuth2 tokens, so connect again after an import.
Add a type
A type is one folder, src/lib/credential/<service>/<name>/index.ts, plus a line in src/lib/credential/index.ts. Its labels go under component.credential.<service>.<name> (name, description, field.<id>):
export const credentialAcmeKey = {
type: "acme/key",
icon: "key-round",
field: [{ id: "key", kind: "password", required: true }],
auth: { kind: "header", header: { "X-Acme-Key": "{key}" } },
test: { method: "GET", url: "https://api.acme.com/me", check: "id" },
} satisfies TCredentialDefinition;
{<field id>} fills from its values, and {oauth.token} is the OAuth2 access token. Fields can use show like node fields.
FAQ
Can I see a saved token again?
No. Passwords and tokens are only shown as a hint. To change one, type the new value. To keep it, leave the field blank.
Why can't I pick my Telegram credential in HTTP request?
HTTP request lists only the credentials that sign a request (headers, query, basic, bearer, OAuth2, custom JSON). A Telegram bot token goes in the URL path, so the Telegram nodes use it instead.
My Google credential says "sign-in expired".
Google gave no refresh token, or revoked it. Choose Connect again. The Google type asks for offline access and consent each time, so a new connect returns a refresh token.