---
slug: "latchkey"
source_type: "readme"
source_url: "https://cdn.jsdelivr.net/gh/imbue-ai/latchkey@main/README.md"
repo: "https://github.com/imbue-ai/latchkey"
source_file: "README.md"
branch: "main"
---
# Latchkey

[![npm](https://img.shields.io/npm/v/latchkey?style=flat-square)](https://npmjs.com/package/latchkey)
[![CI](https://img.shields.io/github/actions/workflow/status/imbue-ai/latchkey/test.yml?style=flat-square)](https://github.com/imbue-ai/latchkey/actions)
[![license](https://img.shields.io/npm/l/latchkey?style=flat-square)](LICENSE)
[![downloads](https://img.shields.io/npm/dm/latchkey?style=flat-square)](https://npmjs.com/package/latchkey)

Inject API credentials into local agent requests.

**[Full documentation](https://docs.imbue.com/latchkey)**

## Quick example

```bash
# User stores the credentials.
latchkey auth set slack -H "Authorization: Bearer xoxb-your-token"

# Agent makes http calls.
latchkey curl -X POST 'https://slack.com/api/conversations.create' \
  -H 'Content-Type: application/json' \
  -d '{"name":"something-urgent"}'
```

## Overview

Latchkey is a command-line tool that injects credentials into curl commands.

- `latchkey services list`
	- List third-party services (Slack, Google Workspace, Linear, GitHub, etc.) that are supported out-of-the-box.
    - (In simple cases, `latchkey services register` can be used to add basic support for a new service at runtime.)
- `latchkey curl <arguments>`
	- Automatically inject credentials into your otherwise standard curl calls to HTTP APIs.
	- Credentials must already exist (see below).
- `latchkey auth set <service_name> <curl_arguments>`
	- Manually store credentials for a service as arbitrary curl arguments.
- `latchkey auth browser <service_name>`
	- Open a browser login pop-up window and store the resulting API credentials.
    - This also allows agents to prompt users for credentials.
    - Only some services support this option.

Latchkey is primarily designed for AI agents. By invoking
Latchkey, agents can utilize user-provided credentials or prompt
the user to authenticate, then continue interacting with HTTP
APIs using standard curl syntax. No custom integrations or
embedded credentials are required.

Unlike OAuth-based flows or typical MCP-style integrations,
Latchkey does not introduce an intermediary between the agent
and the service. When the `browser` command is used, requests are made
directly on the user’s behalf, which enables greater flexibility
at the cost of formal delegation: agents authenticate as the
user.

Latchkey integrates with [Detent](https://github.com/imbue-ai/detent)
to let you define fine-grained HTTP permissions for agents.

If a service you need isn’t supported out of the box, the
[new service registering mechanism](#entirely-new-services)
may help. Otherwise, contributions are welcome! See the [development docs](https://github.com/imbue-ai/latchkey/blob/HEAD/docs/development.md)
for details.

You can also [leave us a note](https://github.com/imbue-ai/latchkey/issues/56).

## Installation

### Prerequisites

- `curl`, `node` and `npm` need to be present on your system in reasonably recent versions.
- The `latchkey auth browser` subcommand requires a graphical environment.

### Steps

```bash
npm install -g latchkey

# Optionally, if you intend to use `latchkey auth browser`:
latchkey ensure-browser
```

The `ensure-browser` command discovers and configures a browser
for Latchkey to use. It searches for Chrome, Chromium, or Edge
on your system. If none is found, it downloads Chromium via
Playwright.

## Agent integrations

Warning: giving AI agents access to your API credentials is
potentially dangerous, especially when using the `auth browser`
feature. They will be able to perform most of the actions you
can. Only do this if you're willing to accept the risks.


### Using skills.sh

```bash
npx skills add imbue-ai/latchkey
```

### From ClawHub

```bash
npx clawhub install latchkey
```

### As a Pi package

```bash
pi install npm:latchkey
```

### Manually

The exact steps will differ depending on the agent. Taking OpenCode as an example:

```bash
mkdir -p ~/.opencode/skills/latchkey
latchkey skill-md > ~/.opencode/skills/latchkey/SKILL.md
```

### Using the Python `llm` tool

See [integrations/llm-latchkey/README.md](https://github.com/imbue-ai/latchkey/blob/HEAD/integrations/llm-latchkey/README.md).
Also check out our [llm-webchat](https://github.com/imbue-ai/llm-webchat) web UI that can be used together with Latchkey!


## Demo

![Image](https://github.com/user-attachments/assets/953eae6e-35ab-4d64-91f3-98b6843c502d)


## Direct usage

Let's revisit the initial example:

```bash
latchkey curl -X POST 'https://slack.com/api/conversations.create' \
  -H 'Content-Type: application/json' \
  -d '{"name":"something-urgent"}'
```

Notice that `-H 'Authorization: Bearer ...'` is absent. This is
because Latchkey injects stored credentials automatically. To
set up credentials for a service (Slack in this example), run:

```bash
latchkey auth browser slack
```

This opens the browser with a login screen. After you log in, Latchkey extracts
the necessary API credentials from the browser session, closes the browser, and
stores the credentials so that they can be reused.

Alternatively, you can provide credentials manually:

```bash
latchkey auth set slack -H "Authorization: Bearer xoxb-your-token"
```

`latchkey curl` passes your arguments straight through to `curl`
so you can use the same interface you are used to. The return
code, stdout and stderr are passed back from curl to the caller
of `latchkey`.


### Multiple accounts

Credentials for a service can be stored under several accounts.
An account is a string that uniquely identifies the account
behind the credentials - typically an e-mail, but for some
services it may be an id. Use the global `--account` option to
select one:

```bash
# Store credentials for a specific account.
latchkey --account bob@example.com auth set slack -H "Authorization: Bearer xoxb-..."

# Use them in a request.
latchkey --account bob@example.com curl https://slack.com/api/auth.test
```

When `--account` is omitted, the single stored account is used
automatically. If a service has more than one stored account,
`--account` becomes required. Credentials stored without an
account live under a "default" account (empty string, `""`).

Browser logins (`latchkey auth browser`) determine the account
automatically. The `--account` option here merely allows users
to specify which existing OAuth client configuration to use for
the login (selecting one associated with an existing account) if
needed.

### Self-hosted services

For services that can be self-hosted, like GitLab, first make Latchkey aware of your service instance:

```bash
latchkey services register my-gitlab-instance --service-family=gitlab --base-api-url="https://gitlab.example.com/api/v4/"
```

Then continue as usual.

```bash
latchkey auth set my-gitlab-instance -H "PRIVATE-TOKEN: <token>"

# Agents can then call the API.
latchkey curl https://gitlab.example.com/api/v4/user
```


### Entirely new services

If you want to use Latchkey with a service that is not in the
list of supported built-in services, you can still use the
mechanism described above to register a new service at runtime:

```bash
latchkey services register mastodon --base-api-url="https://mastodon.social/api/v1/"
latchkey auth set mastodon -H "Authorization: Bearer <your_access_token>"

# Agents can then call the service:
latchkey curl https://mastodon.social/api/v1/timelines/public?limit=2
```

User-registered services only support authentication via static curl arguments provided through `latchkey auth set`.


### Indirect credentials

Some services can't express their credentials as static curl
arguments. For example:

- AWS requires a signature that changes with each request.
- Telegram expects bot tokens to be directly part of the URL.

In similar cases, when supported, you can use the `latchkey auth set-nocurl` command, e.g.
like this:

```bash
latchkey auth set-nocurl aws <access-key-id> <secret-access-key>
```

Latchkey will then modify subsequent `latchkey curl` requests as
needed. You can find more information (including the expected
signature) by calling `latchkey services info <service_name>`.

### Remembering API credentials

Your API credentials and browser state are encrypted and stored
by default under `~/.latchkey`. They are never transmitted
anywhere beyond the endpoints specified by the actual curl
calls.


### Inspecting the status of stored credentials

Calling `latchkey services info <service_name>` will show
information about the service, including a `credentials` object
keyed by account (the default account uses the empty string).
Each entry reports a credentials status of:

- `invalid`
- `valid`
- `unknown` (for user-registered services)

A service with no stored credentials shows an empty `credentials`
object.

### Clearing credentials

Remembered API credentials can expire. The caller of `latchkey
curl` will typically notice this because the calls will start returning
HTTP 401 or 403. To verify that, first call `latchkey services info`, e.g.:

```bash
latchkey services info discord
```

If the credentials status is `invalid`, it means the Unauthorized/Forbidden
responses are caused by invalid or expired credentials rather than insufficient
permissions. In that case, log in again:

```bash
latchkey auth browser discord
```

Or alternatively:

```bash
latchkey auth set discord -H "Authorization: ..."
```


### Clearing credentials and logins

In case you want to remove stored API credentials, use the `auth clear` subcommand.

```bash
latchkey auth clear discord
```

To clear all stored data (both the credential store and browser state file), run:

```bash
latchkey auth clear
```


### Re-encrypting credentials

If you want to export your stored credentials encrypted with
a different key or containing only some of the credentials (for
example to move them to another machine) , use the `auth re-encrypt`
subcommand.


### Permissions

Optionally, you can specify rules for approving / rejecting
requests by creating the `permissions.json` file in the Latchkey
directory (`~/.latchkey/permissions.json`). For example:

```json
{
  "rules": [
    {"google-gmail-api": ["google-gmail-read-all"]},
    {"slack-api": ["slack-read-all"]}
  ]
}
```

This would mean that:

- When accessing the Gmail or the Slack API, only read actions are allowed.
- No requests are allowed to any other domains.

Ideally make the file read-only: `chmod -w ~/.latchkey/permissions.json`.
In the gateway mode, you can use [permission overrides](#permission-overrides) to let
different callers claim different permission policies.
For more details, check out the [permission docs](https://docs.imbue.com/latchkey/basics/permissions).


### Gateway mode

People sometimes run agents in isolated sandboxes for higher
security. Using gateway mode lets you extend the isolation
to Latchkey itself. Run this to start a Latchkey server on the
machine where your main configuration lives:

```bash
# Listens on localhost:1989 by default.
# You can override this using the LATCHKEY_GATEWAY_LISTEN_HOST
# and LATCHKEY_GATEWAY_LISTEN_PORT environment variables.
latchkey gateway
```

Suppose you run an agent inside a container on the same machine,
with the gateway port mapped into the container. Then setting
`LATCHKEY_GATEWAY=http://localhost:1989` in your container will
route your agent's `latchkey` calls to the host Latchkey
(limited to a selected safe subset of commands). That way the
agent won't be able to tamper with your Latchkey configuration
while still being able to use Latchkey itself as intended.

#### Password

You can additionally protect the gateway with a shared password.
Set `LATCHKEY_GATEWAY_LISTEN_PASSWORD` on the machine running
`latchkey gateway` (the value the server requires) and
`LATCHKEY_GATEWAY_PASSWORD` on the client (the value the CLI
sends). When the two match, the request is accepted; otherwise
the gateway responds with `401`. The password is carried in the
`X-Latchkey-Gateway-Password` header and is never forwarded
upstream, so it stays internal to Latchkey.


#### Permission overrides

Individual gateway requests can also override which
`permissions.json` file the gateway consults by sending a
`X-Latchkey-Gateway-Permissions-Override` header. The header
value is a minimal HS256 JWT whose only payload field,
`permissionsConfig`, is an absolute path. The signing key is
derived from your Latchkey encryption key, so only someone with
access to that key can mint valid pointers. Generate one with:

```bash
latchkey gateway create-jwt /etc/latchkey/permissions-readonly.json
```

(Pass `--no-validate` to skip the existence check, e.g. when
the target file lives on a different machine.) The gateway
responds with `401` for invalid or missing-signature JWTs and
with `400` when the JWT is valid but the referenced file does
not exist.

On the client side, set `LATCHKEY_GATEWAY_PERMISSIONS_OVERRIDE`
to the JWT and the CLI will attach it to every outgoing gateway
request automatically (analogous to `LATCHKEY_GATEWAY_PASSWORD`).


#### Extensions

You can extend the gateway with your own HTTP endpoints. For
details, see the [extensions docs](https://github.com/imbue-ai/latchkey/blob/HEAD/docs/extensions.md).


### Other configuration

You can set these environment variables to override certain
defaults:

- `LATCHKEY_DIRECTORY`: path to the directory where Latchkey stores its data (defaults to `~/.latchkey`)
- `LATCHKEY_CURL`: path to the curl binary
- `LATCHKEY_KEYRING_SERVICE_NAME`, `LATCHKEY_KEYRING_ACCOUNT_NAME`: identifiers that are used to store the encryption password in your keyring
- `LATCHKEY_ENCRYPTION_KEY`: override the encryption key, e.g. when a keyring is not available. Example: `export LATCHKEY_ENCRYPTION_KEY="$(openssl rand -base64 32)"`
- `LATCHKEY_DISABLE_BROWSER`: when set to a non-empty value, disables the browser login flow; commands that would trigger a browser login (`auth browser`, `auth browser-prepare`) will fail with an error instead
- `LATCHKEY_EPHEMERAL_BROWSER`: when set to a non-empty value, browser flows operate similarly to the browser incognito mode. Useful when logging into multiple accounts.
- `LATCHKEY_DISABLE_COUNTING`: when set to a non-empty value, disables daily usage counting.
- `LATCHKEY_DISABLE_CREDENTIALS_REFRESH`: when set to a non-empty value, expired credentials are never refreshed. (Useful when the credentials are shared and refreshing in one place would risk exhausting the refresh token for the other one.)
- `LATCHKEY_PERMISSIONS_CONFIG`: override the `permissions.json` location.
- `LATCHKEY_PERMISSIONS_DO_NOT_USE_BUILTIN_SCHEMAS`: do not use the built-in permission definitions.
- `LATCHKEY_PASSTHROUGH_UNKNOWN`: if set, Latchkey will forward requests (via `latchkey curl` or gateway) even if no credentials are injected.
- `LATCHKEY_HIDE_BUILTIN_SERVICES`: comma-separated list of built-in service names to disable/hide.
- `LATCHKEY_GATEWAY`: when set to a base URL (e.g. `http://localhost:1989`), the CLI delegates commands to a remote Latchkey gateway instead of running them locally. Commands that change local state (`auth set`, `auth clear`, `auth re-encrypt`, `services register`, `ensure-browser`, `gateway`) cannot run in this mode.
- `LATCHKEY_GATEWAY_LISTEN_HOST`, `LATCHKEY_GATEWAY_LISTEN_PORT`: default address and port the local `latchkey gateway` command binds to when `--host` / `--port` are not supplied (defaults: `localhost`, `1989`). Distinct from `LATCHKEY_GATEWAY`, which configures a *remote* gateway URL.
- `LATCHKEY_GATEWAY_PASSWORD`: optional shared secret used by the client side. When set together with `LATCHKEY_GATEWAY`, the CLI sends the value in the `X-Latchkey-Gateway-Password` header on every outgoing gateway request.
- `LATCHKEY_GATEWAY_LISTEN_PASSWORD`: optional shared secret used by the server side. When set, `latchkey gateway` rejects (with `401`) any request that does not present the same value in the `X-Latchkey-Gateway-Password` header. The header is stripped before requests are forwarded upstream.
- `LATCHKEY_GATEWAY_PERMISSIONS_OVERRIDE`: optional permissions-override JWT (see `latchkey gateway create-jwt`) used by the client side. When set together with `LATCHKEY_GATEWAY`, the CLI sends the value in the `X-Latchkey-Gateway-Permissions-Override` header on every outgoing gateway request, causing the remote gateway to enforce the permissions.json file referenced by the JWT instead of its default one.
- `LATCHKEY_APP_NAME_PREFIX`: prefix used for the app, project, and OAuth client names that Latchkey creates on your behalf inside third-party consoles (defaults to `Latchkey`).

All of the above settings, except for `LATCHKEY_DIRECTORY`,
`LATCHKEY_ENCRYPTION_KEY`, `LATCHKEY_GATEWAY_PASSWORD`,
`LATCHKEY_GATEWAY_LISTEN_PASSWORD`, and
`LATCHKEY_GATEWAY_PERMISSIONS_OVERRIDE`, can alternatively be
specified in the `settings` section of `config.json` inside the
Latchkey directory.  In case of a clash, environment variables
override `config.json` values.

```json
{
  "settings": {
    "curlCommand": "/usr/local/bin/curl",
    "keyringServiceName": "latchkey",
    "keyringAccountName": "encryption-key",
    "browserDisabled": false,
    "browserEphemeral": false,
    "countingDisabled": true,
    "permissionsConfig": "/etc/latchkey/permissions.json",
    "permissionsDoNotUseBuiltinSchemas": false,
    "passthroughUnknown": false,
    "hideBuiltinServices": ["telegram", "yelp"],
    "gateway": "http://localhost:1989",
    "gatewayListenHost": "localhost",
    "gatewayListenPort": 1989,
    "appNamePrefix": "Latchkey"
  }
}
```


## Disclaimers

- This is still a work in progress.
- Latchkey has been created with the help of AI-assisted coding tools with careful human curation.
- Invoking `latchkey auth browser ...` can sometimes have side effects in the form of
  new API keys being created in your accounts (through browser automation).
- Using agents for automated access may be prohibited by some services' ToS.
- Unless `LATCHKEY_DISABLE_COUNTING` is set, once a day, when invoking `latchkey`, a GET request is sent to [goatcounter.com](https://goatcounter.com). We don't store any sort of private information and don't track anything beyond a simple daily count of active users.
- We reserve the right to change the license of future releases of Latchkey.
- Latchkey was not tested on Windows.

## Currently supported services

Latchkey currently offers varying levels of support for the
following services: AWS, Calendly, Discord, Dropbox, Figma, GitHub, GitLab,
Gmail, Google Analytics, Google Calendar, Google Docs, Google Drive, Google Sheets,
Linear, Mailchimp, Notion, Ramp, Sentry, Slack, Stripe, Telegram, Todoist, Yelp, Zoom, and more.
