> ## Documentation Index
> Fetch the complete documentation index at: https://www.1password.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Use the SDK

> Create access requests, check their status, and read granted logins from your own code with the 1Password JavaScript SDK.

export const StatusBadge = ({children}) => <span className="op-status-badge not-prose">{children}</span>;

<StatusBadge>Partner preview</StatusBadge>

<Warning>
  Agentic Autofill integrations are built on the browser extension. If your product needs the SDK, contact 1Password for guidance before you build with it, so we can review our shared security model with you. With the SDK, your code can receive credential values, which changes what your platform is responsible for.
</Warning>

The 1Password JavaScript SDK lets your code create access requests and check their status without a browser. It can also read a granted login's username, password, and one-time password.

Use the SDK in two ways:

* **Request with the SDK, fill with the extension.** Your code asks for access before a browser or tab exists, and the extension fills when the agent reaches the sign-in page. Your code never receives a value.
* **Read with the SDK.** Your code receives the values. Your application becomes responsible for handling them safely and for how they're used, including filling them into a page.

The extension and the SDK work together:

| Capability | Browser extension | SDK |
| - | - | - |
| Create an access request | Yes | Yes |
| Check or wait for its status | Yes | Yes |
| Fill a granted login in a browser tab | Yes | No |
| Read granted login values | No | Yes |

## Install

The SDK is published as a developer preview. It needs Node.js 18 or later.

```bash theme={null}
npm install --save-exact @1password/sdk@0.0.0-credential-broker-dev-preview
```

<Warning>
  The developer preview has no backwards-compatibility guarantees. Pin the exact version, and expect changes before general availability.
</Warning>

## Create a client

Every call takes two credentials: the person's current access token, and the integration key from their connection. See [Connect a user](/agentic-autofill/partners/connect).

```typescript theme={null}
import { createOAuthClient } from "@1password/sdk";

const client = await createOAuthClient({
  accessToken,
  integrationKey,
});
```

* The client exposes only `client.credentialBroker`. The items, vaults, and secrets APIs belong to other client types and aren't available with OAuth credentials.
* The client is bound to the access token you passed in. Access tokens expire after 15 minutes. When you refresh, create a new client.
* The client sends every request to the API host named in the integration key, so a key issued on 1password.com talks to `api.1password.com`. There's no host setting. If your network restricts outbound traffic, allow that host.

## Create an access request

```typescript theme={null}
import { AccessRequestEntryType } from "@1password/sdk";

const accessRequest = await client.credentialBroker.accessRequests.create({
  goal: "Book a flight to Berlin and expense it",
  entries: [
    {
      type: AccessRequestEntryType.Login,
      parameters: { website: "https://www.lufthansa.com" },
      reason: "Sign in to search flights",
      keywords: ["lufthansa", "miles & more"],
    },
  ],
});
```

The fields and limits are the same as the extension's. See [Create an access request](/agentic-autofill/partners/access-requests).

You need `accessRequest.id` to check status, each `entries[].id` to match grants to entries, and the whole object to build the approval link.

## Send the person to approval

