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_uri values (exact-match HTTPS, or platform-specific schemes like googleassistant://, alexa://, jio-hello://)
  • An allowlisted set of Swiggy MCP servers - which of food, instamart, dineout your client_id is approved to call. Access is client_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

ItemLifetimeNotes
Access token5 daysSigned JWT; includes user_id and transaction id - scopes and client_id 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). Treat any 401 as "re-run the authorization flow"; never cache success assumptions.

Scopes

ScopeGrants
mcp:toolsCall any tool on the servers your client_id is allowlisted for
mcp:resourcesRead MCP resources (widget registry, static metadata)
mcp:promptsAccess 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

SymptomLikely causeFix
401 on every callToken expiredRe-run authorization; silent if session still valid
419Session revokedFull re-auth (phone + OTP)
403Scope missingRe-auth with broader scope
Upstream sheddingUnexpected traffic spikeBack off; negotiate capacity per 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.