feat(credentials): add v2 credential lifecycle APIs - #6664
feat(credentials): add v2 credential lifecycle APIs#6664TheodoreSpeaks wants to merge 6 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryHigh Risk Overview New routes: OAuth browser path: Contracts add provider schemas, connection body unions, service-account body validation against Reviewed by Cursor Bugbot for commit cf680e6. Bugbot is set up for automated code reviews on this repo. Configure here. |
Greptile SummaryThe PR adds the V2 credential lifecycle APIs and secures browser OAuth handoff with short-lived, user-bound connection drafts.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| apps/sim/lib/credentials/connect-draft.ts | Creates user-bound expiring drafts, preserves active draft IDs, CAS-guards immutable targets, and allows renamed reconnect retries. |
| apps/sim/lib/credentials/application/create-credential-connection.ts | Resolves new versus reconnect intent and passes the correct display-name identity semantics into draft creation. |
| apps/sim/app/api/auth/oauth2/authorize/route.ts | Reauthorizes active drafts against the authenticated browser user before launching OAuth. |
| apps/sim/lib/credentials/connect-draft.test.ts | Covers stable draft IDs, mutable reconnect names, and conflicting connection intents. |
| apps/sim/lib/credentials/deletion.ts | Coordinates credential disconnection with cleanup of stored references. |
| apps/sim/lib/api/contracts/v2/credentials.ts | Defines the V2 credential lifecycle request and response contracts. |
Sequence Diagram
sequenceDiagram
participant Client
participant V2 as V2 Credentials API
participant Drafts as OAuth Draft Store
participant Browser as Authenticated Browser
participant OAuth as OAuth Provider
Client->>V2: POST /credentials/connections
V2->>Drafts: Create or refresh immutable intent
Drafts-->>V2: draftId and expiry
V2-->>Client: authorizationUrl
Client->>Browser: Open authorizationUrl
Browser->>Drafts: Load user-bound active draft
Drafts-->>Browser: Authorized connection target
Browser->>OAuth: Complete authorization
OAuth-->>Browser: OAuth callback
Browser->>V2: Materialize or reconnect credential
V2-->>Browser: /oauth/credential-connected
Reviews (6): Last reviewed commit: "fix(credentials): stabilize oauth draft ..." | Re-trigger Greptile
|
@cursor review |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit e4b09dc. Configure here.
|
@cursor review |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 7fcf26f. Configure here.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit cf680e6. Configure here.
Problem:
The V2 API could list stored credentials, but clients could not discover the complete set of credential methods, create service-account credentials, start or reconnect OAuth credentials safely, or disconnect credentials. OAuth still has to cross into an authenticated browser session, and the old browser entrypoint accepted mutable target parameters instead of an API-created connection intent.
Solution:
/api/v2/credentials./oauth/credential-connected.{ data }/{ data, nextCursor }/{ error }envelopes.API shapes:
GET /api/v2/credentials?workspaceId={workspaceId}{ "data": [ { "id": "credential-id", "type": "oauth", "displayName": "Work Gmail", "description": null, "providerId": "google-email", "accountId": "provider-account-id", "hasServiceAccountKey": false, "role": "admin", "createdAt": "2026-08-13T18:00:00.000Z", "updatedAt": "2026-08-13T18:00:00.000Z" } ], "nextCursor": null }GET /api/v2/credentials/providers?workspaceId={workspaceId}OAuth entry:
{ "type": "oauth", "serviceId": "salesforce", "name": "Salesforce", "description": "Connect to Salesforce CRM data and operations.", "providerFamily": "salesforce", "available": true, "supportsReconnect": true, "authorizationOptions": [ { "providerId": "salesforce", "label": "Production" }, { "providerId": "salesforce-sandbox", "label": "Sandbox" } ] }Service-account entry:
{ "type": "service_account", "serviceId": "zoom-service-account", "providerId": "zoom-service-account", "name": "Zoom server-to-server app", "description": "Connect Zoom with a server-to-server app.", "providerFamily": "zoom", "available": true, "docsUrl": "https://docs.sim.ai/integrations/zoom-service-account", "requiresClientGeneratedCredentialId": false, "fields": [ { "id": "clientId", "label": "Client ID", "placeholder": "Paste the client ID", "required": true, "secret": false, "multiline": false }, { "id": "clientSecret", "label": "Client secret", "placeholder": "Paste the client secret", "required": true, "secret": true, "multiline": false }, { "id": "orgId", "label": "Account ID", "placeholder": "Paste the account ID", "required": true, "secret": false, "multiline": false } ] }The endpoint returns
{ "data": [oauthEntry, serviceAccountEntry], "nextCursor": null }.POST /api/v2/credentialsCreates a service-account credential.
displayNameis optional because providers may derive it from the verified account identity.{ "workspaceId": "workspace-id", "type": "service_account", "providerId": "zoom-service-account", "displayName": "Zoom automation", "clientId": "YOUR_CLIENT_ID", "clientSecret": "YOUR_CLIENT_SECRET", "orgId": "YOUR_ACCOUNT_ID" }Returns
201 { "data": credential }for a new credential or200 { "data": credential }for an accepted replay. Secret fields are write-only and never returned.POST /api/v2/credentials/connectionsNew OAuth credential:
{ "workspaceId": "workspace-id", "providerId": "google-email", "displayName": "Work Gmail" }Reconnect an existing OAuth credential:
{ "workspaceId": "workspace-id", "credentialId": "credential-id" }{ "data": { "authorizationUrl": "https://www.sim.ai/api/auth/oauth2/authorize?draftId=draft-id", "expiresAt": "2026-08-13T18:30:00.000Z" } }This write requires a personal API key because the draft is bound to the human who must sign in in the browser. Workspace API keys can still list credentials and providers.
DELETE /api/v2/credentials/{credentialId}?workspaceId={workspaceId}{ "data": { "id": "credential-id", "deleted": true } }Validation:
bun run lintbunx turbo run type-check --filter=sim --filter=@sim/authbun run check:audits: 26 passedbun run check:api-validation:strictbun run check:openapi