# Quickstart

> One-page reference: get a token, make a tool call, refresh, and handle rate limits.

A one-page cheat sheet for integrations that are already familiar with OAuth 2.0 + PKCE. For a step-by-step walkthrough, see [Your first call](your-first-call).

## Prerequisites

- A Caramel account at [caramelme.com](https://caramelme.com).
- An OAuth client ID. Clients using a `localhost` redirect URI self-register — see step 1b. Other redirect URIs require manual allow-listing (email `aymen@reactmotion.com`).

## 1. Discover endpoints

```bash
curl https://app.caramelme.com/.well-known/oauth-authorization-server
```

- `authorization_endpoint` → redirect the user here
- `token_endpoint` → exchange code and refresh here
- `registration_endpoint` → self-register a client here (loopback + `claude.ai` URIs only)

## 1b. Self-register (loopback clients only)

```bash
curl -X POST https://api.caramelme.com/functions/v1/mcp-oauth/register \
  -H "Content-Type: application/json" \
  -d '{"client_name":"my-app","redirect_uris":["http://localhost:3000/callback"],"grant_types":["authorization_code","refresh_token"],"response_types":["code"],"token_endpoint_auth_method":"none"}'
```

Save the returned `client_id`.

## 2. Authorize (PKCE S256)

```
GET https://api.caramelme.com/functions/v1/mcp-oauth
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=YOUR_REDIRECT_URI
  &code_challenge=BASE64URL_SHA256_OF_VERIFIER
  &code_challenge_method=S256
  &state=RANDOM_CSRF_TOKEN
  &scope=meta:read forms:write audience:write
```

User receives a magic-link email, clicks it, browser redirects to `redirect_uri?code=AUTH_CODE&state=…`.

Verify `state` matches before proceeding. Discard the code if it doesn't.

## 3. Exchange code for tokens

```bash
curl -X POST https://api.caramelme.com/functions/v1/mcp-oauth \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code&code=AUTH_CODE&redirect_uri=YOUR_REDIRECT_URI&client_id=YOUR_CLIENT_ID&code_verifier=YOUR_VERIFIER"
```

Response:

```json
{
  "access_token":  "eyJ…",
  "refresh_token": "rt_…",
  "expires_in":    3600,
  "token_type":    "Bearer"
}
```

- Access token: valid for **1 hour**.
- Refresh token: valid for **30 days from last use**, **rotates on every use** — persist the new one immediately.

## 4. Call a tool

All tools use the MCP endpoint. The JSON-RPC method is always `tools/call`.

```bash
curl -X POST https://app.caramelme.com/api/functions/caramel-mcp \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method":  "tools/call",
    "id":      1,
    "params":  { "name": "caramel.v1.business.list", "arguments": {} }
  }'
```

> **Note** The REST gateway (`https://gateway.caramelme.com/v1/*`) is in limited preview. Use the MCP endpoint for new integrations.

The `result.content[0].text` field is a JSON string — parse it to get the payload.

## 5. Refresh before expiry

Refresh ~60 seconds before the token expires. Do not wait for a `401`.

```bash
curl -X POST https://api.caramelme.com/functions/v1/mcp-oauth \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token&refresh_token=$REFRESH_TOKEN&client_id=$CLIENT_ID"
```

**Always replace the stored refresh token** with the new one from the response. Using a rotated-out token returns `400 invalid_grant` and forces re-authorization.

## 6. Handle 429 (rate limits)

When you receive a `429`, read the `Retry-After` header and wait that many seconds before retrying.

```js
async function callWithRetry(body, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const res = await fetch('https://app.caramelme.com/api/functions/caramel-mcp', {
      method:  'POST',
      headers: { 'Authorization': `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
      body:    JSON.stringify(body),
    });
    if (res.status === 429) {
      const wait = parseInt(res.headers.get('Retry-After') ?? '5', 10);
      await new Promise(r => setTimeout(r, wait * 1000));
      continue;
    }
    return res.json();
  }
  throw new Error('Rate limit retries exhausted');
}
```

## Quick reference

| What | Value |
|---|---|
| MCP endpoint | `https://app.caramelme.com/api/functions/caramel-mcp` |
| Token endpoint | `https://api.caramelme.com/functions/v1/mcp-oauth` |
| Registration endpoint | `https://api.caramelme.com/functions/v1/mcp-oauth/register` |
| Discovery URL | `https://app.caramelme.com/.well-known/oauth-authorization-server` |
| Access token lifetime | 1 hour |
| Refresh token lifetime | 30 days from last use |
| Refresh token rotation | Yes — always persist the new token |
| Scopes always granted | `meta:read` |
| MCP JSON-RPC method | `tools/call` |
| List all tools | `caramel.v1.meta.capabilities` |
| List businesses | `caramel.v1.business.list` (or legacy `list_businesses`) |
| Error typo | Some errors use `messsage` (3 s's) — normalize to `message` |

## Common error codes

| Code | Meaning | Fix |
|---|---|---|
| `400 invalid_grant` | Code used/expired or refresh token rotated | Restart authorize flow |
| `401 unauthorized` | Token expired or missing | Refresh, then retry |
| `403 scope_required` | Token missing required scope | Re-authorize with the scope |
| `403 tier_required` | Business tier too low for this tool | Upgrade the business tier |
| `-32001` (JSON-RPC) | Token expired mid-session | Refresh and retry the call |

## Next steps

- [Authentication](authentication) — full flow with code samples, edge cases, and storage guidance.
- [Your first call](your-first-call) — step-by-step walkthrough.
- [Reference](../reference/index) — every tool's parameters and response shape.
