> ## 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.

# Personal dashboards

> Connect v0, Lovable, or your own app to the Vings External API with owner-only OAuth.

Build a **personal dashboard** that reads **your** Vings data through the [External API](/external/docs/introduction). Use [v0](https://v0.dev), [Lovable](https://lovable.dev), or any stack you control.

This path uses **OAuth + REST** (`/api/v1/*`). It is **not** the [MCP](/external/docs/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`](https://external.vin.gs/api/openapi.json)                              |
| **OAuth discovery** | [`GET /api/.well-known/oauth-protected-resource`](https://external.vin.gs/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](https://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

1. **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.
2. **Copy the callback URL** from your deployment (scheme, host, path, and trailing slash must match exactly).
3. In Vings, open **Settings → Integrations → Personal dashboards**, choose **Add dashboard**, paste the redirect URI, and copy the **Client ID**.
4. Set **`VINGS_OAUTH_CLIENT_ID`** in your project to that Client ID.
5. **Discover OAuth endpoints** — fetch [`GET /api/.well-known/oauth-protected-resource`](https://external.vin.gs/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](#oauth-flow)).
6. **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`](https://www.vin.gs/oauth/consent).
7. **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.
8. **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.

### v0

* Paste the **v0 agent prompt** into project instructions before scaffolding OAuth.
* Add the [reference implementation](#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](#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 the Vings authorize proxy at **`/api/oauth/authorize`** and Supabase **`/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 URL shapes after discovery:

```
Authorization: https://external.vin.gs/api/oauth/authorize  (proxy → Supabase)
Token:         https://{supabase-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:**

```bash theme={null}
curl -sS https://external.vin.gs/api/.well-known/oauth-protected-resource
```

Read `authorization_servers[0]` as the external host (for example `https://external.vin.gs`), not the Supabase `/auth/v1` path.

**Step 2 — OpenID configuration:**

```bash theme={null}
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. `authorization_endpoint` is the Vings authorize proxy (`/api/oauth/authorize`); `token_endpoint` remains on Supabase Auth.

**Manual fallback (only if you cannot read discovery):** use `https://external.vin.gs/api/oauth/authorize` for authorize and the Supabase `…/auth/v1/oauth/token` URL for token exchange. Do **not** append bare `/authorize` or `/token` to Supabase.

### 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`](https://www.vin.gs/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](#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](#reference-implementation) shows this pattern end to end.

## Call the API (server or curl)

```http theme={null}
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](/external/docs/authentication) for scopes and [Introduction](/external/docs/introduction) for the full REST list.

Example (after you have a token):

```bash theme={null}
curl -sS https://external.vin.gs/api/v1/portfolio \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

## Common field names

The [OpenAPI](https://external.vin.gs/api/openapi.json) 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](https://external.vin.gs/api/openapi.json) 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](/external/docs/authentication) (`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](#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`.                                                           |
