Quickstart
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.
Prerequisites
Section titled “Prerequisites”- A Caramel account at caramelme.com.
- An OAuth client ID. Clients using a
localhostredirect URI self-register — see step 1b. Other redirect URIs require manual allow-listing (emailaymen@reactmotion.com).
1. Discover endpoints
Section titled “1. Discover endpoints”curl https://app.caramelme.com/.well-known/oauth-authorization-serverauthorization_endpoint→ redirect the user heretoken_endpoint→ exchange code and refresh hereregistration_endpoint→ self-register a client here (loopback +claude.aiURIs only)
1b. Self-register (loopback clients only)
Section titled “1b. Self-register (loopback clients only)”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)
Section titled “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:writeUser 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
Section titled “3. Exchange code for tokens”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:
{ "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
Section titled “4. Call a tool”All tools use the MCP endpoint. The JSON-RPC method is always tools/call.
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
Section titled “5. Refresh before expiry”Refresh ~60 seconds before the token expires. Do not wait for a 401.
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)
Section titled “6. Handle 429 (rate limits)”When you receive a 429, read the Retry-After header and wait that many seconds before retrying.
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
Section titled “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
Section titled “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
Section titled “Next steps”- Authentication — full flow with code samples, edge cases, and storage guidance.
- Your first call — step-by-step walkthrough.
- Reference — every tool’s parameters and response shape.