Validate API keys with provider aware diagnostics, health scoring, latency checks, and secure history.
KeyPing is an API key testing workspace for developers and teams. Paste a supported key, select a provider, and review authentication status, provider health, latency, rate limit signals, and scopes where the provider exposes them.
Validation runs through a Supabase Edge Function. The full key is used for the request and is not stored. A saved result contains only a short key preview, status metadata, diagnostics, and optional notes.
- Single key validation with clear valid, limited, and invalid states
- Provider aware check controls for status, rate limits, scopes, latency, and health score
- Composite health score from 0 to 100
- Provider health and uptime summaries
- Latency trends and provider comparisons
- History and vault view with filters and short key previews
- Bulk testing for up to 10 keys
- Request lab for safe custom HTTPS endpoint checks with response text and status banners
- PDF and CSV exports
- Expiry alerts and notification preferences
- Team workspaces with secure invite flow
- Keyboard friendly command palette with Cmd+K or Ctrl+K
Provider metadata has a single source of truth:
supabase/functions/_shared/provider-contract.ts. The frontend imports it
directly through src/lib/providers.ts, and the Edge Function imports the same
file, so the two cannot disagree about the provider list, availability,
supported checks, icon names, or documentation links.
| Provider | Availability | Validation |
|---|---|---|
| OpenAI | active | Authenticated GET /v1/models |
| Groq | active | Authenticated models request |
| Anthropic | active | Authenticated GET /v1/models |
| Stripe | active | Authenticated balance request |
| GitHub | active | Authenticated current user request, returns scopes |
| Twitter or X | active | Authenticated identity request |
| Notion | active | Authenticated identity request |
| Gemini | active | Models request using x-goog-api-key |
| Supabase | limited | Legacy JWTs with a valid ref claim are checked against that project's public auth settings. Publishable and secret keys carry no project URL and need a custom endpoint |
| AWS | limited | Not validated. A safe check needs a SigV4 signed STS request, so the function returns an explanatory VALIDATION_UNAVAILABLE result |
| Custom | active | Public HTTPS endpoints only, with SSRF protection |
limited means the provider is listed and selectable but cannot be fully
validated. planned is a valid state in the contract that no provider currently
uses. A provider marked planned is hidden from the pickers by
isProviderSelectable and answered with HTTP 422 PROVIDER_PLANNED.
Do not advertise a provider as active until its backend validator is deployed and tested.
| Route | Purpose |
|---|---|
/dashboard |
Overview, quick validation, recent results, provider health, alerts, and request lab |
/dashboard/analytics |
Trends, uptime, latency, status, and provider breakdowns |
/dashboard/history |
Saved result history, filters, previews, and deletion |
/dashboard/alerts |
Expiry reminders and credential follow up |
/dashboard/bulk |
Parallel key testing and report export |
/dashboard/team |
Team members, invitations, and shared workflows |
/dashboard/settings |
Profile, preferences, alerts, security, export, and help |
| Category | Technology |
|---|---|
| Frontend | React 18, TypeScript, Vite |
| Styling | Tailwind CSS, shadcn style primitives, Radix UI |
| Backend | Supabase Auth, PostgreSQL, RLS, Edge Functions |
| Charts | Recharts |
| jsPDF | |
| Forms | React Hook Form and Zod |
| Data | Supabase client with hand written data hooks. There is no query cache library, and the request contract is shared with the Edge Function so the two cannot drift |
| Testing | Vitest for the frontend, Deno test for the Edge Function |
| Deployment | Vercel and Supabase |
- Node.js 18 or newer
- pnpm
- A Supabase project
git clone https://github.com/MuhammadTanveerAbbas/Keyping.git
cd Keyping
pnpm install
cp .env.example .env.local
pnpm devOpen http://localhost:8080.
The app needs Supabase client credentials before the authenticated dashboard can load. Do not commit .env.local.
Client side variables are prefixed with VITE_ and are inlined into the
browser bundle. Never prefix a secret with VITE_.
| Variable | Scope | Required | Description |
|---|---|---|---|
VITE_SUPABASE_URL |
Browser | Yes | Supabase project URL |
VITE_SUPABASE_ANON_KEY |
Browser | Yes | Anon or publishable key. VITE_SUPABASE_PUBLISHABLE_KEY is accepted as an alternative name |
SUPABASE_URL |
Server | Yes | Project URL for the Vercel routes and the Edge Function |
SUPABASE_ANON_KEY |
Edge Function | Yes | Gateway key used to verify the caller's token. Preferred over the two variables below |
SUPABASE_PUBLISHABLE_KEYS |
Edge Function | No | Newer key format. A JSON object; anon, then default, then the first value is used |
SUPABASE_SECRET_KEYS |
Edge Function | No | Newer key format, last resort. Because the gateway key is attached to every token verification, a deployment that sets only this will send a secret key on that request, so set SUPABASE_ANON_KEY |
SUPABASE_SERVICE_ROLE_KEY |
Server | Yes | Authenticates the rate limit RPC. Never exposed to the browser |
VERCEL_URL |
Vercel | No | Set automatically by Vercel. Used by the health route to allow the deployment origin |
CRON_SECRET and the Stripe variables in .env.example are placeholders for
integrations that are not implemented. See "Known gaps".
supabase/config.toml declares the schema files in order through
db.migrations.schema_paths, so a local reset or a fresh project is built by:
supabase db resetThat applies supabase/database/structure.sql and then
supabase/database/security.sql, in that order. The split is by purpose:
structure.sql is what the database is, security.sql is who may do what.
supabase/database/upgrade.sql is the one time path for a database that was
created before the current schema. It is deliberately not in
db.migrations.schema_paths, because it alters tables a fresh project does not
have yet. It is idempotent, so it is safe to apply to a partially upgraded
database and safe to re-run.
Read the verification block at the top of upgrade.sql and run those queries
before applying anything. It reports whether the known security findings are
still present, and which legacy CHECK constraints are still unvalidated.
supabase functions deploy test-api-key --project-ref your-project-idThe browser must call the same function name that is deployed:
supabase.functions.invoke("test-api-key", { body });All three testers go through invokeTestApiKey in src/lib/edge-function.ts
rather than calling supabase.functions.invoke directly, so the error payload
is parsed in exactly one place.
- Prevent use of leaked passwords. Supabase checks new passwords against
Have I Been Pwned. There is no
config.tomlkey for this, so it must be enabled in the dashboard under Authentication, then email and password. The Supabase Security Advisor reports it as a finding while it is off. - Set the Auth redirect URL for the deployed domain.
- Keep sign ups enabled only if you intend open registration.
- Full API keys are sent only to the Edge Function for the active request. The key is never logged, never returned in a response, and never persisted.
- The database stores at most the last four characters of a key, in
key_tests.key_preview, constrained to between 1 and 4 characters by a CHECK. - API key fields are cleared after ten minutes of inactivity. The request lab discards the response body at the same moment, because the body can itself contain sensitive material.
- Row level security limits history, alerts, teams, and shared results. Every table has RLS enabled with explicit grants; no table relies on defaults.
- Membership checks live in the
privateschema, which PostgREST does not serve. They areSECURITY DEFINERso a policy onteam_memberscan readteam_memberswithout recursing into itself. - Every
SECURITY DEFINERfunction setssearch_path = pg_catalogand schema qualifies everything it touches. - Every UPDATE policy carries an explicit
WITH CHECK, so a caller cannot pass theUSINGclause and then rewrite an ownership column. private.usage_countersis enabled with no policy, which denies every role.public.consume_rate_limitis the only path to it, and that function is executable byservice_rolealone. The function has to live inpublicbecause PostgREST only routes functions in a schema listed inapi.schemas, andprivateis deliberately not listed.- Custom endpoints require HTTPS on port 443, must resolve only to public addresses, and are reached over a connection pinned to the validated address with redirects disabled. Pinning is what closes DNS rebinding.
- Redirects, private and reserved IP ranges, metadata services, link local addresses, and unsafe request headers are blocked.
- Provider calls use bounded timeouts and conservative retries.
- No provider response is treated as a guarantee of future availability.
- Users can export or delete their saved data from Settings, and can delete their account.
- The production Content Security Policy has no
connect-srcentry for a third party analytics host, and the privacy policy states that no advertising trackers are used. Adding either is a product and legal decision.
Review the custom endpoint policy whenever the Edge Function request contract changes. SSRF protection must also be tested at the network layer, not only in application code.
The schema lives in supabase/database/. The entity diagram is in
docs/diagrams.md.
| Table | Purpose | Grants to authenticated |
|---|---|---|
key_tests |
Saved validation summaries and diagnostics | select, insert, update, delete |
alerts |
Expiry reminders | select, insert, update, delete |
teams |
Workspace ownership | select, insert, update, delete |
team_members |
Workspace membership and roles | select, insert, delete |
team_invites |
Hashed, expiring invitations | select, delete |
shared_results |
Team scoped result sharing | select, insert, delete |
notification_preferences |
One preference row per account | select, insert, update, delete |
private.usage_counters |
Rate limit windows | none, reachable only through public.consume_rate_limit |
team_invites has no direct insert path for authenticated users. Rows are only
created by the create_team_invite RPC, which returns the raw token once and
stores only its SHA-256 hash.
Do not use a destructive schema reset against production data. Test the RLS
behaviour with at least four roles: anonymous, team owner, team member, and an
unrelated authenticated user. upgrade.sql ends with a nine step manual check
list covering exactly those cases.
These are real and deliberately not hidden.
- Alert delivery does not exist.
alerts.notifiedis never written and there is no scheduler, so an alert is a stored record only. The Alerts page says so in the interface.vercel.jsonhas nocronsarray, so nothing triggers a revalidation or a digest either. api/keep-alive.tshas no scheduler. It is reachable and correctly authenticated, but nothing calls it on a schedule. Itsconfig.cronexport is the Next.js idiom and is inert in this Vite project. Remove it, or add acronsarray tovercel.json, but do not leave it looking active.team_inviteshas no list or revoke UI. Invites are created and accepted but never listed, so each invite link click leaves a row until it expires.revoke_team_inviteexists in the database but the frontend never calls it.shared_resultsis unused by the frontend. The table, its three policies, and theprivate.is_valid_shared_resulthelper are all in place, and no page reads or writes the table.- No product analytics exists. See the security model note above.
- The Deno test suite is not run by
pnpm test.vitest.config.tsscopes tosrc, and the five files undersupabase/functionsneeddeno test. There is no CI workflow, so nothing runs them automatically. - Leaked password protection is off until enabled in the dashboard.
| Command | Description |
|---|---|
pnpm dev |
Start the development server on port 8080 |
pnpm build |
Create a production build |
pnpm build:dev |
Create a development mode build |
pnpm preview |
Preview the production build |
pnpm lint |
Run ESLint |
pnpm typecheck |
Run TypeScript checks |
pnpm check:copy |
Verify that UI and documentation contain no typographic dash characters |
pnpm test |
Run the Vitest suite |
pnpm test:watch |
Run Vitest in watch mode |
Keyping/
├── api/ Vercel edge routes: health, keep alive
├── docs/diagrams.md Mermaid architecture, flow, and ER diagrams
├── public/ Static assets
├── scripts/ Copy check used by the quality gate
├── src/
│ ├── components/ Shared UI and dashboard components
│ ├── hooks/ Data hooks and shared row normalization
│ ├── integrations/supabase/ Supabase client and database types
│ ├── lib/ Auth, provider registry, CSV, events, helpers
│ ├── pages/ Route level pages
│ └── test/ Vitest suites
├── supabase/
│ ├── database/
│ │ ├── structure.sql Tables, constraints, indexes, triggers
│ │ ├── security.sql RLS, policies, functions, grants
│ │ └── upgrade.sql One time path for an existing database
│ ├── functions/
│ │ ├── _shared/ SSRF guards, retry, shared contracts
│ │ └── test-api-key/ The deployable edge function
│ └── config.toml Local project config and schema file order
├── README.md
└── package.json
- Single key validation
- Provider diagnostics and health score
- Bulk testing
- History and short key previews
- Analytics and latency trends
- Expiry alert data model and settings workflow
- Team workspace data model
- PDF and CSV exports
- Command palette
- Scheduled revalidation jobs
- Email and webhook delivery
- Public REST validation API
- CLI integration
- Billing and usage limits
- Full AWS SigV4 validation
- Saved request collections and assertions
Before merging a change:
pnpm typecheck
pnpm lint
pnpm check:copy
pnpm test
pnpm buildFor Edge Function changes, also run the Deno checks, which pnpm test does not
cover:
deno check --config supabase/functions/test-api-key/deno.json supabase/functions/test-api-key/index.ts
deno test --config supabase/functions/test-api-key/deno.json supabase/functions/test-api-key/*_test.ts
deno lint --config supabase/functions/test-api-key/deno.json supabase/functions/_shared supabase/functions/test-api-keyFor database changes, run supabase db reset for a fresh database, and work
through the manual checks at the end of upgrade.sql against any existing one.
For provider changes, test both successful and failing responses without
logging the API key, and update docs/diagrams.md if the flow changed.
- Fork the repository.
- Create a feature branch.
- Make a focused change.
- Add or update tests.
- Run the quality checklist.
- Open a pull request with the affected routes and database migrations listed.
Distributed under the MIT License. See LICENSE for more information.
SaaS Developer and builder of practical production tools.