Files
supabase/apps/docs/content/guides/getting-started/api-keys.mdx
Miranda Limonczenko 0d465e7b5f chore(ui): Remove 'tip' from Admonition (#48419)
Closes FE-3966

## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## Problem

- The admonition uses both 'tip' and 'note', but the visual distinction
has long-ago collapsed.
- 'Note' is used far more frequently than 'tip'
- The two are very similar and it is confusing to know which one to use
when they are visually identical

## Solution

Collapse 'tip' and 'note' into one by removing all places where there is
'tip' and updating all references to 'tip' into 'note'.

**Note:** This PR also resolves new broken links flagged by the E2E docs
checker. It may move to another PR since E2Es keep erroring.

### Specific changes

See below for an AI-generated list of changes:

- **Type system** — removed `'tip'` from `AdmonitionType`, its
`TYPE_TO_VARIANT`/`TYPE_LABEL` entries, and the test case in
[`packages/ui-patterns/src/Admonition/](packages/ui-patterns/src/Admonition/)
- **Remark plugin** —
[remarkAdmonition.ts](apps/docs/lib/mdx/plugins/remarkAdmonition.ts) now
maps mkdocs `tip` → `note`
- **Lint allowlist** — `tip` dropped from `supa-mdx-lint.config.toml`
- **Content migration** — all 109 files with `type="tip"` (across
`apps/docs`, `apps/www`, `apps/studio`) converted to `type="note"`; zero
remaining hits confirmed by repo-wide grep
- **Style guide** — `CONTRIBUTING.md` and `contributing/content.mdx`
updated to describe 4 admonition types instead of 5

### Usage before implementation

See the usage table that points toward 'note' as being dominant across
all apps:

Here's the usage table:

| Location | `note` | `tip` |
|---|---|---|
| apps/docs | ~480 | ~143 |
| apps/studio | 34 | 6 |
| apps/www (blog) | 19 | 3 |
| packages/ui-patterns (tests) | 3 | 1 (parametrized) |
| design-system / ui-library / packages/ui / packages/common | 0–1 (test
fixture only) | 0 |

## Preview links


| App | Page | Search text (Ctrl+F) | Verify |
|---|---|---|---|
| docs |
[/docs/guides/ai-tools/byo-mcp](https://docs-git-admonition-collapse-note-tip-supabase.vercel.app/docs/guides/ai-tools/byo-mcp)
| official MCP TypeScript SDK | callout's aria-label="Note" |
| docs |
[/docs/guides/ai-tools/mcp](https://docs-git-admonition-collapse-note-tip-supabase.vercel.app/docs/guides/ai-tools/mcp)
| MCP server is available at | callout's aria-label="Note" |
| docs |
[/docs/guides/ai/python-clients](https://docs-git-admonition-collapse-note-tip-supabase.vercel.app/docs/guides/ai/python-clients)
| Click Connect at the top of any project page | callout's
aria-label="Note" |
| docs |
[/docs/guides/auth/audit-logs](https://docs-git-admonition-collapse-note-tip-supabase.vercel.app/docs/guides/auth/audit-logs)
| Disabling Postgres storage reduces your database storage costs |
callout's aria-label="Note" |
| docs |
[/docs/guides/database/tables](https://docs-git-admonition-collapse-note-tip-supabase.vercel.app/docs/guides/database/tables)
| access a custom schema through the Supabase Data API | callout's
aria-label="Note" |
| docs |
[/docs/guides/troubleshooting/edge-function-404-error-response](https://docs-git-admonition-collapse-note-tip-supabase.vercel.app/docs/guides/troubleshooting/edge-function-404-error-response)
| Always configure an appropriate time frame | callout's
aria-label="Note" (was single-quoted type='tip') |
| www | [blog:
cli-v2-config-as-code](https://zone-www-dot-com-git-admonition-collapse-note-tip-supabase.vercel.app/blog/cli-v2-config-as-code)
| Detecting config drift | callout's aria-label="Note" |
| www | [blog:
cli-v2-config-as-code](https://zone-www-dot-com-git-admonition-collapse-note-tip-supabase.vercel.app/blog/cli-v2-config-as-code)
| Setting Edge Function secrets | callout's aria-label="Note" |
| www | [blog:
nosql-mongodb-compatibility-with-ferretdb-and-flydotio](https://zone-www-dot-com-git-admonition-collapse-note-tip-supabase.vercel.app/blog/nosql-mongodb-compatibility-with-ferretdb-and-flydotio)
| If your network supports IPv6 connections | callout's
aria-label="Note" |

Note: the `www` rows use the `zone-www-dot-com` preview host, not the
`docs` one you gave — since blog pages are served from the www app, not
docs.


## Manual testing

1. Open preview links for affected pages.
2. Inspect. Open console.
3. Paste the following in and see there is no 'Tip' on the page:
```
document.querySelectorAll('[role="alert"]').forEach(el => console.log(el.getAttribute('aria-label'), el.textContent.slice(0,60)))
```



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Documentation**
* Standardized informational callouts across docs and tutorials from
**“Tip”** to **“Note”**, updating multiple examples and guidance blocks.
* Updated a few related doc references/links and conditional “Next
steps” content.
* **UI Updates**
* Switched various in-app banners and notices to the **“Note”** style
variant.
* **Bug Fixes / Improvements**
* Removed support for the retired **“Tip”** callout type and aligned
docs linting, component behavior, and aria labeling to the remaining
admonition types.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-29 09:26:50 -07:00

142 lines
13 KiB
Plaintext

---
id: 'api-keys'
title: 'Understanding API keys'
description: "First-layer protection for your project's data"
---
Supabase gives you fine-grained control over which application components are allowed to access your project through API keys.
<Admonition type="note" title="Looking for your API Keys?">
In most cases, you can get the correct key from [the Project's **Connect** dialog](/dashboard/project/_?showConnect=true), but if you want a specific key, you can find all keys in the [**Settings > API Keys**](/dashboard/project/_/settings/api-keys/) section of the Dashboard:
</Admonition>
API keys provide the first layer of authentication for data access. Auth then builds upon that. This chart covers the differences:
| Responsibility | Question | Answer |
| ---------------------------------- | ---------------------------------- | -------------------------------------------------- |
| API keys | **What** is accessing the project? | Web page, mobile app, server, Edge Function... |
| [Supabase Auth](/docs/guides/auth) | **Who** is accessing the project? | Monica, Jian Yang, Gavin, Dinesh, Laurie, Fiona... |
## Overview
An API key authenticates an application component to give it access to Supabase services. An application component might be a web page, a mobile app, or a server. The API key _does not_ distinguish between users, only between applications.
There are 4 types of API keys that you can use with Supabase:
| Type | Format | Privileges | Availability | Use |
| ---------------------------------------------------------- | ---------------------------------------------------------------- | ---------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Publishable&nbsp;key | <span className="whitespace-nowrap!">`sb_publishable_...`</span> | Low | Platform | Safe to expose online: web page, mobile or desktop app, GitHub actions, CLIs, source code. |
| Secret&nbsp;keys | <span className="whitespace-nowrap!">`sb_secret_...`</span> | Elevated | Platform | **Only use in backend components of your app:** servers, already secured APIs (admin panels), [Edge Functions](/docs/guides/functions), microservices, etc. They provide _full access_ to your project's data, bypassing [Row Level Security](/docs/guides/database/postgres/row-level-security). |
| <span className="whitespace-nowrap!">`anon`</span> | JWT (long lived) | Low | <span className="whitespace-nowrap!">Platform, CLI</span> | Legacy version of publishable keys. |
| <span className="whitespace-nowrap!">`service_role`</span> | JWT (long lived) | Elevated | <span className="whitespace-nowrap!">Platform, CLI</span> | Legacy version of secret keys. |
<Admonition type="note" title="Creating new keys does not revoke your legacy keys">
Both key types work simultaneously. Creating publishable and secret keys adds them _alongside_ your existing `anon` and `service_role` keys without affecting them — your legacy keys keep working. They remain valid until you explicitly disable them in the [**Settings > API Keys**](/dashboard/project/_/settings/api-keys/) section of the Dashboard which is a separate step. See [Migrating to new API keys](/docs/guides/getting-started/migrating-to-new-api-keys) for the full process.
</Admonition>
<$Partial path="api_keys_deprecation.mdx" />
## Publishable keys
Publishable keys identify the public components of your application. Public components run in environments where it is impossible to secure any secrets. These include:
- Web pages, where the key is bundled in source code.
- Mobile or desktop applications, where the key is bundled inside the compiled packages or executables.
- CLI, scripts, tools, or other pre-built executables.
- Other publicly available APIs that return the key without prior additional authorization.
These environments are always considered public because anyone can retrieve the key from the source code or build artifacts.
### Interaction with Supabase Auth
Using a publishable key does not mean that your user is anonymous. You can authenticate your application with the publishable key, while your user is authenticated (via Supabase Auth) with their personal JWT:
| Key | User logged in via Supabase Auth | Postgres role used for RLS, etc. |
| --------------- | -------------------------------- | -------------------------------- |
| Publishable key | No | `anon` |
| Publishable key | Yes | `authenticated` |
### Security considerations
Publishable keys are not intended to protect from the following, since key retrieval is always possible from a public component:
- Static or dynamic code analysis and reverse engineering attempts.
- Use of the Network inspector in the browser.
- Cross-site request forgery, cross-site scripting, phishing attacks.
- Man-in-the-middle attacks.
When using a publishable key, access to your project's data is guarded by Postgres via the built-in `anon` and `authenticated` roles. For full protection make sure:
- You have enabled Row Level Security on all tables.
- You regularly review your Row Level Security policies for permissions granted to the `anon` and `authenticated` roles.
- You do not modify the role's attributes without understanding the changes you are making.
Your project's [Security Advisor](/dashboard/project/_/advisors/security) constantly checks for common security problems with the built-in Postgres roles. Make sure you carefully review each finding before dismissing it.
## What secret keys allow access to
Unlike publishable keys, secret keys allow elevated access to your project's data. It is meant to be used only in secure, developer-controlled components of your application, such as:
- Servers that implement prior authorization themselves, such as Edge Functions, microservices, traditional or specialized web servers.
- Periodic jobs, queue processors, topic subscribers.
- Admin and back-office tools, with prior authorization checks only.
- Data processing pipelines, such as for analytics, reports, backups, or database synchronization.
<Admonition type="caution">
Never expose your secret keys publicly. Your data is at risk. **Do not:**
- Add it to web pages, public documents, source code, bundle in executables or packages for mobile, desktop or CLI apps.
- Send over chat applications, email or SMS to your peers.
- Never use in a browser, even on `localhost`.
- Do not pass in URLs or query params, as these are often logged.
- Be careful passing them in request headers without prior log sanitization.
- Take extra care logging even potentially **invalid API keys**. Typos might reveal the real key in the future.
- Reveal, copy, use or manipulate on hardware devices without full disk encryption and which you do not directly own or control (such as public computers, friend's laptop, etc.)
Ensure you handle them with care and using [secure coding practices](https://owasp.org/www-project-secure-coding-practices-quick-reference-guide/stable-en/).
</Admonition>
Secret keys authorize access to your project's data via the built-in `service_role` Postgres role. By design, this role has full access to your project's data. It also uses the [`BYPASSRLS` attribute](https://www.postgresql.org/docs/current/ddl-rowsecurity.html#:~:text=BYPASSRLS), skipping any and all Row Level Security policies you attach.
The secret key is an improvement over the old JWT-based `service_role` key, and we recommend using it where possible. It adds more checks to prevent misuse, specifically:
- You cannot use a secret key in the browser (matches on the `User-Agent` header) and it will always reply with HTTP 401 Unauthorized.
- You don't need to have any secret keys if you are not using them.
### Best practices for handling secret keys
Below are some starting guidelines on how to securely work with secret keys:
- Always work with secret keys on computers you fully own or control.
- Use secure & encrypted send tools to share API keys with others (often provided by good password managers), but prefer the [**Settings > API Keys**](/dashboard/project/_/settings/api-keys/) section of the Dashboard instead.
- Prefer encrypting them when stored in files or environment variables.
- Do not add in source control, especially for CI scripts and tools. Prefer using the tool's native secrets capability instead.
- Prefer using a separate secret key for each separate backend component of your application, so that if one is found to be vulnerable or to have leaked the key you will only need to change it and not all.
- Even though a secret key will always return HTTP 401 Unauthorized error when used in a browser, it does not mean that attackers will not use it with other tools. Delete immediately!
- If you must include them in logs, log the first few random characters (but never more than 6).
- If you wish to log or store which valid API key was used, store it as a SHA256 hash.
### What to do if a secret key or `service_role` has been leaked or compromised?
Don't rush if this has happened, or you are suspecting it has. Make sure you have fully considered the situation and have remediated the root cause of the suspicion or vulnerability **first**. Consider using the [OWASP Risk Rating Methodology](https://owasp.org/www-community/OWASP_Risk_Rating_Methodology) as an easy way to identify the severity of the incident and to plan your next steps.
To rotate a secret key (`sb_secret_...`), use the [**Settings > API Keys**](/dashboard/project/_/settings/api-keys/) section of the Dashboard to create a new secret API key, then replace it with the compromised key. Once all components are using the new key, delete the compromised one.
**Deleting a secret key is irreversible and once done it will be gone forever.**
If you are still using the JWT-based `service_role` key, replace the `service_role` key with a new secret key instead. Follow the guide from above as if you are rotating an existing secret key.
## Known limitations and compatibility differences
As the publishable and secret keys are no longer JWT-based, there are some known limitations and compatibility differences that you may need to plan for:
- You cannot send a publishable or secret key in the `Authorization: Bearer ...` header, except if the value exactly equals the `apikey` header. In this case, your request will be forwarded down to your project's database, but will be rejected as the value is not a JWT.
- Edge Functions **only support JWT verification** via the `anon` and `service_role` JWT-based API keys. You will need to use the `--no-verify-jwt` option when using publishable and secret keys. The Supabase platform does not verify the `apikey` header when using Edge Functions in this way. Implement your own `apikey`-header authorization logic inside the Edge Function code itself.
- Public Realtime connections are limited to 24 hours in duration, unless the connection is upgraded and further maintained with user-level authentication via Supabase Auth or a supported Third-Party Auth provider.