Lettuce can pull repos directly from Bitbucket Cloud through an OAuth2 consumer that your operator (the team running the Lettuce backend) registers once on Atlassian. Users in your account then click Connect Bitbucket, grant access, pick repos from a list, and Lettuce indexes them plus wires up auto-refresh webhooks — no copy-pasting URLs or webhook secrets. The manual “paste a git URL” flow keeps working alongside this — use it for public repos hosted anywhere git speaks.Documentation Index
Fetch the complete documentation index at: https://recipe.uselettuce.dev/llms.txt
Use this file to discover all available pages before exploring further.
There are two audiences here. Operators (you self-host Lettuce) do the
one-time Bitbucket OAuth consumer setup. Users (you sign in to Lettuce
on the web) just click Connect Bitbucket and pick repos.
For operators — one-time setup
You only do this once per Lettuce deployment. You can register the consumer either on a Bitbucket workspace (recommended for org installs) or on your personal account; the flow is identical.Open the OAuth consumer settings
For a workspace-owned consumer (recommended), go to
https://bitbucket.org/{workspace}/workspace/settings/api and replace
{workspace} with your workspace slug.For a user-owned consumer, go to Your profile and settings → All
workspaces → Personal settings → OAuth consumers.Click Add consumer.Fill in the consumer
- Name:
Lettuce - Description:
Pay-as-you-go codebase navigator - Callback URL:
https://<your-lettuce-host>/v1/integrations/bitbucket/callback— Lettuce verifies an HMAC-signedstateparam here, so the URL must be exact. Build it from yourCODEWAZE_PUBLIC_URL. - This is a private consumer: tick this. (Bitbucket refuses to issue refresh tokens to public consumers — Lettuce needs the refresh token so user access tokens don’t expire after two hours.)
Permissions
Tick exactly the following — least privilege for “list workspaces +
list repos + clone + manage webhooks”:
- Account: Read
- Workspace membership: Read
- Projects: Read
- Repositories: Read, Write
- Webhooks: Read and Write
Save and copy credentials
Click Save. Bitbucket reveals the Key (the consumer_key, which
becomes
BITBUCKET_APP_CLIENT_ID) and the Secret (which becomes
BITBUCKET_APP_CLIENT_SECRET). The secret is shown in full once;
treat it like a password.Set env vars on the Lettuce backend
CODEWAZE_PUBLIC_URL must already be set to a URL Bitbucket can reach
— the Callback URL above is built from it.How the security model works
- The OAuth consumer’s secret lives only in
BITBUCKET_APP_CLIENT_SECRETon the backend. It signs every token exchange via HTTP Basic Auth and is never written to the database. - When a user authorises Lettuce, Bitbucket returns an
access_token(~2-hour lifetime) and a long-livedrefresh_token. Both are persisted onprovider_installations. The access token is refreshed on demand whenever Lettuce needs to call the API — refresh failures flipmeta.needs_reauthso the UI prompts for re-login instead of failing the whole flow. - The OAuth-style
stateparam on the install callback is an HMAC of(account_id, provider, timestamp, nonce)signed with the backend’s admin token. This binds the install to the account that started it — even though Bitbucket’s redirect carries no session cookie. - The webhook secret is per-account, stored in
accounts.webhook_secret, and reused across every repo the account adds. Bitbucket signs each webhook payload with HMAC-SHA256 in theX-Hub-Signatureheader (same format as GitHub:sha256=<hex>), and Lettuce rejects any delivery whose signature doesn’t match.
For users — connect Bitbucket and add repos
This is what users see in the Lettuce UI once the operator has set up the OAuth consumer above.Connect Bitbucket
On the Bitbucket tab, click Connect Bitbucket →. You’re
redirected to bitbucket.org.Bitbucket shows the permissions Lettuce is requesting (Account: Read,
Workspace membership: Read, Projects: Read, Repositories: Read+Write,
Webhooks: Read+Write). Click Grant access.Bitbucket redirects you back to Lettuce.
Pick a repo
You land back on Repos with a green toast — “Bitbucket connected”.
The Add-repo dialog now lists every repo you have contributor or
higher access to, across every workspace you’re a member of. Filter
by
workspace/repo substring, then click one.Lettuce:- Registers the repo against your account.
- Auto-creates a webhook on it (
repo:pushevent) so future commits trigger a re-index. - Queues a clone + index job. Status moves from pending to indexing to ready — usually in a few seconds for small repos.
Disconnecting
Owners can disconnect the Bitbucket install from the Lettuce side atDELETE /v1/integrations/bitbucket (a UI button is on the roadmap).
Disconnecting:
- Removes the
provider_installationsrow, which discards your access + refresh tokens. - Leaves all your existing repos in place — they keep working as long as they’re public. Private repos will fail their next re-clone until you reconnect or switch them to a paste-URL with credentials.
Troubleshooting
The 'Connect Bitbucket' button is greyed out / missing
The 'Connect Bitbucket' button is greyed out / missing
Bitbucket redirects me back with 'invalid or expired install state'
Bitbucket redirects me back with 'invalid or expired install state'
The signed
state param is older than 15 minutes, or the backend’s
admin token has rotated since you started the install. Restart the
install from the Add-repo dialog.I see 'Bitbucket access was denied'
I see 'Bitbucket access was denied'
You clicked Cancel on Bitbucket’s authorisation screen, or your
workspace admin has restricted third-party OAuth consumers. Ask your
Bitbucket admin to allow the Lettuce consumer, then retry.
The picker says 'No repos visible in any of your workspaces'
The picker says 'No repos visible in any of your workspaces'
Lettuce only lists repos you have at least contributor access to.
If you only have read access to a repo, ask the owner to bump your
permission or to install Lettuce themselves and add the repo for the
workspace.
My private repo clones but later pushes don't trigger a refresh
My private repo clones but later pushes don't trigger a refresh
The auto-webhook step may have failed if the OAuth consumer wasn’t
granted Webhooks: Read and write. Re-create the consumer with the
correct permissions and reconnect from the Add-repo dialog.