Documentation Index
Fetch the complete documentation index at: https://docs.vin.gs/llms.txt
Use this file to discover all available pages before exploring further.
Build a personal dashboard that reads your Vings data through the External API. Use v0, Lovable, or any stack you control.
This path uses OAuth + REST (/api/v1/*). It is not the MCP integration used for ChatGPT and Claude connectors.
What you get
| Piece | Value |
|---|
| API base | https://external.vin.gs/api |
| OpenAPI | https://external.vin.gs/api/openapi.json |
| OAuth discovery | GET /api/.well-known/oauth-protected-resource |
| Vings setup | Dashboard → Settings → Integrations → Personal dashboards |
Personal dashboard OAuth clients are owner-only: only the Vings user who created the client can approve consent. They are public clients (PKCE, no client secret).
Reference implementation
Use this public v0 example app as the canonical pattern for OAuth (PKCE), httpOnly cookies, and server-side API proxies:
github.com/chriskrogh/v0-personal-finance-dashboard
When building with v0 or Lovable, point your agent at that repository (and this guide) instead of inventing routes or field names from scratch.
Setup steps
- Deploy or preview your app with a fixed HTTPS callback path (for example
https://your-app.vercel.app/auth/callback). Localhost callbacks are also allowed when registered.
- Copy the callback URL from your deployment (scheme, host, path, and trailing slash must match exactly).
- In Vings, open Settings → Integrations → Personal dashboards, choose Add dashboard, paste the redirect URI, and copy the Client ID.
- Set
VINGS_OAUTH_CLIENT_ID in your project to that Client ID.
- Discover OAuth endpoints — fetch
GET /api/.well-known/oauth-protected-resource, then GET {authorization_servers[0]}/.well-known/openid-configuration. Use authorization_endpoint and token_endpoint from that JSON (see OAuth flow).
- PKCE sign-in — public client, S256
code_challenge, scopes openid email profile; exchange the authorization code at the token endpoint with code_verifier (no client secret). After login, approve access on /oauth/consent.
- Proxy API calls server-side — browser apps cannot call
external.vin.gs directly (CORS). Add Next.js (or similar) routes that attach the bearer token, then call your proxy from the client.
- Verify —
GET /v1/me through your proxy. Use REST only — not MCP.
Give your builder the right instructions
On the Personal dashboards settings page, open the v0 or Lovable tab and copy the platform-specific agent prompt into your project instructions or project knowledge. It links back to this guide and the live OpenAPI document so the agent does not invent endpoints or use MCP by mistake.
- Paste the v0 agent prompt into project instructions before scaffolding OAuth.
- Add the reference implementation repo URL so the agent can mirror working routes.
- Add
VINGS_OAUTH_CLIENT_ID in the project environment.
- If your preview or production URL changes, update the redirect URI in Vings and redeploy.
Lovable
- Paste the Lovable agent prompt into project knowledge so generated code uses REST, not MCP.
- Link the reference implementation in project knowledge.
- Set
VINGS_OAUTH_CLIENT_ID in env or secrets.
- Re-check the redirect URI in Vings when Lovable changes your published domain.
OAuth flow
Common mistake: Personal dashboard OAuth uses /auth/v1/oauth/authorize and /auth/v1/oauth/token. Do not use /auth/v1/authorize or /auth/v1/token — those are for Google/Apple social sign-in and return errors like Unsupported provider: Provider could not be found.
Expected full URL shapes (issuer varies per environment):
Authorization: https://{issuer-host}/auth/v1/oauth/authorize
Token: https://{issuer-host}/auth/v1/oauth/token
Always prefer the authorization_endpoint and token_endpoint values from OpenID discovery rather than guessing paths.
Discovery
Step 1 — protected resource metadata:
curl -sS https://external.vin.gs/api/.well-known/oauth-protected-resource
Read authorization_servers[0] as the OAuth issuer (Vings Supabase Auth base, typically ending in /auth/v1).
Step 2 — OpenID configuration:
curl -sS "{authorization_servers[0]}/.well-known/openid-configuration"
Use the authorization_endpoint and token_endpoint fields from that JSON verbatim in your authorize and token requests.
Manual fallback (only if you cannot read discovery): append /oauth/authorize and /oauth/token to the issuer from step 1 — for example {issuer}/oauth/authorize. Do not append bare /authorize or /token.
Public client + PKCE
Vings registers personal dashboards as public clients with token_endpoint_auth_method: none. Your app must:
- Generate a
code_verifier / code_challenge (S256) for each sign-in.
- Send
client_id, redirect_uri, response_type=code, code_challenge, and code_challenge_method=S256 to the OAuth authorization endpoint.
- Exchange the authorization code at the token endpoint with
code_verifier (no client secret).
At authorize time, request standard OIDC scopes (openid, email, profile). Vings data scopes (for example portfolio:read, transactions:read) are attached to the OAuth client when you create it in settings and are shown on the Vings consent screen at /oauth/consent.
Consent
After the user signs in to Vings (if needed), they approve readonly access on the Vings consent page. If someone other than the client owner tries to authorize a personal dashboard client, consent is rejected.
Cookie configuration
Store OAuth state and code_verifier in httpOnly cookies before redirecting to the authorization endpoint. After token exchange, store the access token the same way (not localStorage in production).
httpOnly: true — tokens are not readable from client JavaScript.
sameSite: "lax" (not "strict") — cookies are sent when the identity provider redirects back to your callback.
secure: true in production (HTTPS).
- Short
maxAge for state and verifier (for example 10 minutes).
See the reference implementation for a working Next.js layout (/auth/login, /auth/callback, and cookie names).
Server-side API proxy (required for browser apps)
The External API does not allow direct browser requests (CORS). Every dashboard UI should call your own server routes, which forward requests with Authorization: Bearer <access_token>.
- Create one route file per upstream endpoint (for example
/api/vings/me, /api/vings/transactions).
- Read the access token from your httpOnly cookie on the server, then
fetch https://external.vin.gs/api/....
- Do not use catch-all proxy routes like
app/api/vings/[...path]/route.ts — they can return 404 or behave inconsistently on some hosts.
- From the browser, call only your
/api/vings/* routes — never https://external.vin.gs/api/... directly.
The reference implementation shows this pattern end to end.
Call the API (server or curl)
GET https://external.vin.gs/api/v1/me
Authorization: Bearer <access_token>
Verify with GET /v1/me, then add endpoints for the widgets you need. See Authentication for scopes and Introduction for the full REST list.
Example (after you have a token):
curl -sS https://external.vin.gs/api/v1/portfolio \
-H "Authorization: Bearer $ACCESS_TOKEN"
Common field names
The OpenAPI spec is authoritative. These names differ from what many agents guess:
| You might expect | Actual name / notes |
|---|
amount | amount_cents (integer; divide by 100 for display) |
limit | pageSize (query param on GET /v1/transactions) |
merchant | merchant_name on transaction objects |
description | title on transaction objects |
is_income | type — "INCOME" or "EXPENSE" (also INTERNAL_TRANSFER) |
category | category — uppercase enum (for example GROCERIES, RENT) |
| Money totals | Object { amount_cents, currency } (not a bare number) |
| Next page | pagination.nextCursor and pagination.hasMore (not page numbers) |
| Min/max filters | minAmountCents, maxAmountCents (query params, in cents) |
| Merchant search | merchantQuery (query param, not merchant) |
Suggested dashboard endpoints
| Goal | Endpoint |
|---|
| Sanity check | GET /v1/me |
| Net worth / holdings | GET /v1/portfolio, GET /v1/portfolio/history |
| Spending overview | GET /v1/spending/summary, GET /v1/spending/categories |
| Recent activity | GET /v1/transactions |
| Budget progress | GET /v1/budgets/summary |
| Cashflow | GET /v1/cashflow/summary |
Use OpenAPI for parameters and response shapes. Do not expose tokens in query strings, logs, or client-side analytics.
Prototype without OAuth (optional)
For local experiments only, you can use a personal access token (vng_pat_*) created under Settings → Integrations → API keys. PATs are for scripts and quick tests—not for shipping a multi-user hosted dashboard. Production personal apps should use OAuth.
Security practices
- Treat the access token like a password; never commit it or pass it in URLs.
- Responses are user-specific and use
Cache-Control: no-store.
- The External API is read-only; no writes or bank linking through this API.
- Revoke the OAuth client in Vings settings to cut off access immediately.
- Do not use MCP (
/api/mcp) for v0/Lovable dashboard UIs unless you are explicitly building an MCP host.
Troubleshooting
| Symptom | What to check |
|---|
Unsupported provider: Provider could not be found | Wrong authorize URL — use OIDC authorization_endpoint (/auth/v1/oauth/authorize), not /auth/v1/authorize. |
redirect_uri mismatch | Redirect in the authorize request must exactly match a URI registered on the personal dashboard client. |
| Consent says the app is private | Personal dashboards only work for the Vings user who created the client. |
401 on API calls | Missing or expired bearer token; repeat sign-in. |
| Empty portfolio or spending | User may have no linked accounts yet; handle empty states in the UI. |
| CORS errors from the browser | Do not call external.vin.gs from the browser — add server-side proxy routes (see reference implementation). |
| Callback route returns 404 | Use explicit API route files per endpoint, not catch-all [...path] proxies (see example repo). |
| State mismatch on callback | OAuth cookies lost on redirect — use sameSite: "lax", httpOnly: true, and secure: true in production. |
amount is undefined in UI | Response field is amount_cents; divide by 100 for dollars. |
| No transactions returned | Query param is pageSize, not limit — e.g. GET /v1/transactions?pageSize=5. |