> ## Documentation Index
> Fetch the complete documentation index at: https://docs.os.meetluno.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Use credentials in code

> Declare credentials on actions and control runtime resolution.

Credentials are consumed by [actions](https://docs.os.meetluno.ai/learn/actions/overview.md). Agents and workflows use credentials indirectly when they call those actions as tools or steps.

This page covers the runtime model: how to declare credentials, how Luno OS resolves a credential instance, and how to pin scopes when a run should use a project, organization, or user connection.

## Declare credentials on an action

For a **new credentialed custom integration**, prefer [`defineIntegration`](https://docs.os.meetluno.ai/learn/credentials/custom-integrations) so the credential lives on the app and actions use `app.action(...)`. This page shows the lower-level shape: a credential declaration listed on `defineAction`, which is what `defineIntegration` produces under the hood — and what you may still use for rare shared secrets that are not one service.

```ts src/actions/search.ts theme={null}
import { defineAction } from "@meetluno/luno/action";
import { defineCredential } from "@meetluno/luno/credentials";
import { z } from "zod";

const exa = defineCredential({
  key: "exa",
  fields: { apiKey: z.string() },
});

export const search = defineAction({
  slug: "search",
  name: "Search",
  description: "Searches the web via Exa for a query.",
  input: z.object({ query: z.string() }),
  output: z.object({ results: z.array(z.string()) }),
  credentials: [exa] as const,
  async run(input, credentials) {
    return callSearchApi(input.query, credentials.exa.apiKey);
  },
});
```

The credential key (`exa`) is the app/credential slug Luno OS looks up in the vault. The schema validates the resolved secret before your action runs.

> [!NOTE]
>
> Credentials belong on actions (or on an app that binds them to actions), not on `defineAgent()` or `defineWorkflow()`. Agents and workflows get credentials by running actions that declare them. Actions with no secrets stay as plain `defineAction` — no app required.

## Credential kinds

There are three runtime credential kinds:

| Kind | Authoring shape | Use it for |
| - | - | - |
| `api_key` | `defineCredential({ key, fields })` or `credential.static()` | Static API keys and secret fields |
| `oauth_managed` | `defineCredential({ key, kind: "oauth" })` or `credential.oauth()` | Native OAuth connections, such as Slack gateway credentials |
| `luno` | `defineCredential({ key, kind: "luno" })` or generated app credentials | Hosted catalog apps routed through Luno OS's platform MCP layer |

Most custom API keys are authored via `defineIntegration({ auth: "api_key", credential: … })`. Built-in app packages usually define the credential for you. Standalone `defineCredential` is for edge cases — see [custom apps and MCP](https://docs.os.meetluno.ai/learn/credentials/custom-integrations.md).

## Use actions in workflows and agents

Once an action declares credentials, use it like any other action.

```ts theme={null}
// Workflow step
export default defineWorkflow({
  slug: "research",
  name: "Research",
  description: "Searches the web for a query.",
  input: z.object({ query: z.string() }),
  output: z.object({ results: z.array(z.string()) }),
})
  .then(search)
  .commit();

// Agent tool
export default defineAgent({
  slug: "researcher",
  name: "Researcher",
  description: "Uses the search tool for current web research.",
  systemPrompt: "Use the search tool for current web research.",
  model: "openai/gpt-5.6-terra",
  tools: [search],
});
```

The runner resolves credentials immediately before the action executes. The model does not see the secret. In a workflow, credentials bind to the step id (`step:<id>`, the action slug unless you rename the step with `.as(id)`), so one assignment covers every execution of that step.

## Resolution order

When an action runs, Luno OS resolves each credential requirement using the run context.

Resolution order:

1. **Explicit selection:** a [credential assignment](https://docs.os.meetluno.ai/learn/credentials/connect-credentials.md#bind-a-credential-to-a-step-tool-or-poll-action) (or other platform-supplied instance id) for this consumer wins.
2. **Pinned scope:** if the action or credential is scoped with `.scope(...)`, only that scope is tried.
3. **Project default:** for unpinned credentials, try the default project credential.
4. **Organization default:** if no project credential resolves, try the default organization credential.
5. **Error:** if nothing resolves, the run fails with a missing-credentials error.

User credentials are intentionally not part of the unpinned chain. To use a user connection: pin `.scope("user")` on the action, then [assign a user credential](https://docs.os.meetluno.ai/learn/credentials/connect-credentials.md#bind-a-credential-to-a-step-tool-or-poll-action) to that workflow step, agent tool, or poll consumer.

## Pin a scope

Use `.scope()` when the action must use a specific credential scope.

```ts theme={null}
// As an agent tool: require a user-scoped credential (then assign one via CLI/API)
tools: [postMessage.scope("user")],

// As a workflow step: use the org credential
.then(postMessage.scope("organization"))
```

You can pin a credential definition or an action. Pinning an action applies the scope to all credentials that action declares.

| Scope | When to pin |
| - | - |
| `organization` | A shared org credential should always be used |
| `project` | The workflow should use a project-specific secret |
| `user` | The action should use someone's personal connected account |

Pinning `.scope("user")` alone does not pick a person or pull identity from the run. Associate a user credential with the step, tool, or poll consumer via an [assignment](https://docs.os.meetluno.ai/learn/credentials/connect-credentials.md#bind-a-credential-to-a-step-tool-or-poll-action) (`luno credentials assignments assign` or the API). Without that assignment, the run cannot resolve the user credential.

If a pinned scope has no matching credential (or assignment), Luno OS does not fall back to another scope.

## Defaults and multiple credentials

Within a scope, Luno OS can resolve a credential automatically when:

* There is exactly one credential instance for that app and scope.
* Or there are multiple instances, but exactly one is marked as default.

If there are multiple possible credentials and no single default, the run needs an explicit selection. This is why labels and defaults matter when you manage credentials from the Integrations page or CLI.

```bash theme={null}
luno credentials update <credential-id> --label "Production Slack" --default
```

To make one specific step, agent tool, or poll action use a particular instance regardless of the default, [bind it with an assignment](https://docs.os.meetluno.ai/learn/credentials/connect-credentials.md#bind-a-credential-to-a-step-tool-or-poll-action).

## Troubleshooting

When a credential can't be resolved, the action fails before `run` executes, with a message that points at the fix:

| Message | Cause | Fix |
| - | - | - |
| "This agent needs the `<key>` credential..." | Nothing resolved for that key at any tried scope | Connect the credential (`luno connect <key>` or the Integrations page), check the action's pinned scope, or [assign](https://docs.os.meetluno.ai/learn/credentials/connect-credentials.md#bind-a-credential-to-a-step-tool-or-poll-action) a credential to the step/tool |
| "Multiple `<key>` credentials are available. Pick one..." | More than one instance and no default | Mark one default, or [assign](https://docs.os.meetluno.ai/learn/credentials/connect-credentials.md#bind-a-credential-to-a-step-tool-or-poll-action) a specific instance |

For `.scope("user")`, connect a user-scoped credential and assign it to the workflow step, agent tool, or poll consumer. User scope is not inferred from a run actor or `actorId`. The Slack gateway runs as the workspace's connected account, not the individual who sent the message, so prefer `organization` or `project` scope there unless you have assigned a specific user credential.

## Credential stores

Credentials live in the hosted platform credential store and are materialized to workers on demand.

Deploying a project does not upload `.env`. If a deployed workflow uses an action with credentials, connect those credentials in the cloud first.

## Next steps

> **[Connect and manage apps](https://docs.os.meetluno.ai/learn/credentials/connect-credentials.md)**
>
> Create credential instances and choose their scopes.

> **[Actions](https://docs.os.meetluno.ai/learn/actions/overview.md)**
>
> Learn how actions define typed inputs, outputs, and credentials.

> **[Workflow steps](https://docs.os.meetluno.ai/learn/actions/workflow-steps.md)**
>
> Run credentialed actions as durable workflow steps.

> **[Agent tools](https://docs.os.meetluno.ai/learn/actions/agent-tools.md)**
>
> Attach credentialed actions as tools an agent can call.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.