The SDK doesn't return an app link yet. Build it from the returned request, then [deliver it to the person's device](/agentic-autofill/partners/approval#deliver-the-approval-link):

```typescript theme={null}
const reference = Buffer.from(JSON.stringify(accessRequest)).toString("base64url");
const appLink = `onepassword://grant-brokered-access?access_request_reference=${reference}`;
```

## Wait for the decision

`getStatus` returns the current status immediately. Loop until the state isn't `pending`:

```typescript theme={null}
async function waitForDecision(accessRequestId: string) {
  const deadline = Date.now() + 5 * 60 * 1000;
  let delayMs = 1_000;

  while (Date.now() < deadline) {
    const status = await client.credentialBroker.accessRequests.getStatus(accessRequestId);
    if (status.state !== "pending") return status;
    await new Promise((resolve) => setTimeout(resolve, delayMs));
    delayMs = Math.min(delayMs * 2, 8_000);
  }
  throw new Error("access request still pending at timeout");
}
```

`getStatus` takes the request `id`, not its `path`. `resolved`, `denied`, and `failed` are final. If your loop times out, the request may still be pending; the timeout doesn't mean the request failed.

## Read a granted login

```typescript theme={null}
if (status.state === "resolved") {
  for (const granted of status.resolved) {
    const entry = accessRequest.entries.find(({ id }) => id === granted.entryId);
    const details = await client.credentialBroker.logins.getDetails(granted.reference);
    const login = await client.credentialBroker.logins.read(granted.reference);
    // details.websites: the websites saved on the login.
    // login.username, login.password, login.totp: any of them can be missing.
  }
}
```

* Pass `granted.reference` unchanged. It's an object, `{ reference: "accounts/.../capability/login/configurations/..." }`, and the read methods take the object, not the string inside it.
* `totp` is the current one-time password, computed when you read.
* Every read is a fresh call to 1Password. Read again when you need the value, rather than caching it. After the grant ends, the next read fails instead of returning a stale value.
* Keep values out of logs, prompts, and model context, and discard them as soon as they're used.

## Request with the SDK and fill with the extension

Switching from the SDK to the extension doesn't require a new request. Keep the request ID, entry IDs, and granted references, and pass them to the part of your system that drives the browser. When the agent reaches the sign-in page, wait for the extension to initialize, turn on Agentic Mode for the tab or the whole browser, then call [`api.agenticAutofill.v1.fillCredential`](/agentic-autofill/partners/fill#fill-a-granted-login) with the string inside the reference object, `granted.reference.reference`, as `resourcePath`. The SDK's read methods take the object, and the extension takes the string inside it. Use the same person's access token and integration key that created and resolved the request.

```typescript theme={null}
const fillResponse = await globalThis.api.agenticAutofill.v1.fillCredential({
  accessToken,
  integrationKey,
  resourcePath: granted.reference.reference,
  tabId,
});
```

This way 1Password fills and submits the login without exposing its username, password, or one-time password to your code.

## Reference

<ResponseField name="createOAuthClient(config)" type="Promise<OAuthClient>">
  Creates a client from `{ accessToken, integrationKey }`.
</ResponseField>

<ResponseField name="client.credentialBroker.accessRequests.create(params)" type="Promise<AccessRequest>">
  Registers an access request. `params` is `{ goal?, entries }`, with 1 to 5 entries of `{ type, parameters: { website }, reason?, keywords? }`. Returns the full request: `path`, `id`, `identity`, `state`, `createdAt`, `goal`, and `entries` with their IDs.
</ResponseField>

<ResponseField name="client.credentialBroker.accessRequests.getStatus(accessRequestId)" type="Promise<AccessRequestStatus>">
  Returns `{ state, resolved }`. `state` is `pending`, `resolved`, `denied`, or `failed`. `resolved` lists `{ entryId?, reference: { reference } }` for each granted login, and is empty for every other state.
</ResponseField>

<ResponseField name="client.credentialBroker.logins.getDetails(reference)" type="Promise<LoginDetails>">
  Returns `{ websites }`, the non-secret details of a granted login.
</ResponseField>

<ResponseField name="client.credentialBroker.logins.read(reference)" type="Promise<LoginCredential>">
  Returns `{ username?, password?, totp? }` for a granted login.
</ResponseField>

## Errors

The SDK rejects its promises on failure, so wrap calls in `try` and `catch`. The developer preview doesn't export typed error classes for these failures, so don't use `instanceof` checks. Match on the message.

| Error | Cause | What to do |
| - | - | - |
| Message contains `not authenticated`, or `you don't have the right permissions to access this resource` | The access token expired, or belongs to another connection. There's no dedicated error type for this yet, so match on the message. | Refresh the token, create a new client, and retry. |
| `InvalidCredentialReference` | The reference is malformed or doesn't name a login. | Pass `granted.reference` unchanged. |
| `CredentialReferenceAccountMismatch` | The reference belongs to a different account than the client. | Read it with the token and integration key of the connection that created the request. |
| `invalid oauth integration key: ... missing the 'ops_' prefix` | You passed something other than the raw integration key, such as the whole URL fragment or a URL-encoded copy. | Pass the `integration_key` value exactly as received. |
| `resource not found` on `create` | The integration key doesn't match the access token, for example a key from another person, client, or environment. | Use the token and key from the same connection. |
| `data did not match any variant of untagged enum` on a read | You passed the reference string instead of the object. | Pass `granted.reference`, the object. |
| Anything else | Transport and decryption failures arrive as internal errors. | Retry once if the call is safe to repeat. If it keeps failing, surface the failure. |


## Related topics

- [1Password SDK concepts](/sdks/concepts.md)
- [Use service accounts with 1Password SDKs](/service-accounts/setup-tutorial.md)
- [1Password SDKs](/sdks.md)
