Delegated auth
OAuth 2.1 on-behalf-of flow for multi-tenant platforms serving end users at scale.
Platform operators don't authenticate as themselves - they authenticate on behalf of each end user. Your user, Swiggy's account, your UI. Swiggy holds the PII; you hold the scoped session. This page is the contract.
The principle
The flow
┌────────────┐ ┌─────────────────┐ ┌────────────────┐ ┌─────────────┐
│ End user │ │ Your platform │ │ Swiggy OAuth │ │ Swiggy │
│ (voice / │ │ (Alexa / │ │ server │ │ identity │
│ chat / │ │ Gemini / │ │ │ │ service │
│ in-app) │ │ platform) │ │ │ │ │
└─────┬──────┘ └────────┬─────────┘ └────────┬───────┘ └──────┬──────┘
│ │ │ │
│ "Order food" │ │ │
├─────────────────────►│ │ │
│ │ Detect: user needs │ │
│ │ Swiggy authorization │ │
│ │ │ │
│ Open link / card: │ │ │
│ "Connect Swiggy" │ │ │
│◄─────────────────────┤ │ │
│ │ │ │
│ /auth/authorize? │ │
│ client_id=YOU&state=...&code_challenge=... │ │
├────────────────────────────────────────────────►│ │
│ │ │ Phone + OTP │
│ │ ├───────────────────►│
│ │ │◄───────────────────┤
│ Redirect to │ │ │
│ your callback URL │ │ │
│ with authorization │ │ │
│ code │ │ │
│◄────────────────────────────────────────────────┤ │
│ │ │ │
│ │ POST /auth/token │ │
│ │ + code_verifier │ │
│ ├────────────────────────►│ │
│ │◄────────────────────────┤ │
│ │ access_token │ │
│ │ (scoped, 5 days) │ │
│ │ │ │
│ │ Call Swiggy MCP tool │ │
│ │ on behalf of user │ │
│ │ with user's token │ │
│ ├────────────────────────►│ │
Implementation
1. Pre-register your platform
At onboarding, Swiggy issues your platform:
- A
client_id - An allowlisted set of
redirect_urivalues (exact-match HTTPS, or platform-specific schemes likegoogleassistant://,alexa://,jio-hello://) - An allowlisted set of Swiggy MCP servers - which of
food,instamart,dineoutyourclient_idis approved to call. Access isclient_id-scoped, not scope-scoped.
2. Initiate authorization per user
Generate a fresh PKCE verifier/challenge pair for each user session:
import crypto from "node:crypto";
const codeVerifier = crypto.randomBytes(32).toString("base64url");
const codeChallenge = crypto
.createHash("sha256")
.update(codeVerifier)
.digest("base64url");
Send the user to:
https://mcp.swiggy.com/auth/authorize?
response_type=code&
client_id=<your-client-id>&
redirect_uri=<your-callback>&
code_challenge=<challenge>&
code_challenge_method=S256&
state=<per-user-csrf-token>&
scope=mcp:tools
3. Exchange the code for a token
Your callback receives ?code=...&state=.... Exchange:
curl -X POST https://mcp.swiggy.com/auth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"code": "<code>",
"code_verifier": "<verifier>",
"client_id": "<your-client-id>",
"redirect_uri": "<your-callback>"
}'
Response:
{
"access_token": "eyJhbGciOiJI...",
"token_type": "Bearer",
"expires_in": 432000,
"scope": "mcp:tools mcp:resources mcp:prompts"
}
4. Store tokens per user
Your platform stores this access token associated with the end user, in secure per-user storage - never shared across users, never persisted in plaintext beyond its lifetime. Access tokens live 5 days; the underlying user session lasts longer, so re-auth is usually silent (no phone + OTP prompt again). Refresh-token issuance is not available in v1.0; when the access token expires, re-run the authorization flow.
5. Call Swiggy MCP on the user's behalf
const response = await fetch("https://mcp.swiggy.com/food", {
method: "POST",
headers: {
Authorization: `Bearer ${user.swiggyAccessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
jsonrpc: "2.0",
method: "tools/call",
params: { name: "search_restaurants", arguments: { /* ... */ } },
id: 1,
}),
});
Token lifecycle
| Item | Lifetime | Notes |
|---|---|---|
| Access token | 5 days | Signed JWT; includes user_id and transaction id - scopes and client_id are not carried in the token claim set today |
| User session | 30 days idle, sliding | Silent re-auth until this expires |
| Authorization code | 120 seconds, single-use | Exchange immediately |
Tokens can be invalidated server-side before exp (user logs out of Swiggy app, security event, policy revoke). Treat any 401 as "re-run the authorization flow"; never cache success assumptions.
Scopes
| Scope | Grants |
|---|---|
mcp:tools | Call any tool on the servers your client_id is allowlisted for |
mcp:resources | Read MCP resources (widget registry, static metadata) |
mcp:prompts | Access server-supplied prompt templates |
v1 uses server-level access control keyed on your client_id rather than per-domain read/write scopes. Finer-grained scopes (food.read, im.write, dineout.read, ...) are on the roadmap but are not enforced today - requesting them has no effect.
Logout
When the end user disconnects Swiggy from your product:
curl -X POST https://mcp.swiggy.com/auth/logout \
-H "Authorization: Bearer <user-access-token>"
This revokes the session on Swiggy's side. Drop the token from your storage.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| 401 on every call | Token expired | Re-run authorization; silent if session still valid |
| 419 | Session revoked | Full re-auth (phone + OTP) |
| 403 | Scope missing | Re-auth with broader scope |
| Upstream shedding | Unexpected traffic spike | Back off; negotiate capacity per rate-limits |
Stuck on /authorize | Bad redirect_uri | Must exact-match an allowlisted URI |
What we commit to
- PII stays with Swiggy. Tool responses return only what the user authorized for your scope.
- Audit logs are available to you per-user, on lawful request - see data-and-compliance.
- Token issuance is instrumented on our side; if you see anomalies on your side, builders@swiggy.in can correlate.