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 /   │      │                  │     │  server        │     │  identity   │
│  chat /    │      │                  │     │                │     │  service    │
│  in-app)   │      │                  │     │                │     │             │
└─────┬──────┘      └────────┬─────────┘     └────────┬───────┘     └──────┬──────┘
      │                      │                         │                    │
      │  "Order food"        │                         │                    │
      ├─────────────────────►│                         │                    │
      │                      │  Detect: user needs     │                    │
      │                      │  Swiggy authorization   │                    │
      │                      │                         │                    │
      │  Open link / card:   │                         │                    │
      │  "Connect Swiggy"    │                         │                    │
      │◄─────────────────────┤                         │                    │
      │                      │                         │                    │
      │  /auth/authorize?                              │                    │
      │  state=...&code_challenge=...&redirect_uri=... │                    │
      ├────────────────────────────────────────────────►│                   │
      │                      │                         │  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

Your MCP client registers itself dynamically via RFC 7591 at POST /auth/register - there's no client identifier to apply for. At enterprise onboarding we align on:

  • An allowlisted set of redirect_uri values (exact-match HTTPS, or platform-specific schemes)
  • Which Swiggy MCP servers your integration will call - any of food, instamart, dineout, scenes. Access is scoped at the user level.

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=<from-dcr>&
  redirect_uri=<your-callback>&
  code_challenge=<challenge>&
  code_challenge_method=S256&
  state=<per-user-csrf-token>&
  scope=mcp:tools

The client_id is whatever /auth/register returned to your MCP client; standard MCP clients handle this transparently.

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": "<from-dcr>",
    "redirect_uri": "<your-callback>"
  }'

Response:

{
  "access_token": "eyJhbGciOiJI...",
  "refresh_token": "eyJhbGciOiJI...",
  "token_type": "Bearer",
  "expires_in": 432000,
  "scope": "mcp:tools mcp:resources mcp:prompts"
}

4. Store tokens per user

Your platform stores both the access token and the refresh_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 tokens are supported: when the access token expires, call /auth/token with grant_type=refresh_token to obtain a new one, and re-run the authorization flow only if the refresh fails. The refresh token rotates on each exchange, so persist the new one each time and discard the previous.

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

ItemLifetimeNotes
Access token5 daysSigned JWT; includes user_id and transaction id - scopes are not carried in the token claim set today
User session30 days idle, slidingSilent re-auth until this expires
Authorization code120 seconds, single-useExchange immediately

Tokens can be invalidated server-side before exp (user logs out of Swiggy app, security event, policy revoke). On a 401, refresh the access token first (grant_type=refresh_token); re-run the authorization flow only if the refresh fails. Never cache success assumptions.

Scopes

ScopeGrants
mcp:toolsCall any tool on any Swiggy MCP server the authenticated user is allowed to use
mcp:resourcesRead MCP resources (widget registry, static metadata)
mcp:promptsAccess server-supplied prompt templates

v1 access control is keyed at the user level, not at the application level.

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

SymptomLikely causeFix
401 on every callToken expiredRefresh the access token (grant_type=refresh_token); re-run authorization if refresh fails
419Session revokedFull re-auth (phone + OTP)
403Scope missingRe-auth with broader scope
Upstream sheddingUnexpected traffic spikeBack off and retry with jitter (see rate-limits)
Stuck on /authorizeBad redirect_uriMust 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.