Skip to content

feat: Artist TikTok Connections via Composio#170

Merged
sweetmantech merged 47 commits intotestfrom
feat/artist-composio-connections
Feb 12, 2026
Merged

feat: Artist TikTok Connections via Composio#170
sweetmantech merged 47 commits intotestfrom
feat/artist-composio-connections

Conversation

@sidneyswift
Copy link
Contributor

@sidneyswift sidneyswift commented Jan 29, 2026

Summary

  • Add API endpoints for artist connector management (GET, POST authorize, DELETE, POST complete)
  • Add callback URL handling for artist-connectors OAuth flow
  • Enable TikTok in Tool Router with artist-specific connections
  • Wire up artistId in chat tool setup for per-artist Composio connections

Stories Implemented

  • US-003: GET /api/artist-connectors endpoint
  • US-004: POST /api/artist-connectors/authorize endpoint
  • US-005: DELETE /api/artist-connectors endpoint
  • US-006: Add artist-connectors callback URL destination
  • US-007: Add TikTok to enabled toolkits
  • US-008: Modify createSession to accept connectedAccounts
  • US-009: Wire up artistId in chat tool setup
  • US-014: POST /api/artist-connectors/complete endpoint

Test plan

  • Connect TikTok for artist via Artist Settings > Connections
  • Send chat message "What are my TikTok stats?" with artist selected
  • Verify AI uses TikTok tools with artist's connection
  • Switch to different artist without TikTok and verify appropriate response

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Entity-specific connector management: optional account selection for authorize, list, and disconnect flows.
    • TikTok artist connector support with artist-only rules and artist connection mapping.
    • Configurable connector authorization: custom callback URLs and auth-specific configs.
    • Ownership verification on disconnects and expanded access checks across account/artist/workspace/organization.
    • Tool sessions can include artist connections to enable artist-specific tools.

sidneyswift and others added 9 commits January 28, 2026 20:51
- Add artist_composio_connections type to database.types.ts
- Create selectArtistComposioConnection.ts (single lookup by artist+toolkit)
- Create selectArtistComposioConnections.ts (all connections for artist)
- Create insertArtistComposioConnection.ts (upsert on unique constraint)
- Create deleteArtistComposioConnection.ts (delete by id)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add ALLOWED_ARTIST_CONNECTORS constant with 'tiktok' as first connector
- Create checkAccountArtistAccess function in Recoup-API (migrated from Recoup-Chat)
- Create getArtistConnectors function to return connector status for artists
- Create GET /api/artist-connectors endpoint with:
  - Bearer token and API key auth via validateAuthContext
  - Artist access validation via checkAccountArtistAccess
  - Returns list of allowed connectors with connection status

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add validateAuthorizeArtistConnectorBody.ts with Zod schema for request validation
- Add authorizeArtistConnector.ts to generate OAuth URLs via Composio
- Add POST /api/artist-connectors/authorize route with auth and access control
- Callback URL redirects to /chat?artist_connected={artistId}&toolkit={slug}

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add DELETE handler to disconnect an artist's connector from Composio
- Create validateDisconnectArtistConnectorBody.ts with Zod schema
- Create verifyArtistConnectorOwnership.ts to check connection ownership
- Create disconnectArtistConnector.ts to remove from Composio and DB

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add 'artist-connectors' to CallbackDestination type in getCallbackUrl.ts
- Add artistId and toolkit to CallbackOptions interface
- Handle artist-connectors destination returning /chat?artist_connected={artistId}&toolkit={toolkit}
- Update authorizeArtistConnector.ts to use getCallbackUrl instead of local function

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add 'tiktok' to the ENABLED_TOOLKITS array so TikTok tools are available
in Tool Router sessions, enabling the LLM to access TikTok data.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Add artistConnections parameter to createToolRouterSession (Record<string, string> | undefined)
- Pass connectedAccounts option to composio.create() call
- Update getComposioTools to accept and pass artistConnections parameter
- Add JSDoc documentation for the new parameter

This enables artist-specific Composio connections to be used when creating
Tool Router sessions, allowing the LLM to use the correct TikTok account
for the selected artist.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Modified getComposioTools to accept artistId parameter
- If artistId provided, fetches artist_composio_connections from DB
- Transforms connections to Record<string, string> format
- Passes artistConnections to createToolRouterSession
- Modified setupToolsForRequest to extract artistId from body
- Passes artistId through to getComposioTools

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Add POST /api/artist-connectors/complete endpoint to finalize OAuth flow:
- Query Composio for the user's connected account after OAuth redirect
- Store the connection mapping in artist_composio_connections table
- Add Zod validation for request body (artist_id, toolkit_slug)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@vercel
Copy link
Contributor

vercel bot commented Jan 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
recoup-api Ready Ready Preview Feb 12, 2026 2:56pm

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Jan 29, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Routes were refactored to delegate connector operations to centralized handlers; connector APIs were made entity-scoped (account/artist), with new validators, access checks, artist-connector restrictions, and tooling to surface and pass artist connections to the tool router.

Changes

Cohort / File(s) Summary
API Routes
app/api/connectors/authorize/route.ts, app/api/connectors/route.ts
Route implementations simplified to delegate to new handler functions; in-route validation, parsing, and response construction removed; function signatures shortened.
Handlers
lib/composio/connectors/authorizeConnectorHandler.ts, lib/composio/connectors/getConnectorsHandler.ts, lib/composio/connectors/disconnectConnectorHandler.ts
New Next.js handlers perform request validation, CORS/auth checks, call core connector functions, and return standardized JSON responses with error handling.
Validators / Request Parsing
lib/composio/connectors/validateAuthorizeConnectorRequest.ts, .../validateGetConnectorsRequest.ts, .../validateDisconnectConnectorRequest.ts, .../validateGetConnectorsQuery.ts, .../validateAuthorizeConnectorBody.ts, .../validateDisconnectConnectorBody.ts
New/updated validators validate body/query, enforce optional account_id flows, perform access/ownership checks, and return NextResponse on validation/auth failures.
Core Connector APIs
lib/composio/connectors/authorizeConnector.ts, getConnectors.ts, disconnectConnector.ts, verifyConnectorOwnership.ts
Core APIs changed to entity-centric signatures with options (authConfigs, customCallbackUrl, verifyOwnershipFor, allowedToolkits/displayNames); getConnectors ensures requested toolkits appear in results.
Public Exports
lib/composio/connectors/index.ts
Public API expanded to export new option types and utilities (GetConnectorsOptions, AuthorizeConnectorOptions, DisconnectConnectorOptions, ALLOWED_ARTIST_CONNECTORS, AllowedArtistConnector, verifyConnectorOwnership).
Artist Connector Utilities
lib/composio/connectors/isAllowedArtistConnector.ts
New constant/type and type-guard for allowed artist connectors (e.g., tiktok).
Tool Router
lib/composio/toolRouter/createToolRouterSession.ts, .../getArtistConnectionsFromComposio.ts, .../getTools.ts, lib/composio/toolRouter/index.ts
Tool router accepts/persists artistConnections; new helper fetches artist connections; getComposioTools gains optional artistId flow with access checks.
Access Control Helpers
lib/auth/checkAccountAccess.ts, lib/supabase/account_artist_ids/checkAccountArtistAccess.ts, lib/supabase/account_workspace_ids/checkAccountWorkspaceAccess.ts
New centralized account-access checks and Supabase-backed helpers for artist/workspace access used by validators.
Chat Integration
lib/chat/setupToolsForRequest.ts
Flow updated to pass artistId into tool retrieval so artist-specific connections can be applied.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Route as /api/connectors/authorize
    participant Handler as authorizeConnectorHandler
    participant Validator as validateAuthorizeConnectorRequest
    participant Auth as validateAuthContext
    participant Access as checkAccountArtistAccess
    participant Core as authorizeConnector
    participant Composio as Composio API

    Client->>Route: POST
    Route->>Handler: delegate request
    Handler->>Validator: validate + auth
    Validator->>Auth: validateAuthContext
    Auth-->>Validator: accountId
    alt account_id provided
        Validator->>Access: checkAccountArtistAccess
        alt denied
            Access-->>Validator: false
            Validator-->>Handler: NextResponse(403)
            Handler-->>Client: 403
        else granted
            Validator-->>Handler: params{composioEntityId, connector, authConfigs?, callbackUrl?}
        end
    else no account_id
        Validator-->>Handler: params{composioEntityId, connector, callbackUrl?}
    end
    Handler->>Core: authorizeConnector(composioEntityId, connector, options)
    Core->>Composio: create session
    Composio-->>Core: session+redirectUrl
    Core-->>Handler: result
    Handler-->>Client: 200 {success:true, data}
Loading
sequenceDiagram
    participant Client
    participant Route as /api/connectors
    participant Handler as getConnectorsHandler
    participant Validator as validateGetConnectorsRequest
    participant Auth as validateAuthContext
    participant Access as checkAccountArtistAccess
    participant Core as getConnectors
    participant Composio as Composio API

    Client->>Route: GET?account_id=...
    Route->>Handler: delegate request
    Handler->>Validator: validate + auth + query
    Validator->>Auth: validateAuthContext
    Auth-->>Validator: accountId
    alt account_id provided
        Validator->>Access: checkAccountArtistAccess
        alt denied
            Access-->>Validator: false
            Validator-->>Handler: NextResponse(403)
            Handler-->>Client: 403
        else granted
            Validator-->>Handler: params{composioEntityId, allowedToolkits?}
        end
    else no account_id
        Validator-->>Handler: params{composioEntityId: accountId}
    end
    Handler->>Core: getConnectors(composioEntityId, options)
    Core->>Composio: fetch connectors
    Composio-->>Core: connectors[]
    Core-->>Handler: connectors[]
    Handler-->>Client: 200 {success:true, connectors}
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

✨ Routes hand off, validators stand guard,
Accounts and artists chart maps unmarred,
TikTok slips in with a friendly nod,
Handlers hum tidy, options applaud,
Small pieces align—clean, steady, and starred.

🚥 Pre-merge checks | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Solid & Clean Code ⚠️ Warning PR contains DRY violations with 222+ lines of duplicated validator orchestration patterns and OCP violations with hardcoded connector-specific configuration scattered throughout. Extract common validator orchestration into reusable helper functions and consolidate connector metadata into a central configuration object to enable extension without code modification.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/artist-composio-connections

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Jan 29, 2026

Braintrust eval report

Catalog Opportunity Analysis Evaluation (HEAD-1770003038)

Score Average Improvements Regressions
Catalog_availability 44.3% (+22pp) 3 🟢 -
Llm_calls 0 (+0) - -
Tool_calls 0 (+0) - -
Errors 0 (+0) - -
Llm_errors 0 (+0) - -
Tool_errors 0 (+0) - -
Prompt_tokens 0tok (+0tok) - -
Prompt_cached_tokens 0tok (+0tok) - -
Prompt_cache_creation_tokens 0tok (+0tok) - -
Completion_tokens 0tok (+0tok) - -
Completion_reasoning_tokens 0tok (+0tok) - -
Total_tokens 0tok (+0tok) - -
Duration 40.1s (-2.18s) 3 🟢 2 🔴

Catalog Songs Count Evaluation (HEAD-1770003038)

Score Average Improvements Regressions
AnswerCorrectness 18.8% (0pp) 1 🟢 2 🔴
Factuality 66.7% (-33pp) - 1 🔴
Llm_calls 4 (+0) - -
Tool_calls 0 (+0) - -
Errors 0 (+0) - -
Llm_errors 0 (+0) - -
Tool_errors 0 (+0) - -
Prompt_tokens 0tok (+0tok) - -
Prompt_cached_tokens 0tok (+0tok) - -
Prompt_cache_creation_tokens 0tok (+0tok) - -
Completion_tokens 0tok (+0tok) - -
Completion_reasoning_tokens 0tok (+0tok) - -
Completion_accepted_prediction_tokens 0tok (+0tok) - -
Completion_rejected_prediction_tokens 0tok (+0tok) - -
Completion_audio_tokens 0tok (+0tok) - -
Total_tokens 0tok (+0tok) - -
Duration 13.8s (+2.95s) - 3 🔴

First Week Album Sales Evaluation (HEAD-1770003038)

Score Average Improvements Regressions
Factuality 35% (-20pp) - 2 🔴
Llm_calls 1 (+0) - -
Tool_calls 0 (+0) - -
Errors 0 (+0) - -
Llm_errors 0 (+0) - -
Tool_errors 0 (+0) - -
Prompt_tokens 0tok (+0tok) - -
Prompt_cached_tokens 0tok (+0tok) - -
Prompt_cache_creation_tokens 0tok (+0tok) - -
Completion_tokens 0tok (+0tok) - -
Completion_reasoning_tokens 0tok (+0tok) - -
Completion_accepted_prediction_tokens 0tok (+0tok) - -
Completion_rejected_prediction_tokens 0tok (+0tok) - -
Completion_audio_tokens 0tok (+0tok) - -
Total_tokens 0tok (+0tok) - -
Duration 14.58s (-0.85s) 1 🟢 3 🔴

Memory & Storage Tools Evaluation (HEAD-1770003038)

Score Average Improvements Regressions
Tools_called 0% (+0pp) - -
Llm_calls 0 (+0) - -
Tool_calls 0 (+0) - -
Errors 0 (+0) - -
Llm_errors 0 (+0) - -
Tool_errors 0 (+0) - -
Prompt_tokens 0tok (+0tok) - -
Prompt_cached_tokens 0tok (+0tok) - -
Prompt_cache_creation_tokens 0tok (+0tok) - -
Completion_tokens 0tok (+0tok) - -
Completion_reasoning_tokens 0tok (+0tok) - -
Total_tokens 0tok (+0tok) - -
Duration 11.43s (-5.71s) 1 🟢 -

Monthly Listeners Tracking Evaluation (HEAD-1770003038)

Score Average Improvements Regressions
AnswerSimilarity 79.1% - -
Llm_calls 2 - -
Tool_calls 0 - -
Errors 0 - -
Llm_errors 0 - -
Tool_errors 0 - -
Prompt_tokens 0tok - -
Prompt_cached_tokens 0tok - -
Prompt_cache_creation_tokens 0tok - -
Completion_tokens 0tok - -
Completion_reasoning_tokens 0tok - -
Total_tokens 0tok - -
Duration 13.87s - -

Search Web Tool Evaluation (HEAD-1770003038)

Score Average Improvements Regressions
AnswerCorrectness 28.4% (+0pp) 5 🟢 6 🔴
Llm_calls 3 (+0) - -
Tool_calls 0 (+0) - -
Errors 0 (+0) - -
Llm_errors 0 (+0) - -
Tool_errors 0 (+0) - -
Prompt_tokens 0tok (+0tok) - -
Prompt_cached_tokens 0tok (+0tok) - -
Prompt_cache_creation_tokens 0tok (+0tok) - -
Completion_tokens 0tok (+0tok) - -
Completion_reasoning_tokens 0tok (+0tok) - -
Completion_accepted_prediction_tokens 0tok (+0tok) - -
Completion_rejected_prediction_tokens 0tok (+0tok) - -
Completion_audio_tokens 0tok (+0tok) - -
Total_tokens 0tok (+0tok) - -
Duration 22.75s (+1.26s) 6 🟢 5 🔴

Social Scraping Evaluation (HEAD-1770003037)

Score Average Improvements Regressions
Tools_called 0% (+0pp) - -
Llm_calls 0 (+0) - -
Tool_calls 0 (+0) - -
Errors 0 (+0) - -
Llm_errors 0 (+0) - -
Tool_errors 0 (+0) - -
Prompt_tokens 0tok (+0tok) - -
Prompt_cached_tokens 0tok (+0tok) - -
Prompt_cache_creation_tokens 0tok (+0tok) - -
Completion_tokens 0tok (+0tok) - -
Completion_reasoning_tokens 0tok (+0tok) - -
Total_tokens 0tok (+0tok) - -
Duration 22.92s (+0.34s) 2 🟢 4 🔴

Spotify Followers Evaluation (HEAD-1770003037)

Score Average Improvements Regressions
AnswerCorrectness 20.6% (0pp) 2 🟢 3 🔴
Llm_calls 3 (+0) - -
Tool_calls 0 (+0) - -
Errors 0 (+0) - -
Llm_errors 0 (+0) - -
Tool_errors 0 (+0) - -
Prompt_tokens 0tok (+0tok) - -
Prompt_cached_tokens 0tok (+0tok) - -
Prompt_cache_creation_tokens 0tok (+0tok) - -
Completion_tokens 0tok (+0tok) - -
Completion_reasoning_tokens 0tok (+0tok) - -
Completion_accepted_prediction_tokens 0tok (+0tok) - -
Completion_rejected_prediction_tokens 0tok (+0tok) - -
Completion_audio_tokens 0tok (+0tok) - -
Total_tokens 0tok (+0tok) - -
Duration 13.35s (+1.11s) 2 🟢 3 🔴

Spotify Tools Evaluation (HEAD-1770003037)

Score Average Improvements Regressions
Tools_called 0% (+0pp) - -
Llm_calls 0 (+0) - -
Tool_calls 0 (+0) - -
Errors 0 (+0) - -
Llm_errors 0 (+0) - -
Tool_errors 0 (+0) - -
Prompt_tokens 0tok (+0tok) - -
Prompt_cached_tokens 0tok (+0tok) - -
Prompt_cache_creation_tokens 0tok (+0tok) - -
Completion_tokens 0tok (+0tok) - -
Completion_reasoning_tokens 0tok (+0tok) - -
Total_tokens 0tok (+0tok) - -
Duration 26.63s (-3.43s) 1 🟢 1 🔴

TikTok Analytics Questions Evaluation (HEAD-1770003037)

Score Average Improvements Regressions
Question_answered 0% (-5pp) - 1 🔴
Llm_calls 0 (+0) - -
Tool_calls 0 (+0) - -
Errors 0 (+0) - -
Llm_errors 0 (+0) - -
Tool_errors 0 (+0) - -
Prompt_tokens 0tok (+0tok) - -
Prompt_cached_tokens 0tok (+0tok) - -
Prompt_cache_creation_tokens 0tok (+0tok) - -
Completion_tokens 0tok (+0tok) - -
Completion_reasoning_tokens 0tok (+0tok) - -
Total_tokens 0tok (+0tok) - -
Duration 14.3s (-3.27s) 1 🟢 1 🔴

- Remove artist_composio_connections table and related code
- Remove /complete endpoint (no longer needed)
- Use artistId directly as Composio entity when connecting
- Query Composio at chat time for artist connections
- Pass connections to user session via connectedAccounts

Composio is now the source of truth for artist connections.
- Add TDD to code principles
- Document thin route files pattern (follow /api/pulses)
- Document handler functions pattern
- Document combined request validators (validateXxxRequest)
- Add DRY guidance for entity types (use options, not duplicate files)
- Add file naming convention (name after function, not constant)
- Add testing requirements for API changes
- Merge test branch to sync with base
- Update test to expect (accountId, artistId, roomId) signature
- Add test case for when artistId is provided
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@app/api/connectors/authorize/route.ts`:
- Around line 23-26: The request currently accepts account_id in the body
(documented in the route JSDoc and extracted in validateAuthorizeConnectorBody /
validateAuthorizeConnectorRequest), which violates the guideline; remove
account_id from the body schema and JSDoc, stop extracting it from
validateAuthorizeConnectorBody/validateAuthorizeConnectorRequest, and instead
obtain the target account from the authentication context in the route handler
(or accept it as a query/path param and validate it against the authenticated
principal before use); update the route.ts JSDoc,
validateAuthorizeConnectorBody, and validateAuthorizeConnectorRequest to reflect
the new source (auth context or query) and ensure authorization checks compare
the requested account id to the authenticated user's allowed accounts.

In `@lib/composio/connectors/validateAuthorizeConnectorBody.ts`:
- Around line 5-11: The schema authorizeConnectorBodySchema currently includes
account_id which violates the guideline to never accept account_id in request
bodies; remove account_id from authorizeConnectorBodySchema (or rename it to a
clearly distinct field such as target_entity_id if the caller must supply a
different entity ID) and update any callers/validators to derive the real
account ID from authentication instead of the body; additionally, if an
identifier must be passed via HTTP, move it to a query param handler rather than
the body and update any references to account_id in related code (e.g.,
validateDisconnectConnectorBody consumers) to use the new name or auth-derived
account ID.

In `@lib/composio/connectors/validateAuthorizeConnectorRequest.ts`:
- Around line 66-77: The validator currently builds connector-specific auth
configs (authConfigs) inside validateAuthorizeConnectorRequest — move that
responsibility to a new helper and call it from the handler: create a new
function buildConnectorAuthConfigs(connector: string):
Record<string,string>|undefined that encapsulates the TikTok env-var logic (and
future connector rules) and remove the authConfigs construction from
validateAuthorizeConnectorRequest so it only returns validated account_id and
connector; then update authorizeConnectorHandler (or the handler that calls
authorizeConnector) to call buildConnectorAuthConfigs(validated.connector) and
attach the returned authConfigs before invoking authorizeConnector.

In `@lib/composio/connectors/validateDisconnectConnectorBody.ts`:
- Around line 5-8: The schema disconnectConnectorBodySchema currently accepts
account_id which violates the guideline to never accept account_id in request
bodies; remove account_id from disconnectConnectorBodySchema (and the analogous
authorize body schema) and ensure the code paths that consumed it in
app/api/connectors/route.ts derive the account ID from authentication instead;
if you truly need to act on a different entity, replace account_id with a
clearly-named field like target_entity_id or move it to a query parameter and
update consumers to treat it as a deliberate target (not the caller's account).
🧹 Nitpick comments (2)
lib/composio/connectors/validateAuthorizeConnectorRequest.ts (2)

7-8: Minor: consolidate imports from the same module.

Both isAllowedArtistConnector and ALLOWED_ARTIST_CONNECTORS are imported from "./isAllowedArtistConnector". Combine them into a single import statement.

♻️ Suggested fix
-import { isAllowedArtistConnector } from "./isAllowedArtistConnector";
-import { ALLOWED_ARTIST_CONNECTORS } from "./isAllowedArtistConnector";
+import { isAllowedArtistConnector, ALLOWED_ARTIST_CONNECTORS } from "./isAllowedArtistConnector";

31-86: Function exceeds the 50-line guideline.

validateAuthorizeConnectorRequest spans ~56 lines. Extracting the authConfigs construction (as suggested above) and potentially the account-access + artist-restriction block into a helper would bring it under the limit and improve readability.

As per coding guidelines: "Keep functions under 50 lines".

Comment on lines 23 to 26
* Request body:
* - connector: The connector slug, e.g., "googlesheets" (required)
* - connector: The connector slug, e.g., "googlesheets" or "tiktok" (required)
* - callback_url: Optional custom callback URL after OAuth
* - account_id: Optional entity ID (e.g., artist ID) for entity-specific connections
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

account_id in the request body violates coding guidelines.

The JSDoc documents account_id as a body parameter, and the underlying handler/validator (validateAuthorizeConnectorRequest) extracts account_id from the parsed body. The coding guidelines explicitly state: "Never use account_id in request bodies or tool schemas — always derive account ID from authentication."

The account (or artist) to act upon should be derived from authentication context or passed via a route/query parameter that is cross-checked against the authenticated principal — not accepted as a trusted field in the request body. Accepting account_id in the body opens the door to authorization bypass if access checks are ever loosened or missed.

Consider redesigning so that the target entity is either:

  1. Derived entirely from auth context, or
  2. Passed as a path/query param (e.g., /api/connectors/authorize?account_id=...) with the access check kept in place.

This applies to the full handler chain — validateAuthorizeConnectorBody, validateAuthorizeConnectorRequest, and this route's JSDoc should all be updated.

As per coding guidelines: "Never use account_id in request bodies or tool schemas - always derive account ID from authentication."

🤖 Prompt for AI Agents
In `@app/api/connectors/authorize/route.ts` around lines 23 - 26, The request
currently accepts account_id in the body (documented in the route JSDoc and
extracted in validateAuthorizeConnectorBody /
validateAuthorizeConnectorRequest), which violates the guideline; remove
account_id from the body schema and JSDoc, stop extracting it from
validateAuthorizeConnectorBody/validateAuthorizeConnectorRequest, and instead
obtain the target account from the authentication context in the route handler
(or accept it as a query/path param and validate it against the authenticated
principal before use); update the route.ts JSDoc,
validateAuthorizeConnectorBody, and validateAuthorizeConnectorRequest to reflect
the new source (auth context or query) and ensure authorization checks compare
the requested account id to the authenticated user's allowed accounts.

Comment on lines 5 to 11
export const authorizeConnectorBodySchema = z.object({
connector: z
.string({ message: "connector is required" })
.min(1, "connector cannot be empty (e.g., 'googlesheets', 'gmail')"),
.min(1, "connector cannot be empty (e.g., 'googlesheets', 'tiktok')"),
callback_url: z.string().url("callback_url must be a valid URL").optional(),
account_id: z.string().uuid("account_id must be a valid UUID").optional(),
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Same account_id in request body concern applies here.

As noted in validateDisconnectConnectorBody.ts, accepting account_id in the request body conflicts with the coding guideline. The same recommendation applies: consider a query param or a distinctly-named field like target_entity_id.

As per coding guidelines, {app/api/**/*.ts,lib/mcp/tools/**/*.ts}: "Never use account_id in request bodies or tool schemas - always derive account ID from authentication".

🤖 Prompt for AI Agents
In `@lib/composio/connectors/validateAuthorizeConnectorBody.ts` around lines 5 -
11, The schema authorizeConnectorBodySchema currently includes account_id which
violates the guideline to never accept account_id in request bodies; remove
account_id from authorizeConnectorBodySchema (or rename it to a clearly distinct
field such as target_entity_id if the caller must supply a different entity ID)
and update any callers/validators to derive the real account ID from
authentication instead of the body; additionally, if an identifier must be
passed via HTTP, move it to a query param handler rather than the body and
update any references to account_id in related code (e.g.,
validateDisconnectConnectorBody consumers) to use the new name or auth-derived
account ID.

Comment on lines 66 to 77
// Build auth configs for custom OAuth
const authConfigs: Record<string, string> = {};
if (connector === "tiktok" && process.env.COMPOSIO_TIKTOK_AUTH_CONFIG_ID) {
authConfigs.tiktok = process.env.COMPOSIO_TIKTOK_AUTH_CONFIG_ID;
}

return {
composioEntityId: account_id,
connector,
callbackUrl: callback_url,
authConfigs: Object.keys(authConfigs).length > 0 ? authConfigs : undefined,
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Connector-specific auth config construction doesn't belong in a validation function — violates single responsibility.

Lines 66–70 build TikTok-specific authConfigs by reading an environment variable. This is business/integration logic, not request validation. It couples the validator to connector-specific implementation details, making it harder to extend when new connectors (with their own auth configs) are added.

Extract authConfigs construction into a dedicated helper (e.g., buildConnectorAuthConfigs(connector: string)) and call it from the handler, not the validator. The validator should return the validated account_id and connector, and the handler should enrich the params before calling authorizeConnector.

♻️ Sketch of the separation

In a new file like lib/composio/connectors/buildConnectorAuthConfigs.ts:

export function buildConnectorAuthConfigs(
  connector: string,
): Record<string, string> | undefined {
  const configs: Record<string, string> = {};
  if (connector === "tiktok" && process.env.COMPOSIO_TIKTOK_AUTH_CONFIG_ID) {
    configs.tiktok = process.env.COMPOSIO_TIKTOK_AUTH_CONFIG_ID;
  }
  return Object.keys(configs).length > 0 ? configs : undefined;
}

Then in authorizeConnectorHandler.ts, after validation:

const authConfigs = buildConnectorAuthConfigs(validated.connector);

This also helps address the function exceeding the 50-line guideline limit.

As per coding guidelines: "Single responsibility per function" and "Keep functions under 50 lines".

🤖 Prompt for AI Agents
In `@lib/composio/connectors/validateAuthorizeConnectorRequest.ts` around lines 66
- 77, The validator currently builds connector-specific auth configs
(authConfigs) inside validateAuthorizeConnectorRequest — move that
responsibility to a new helper and call it from the handler: create a new
function buildConnectorAuthConfigs(connector: string):
Record<string,string>|undefined that encapsulates the TikTok env-var logic (and
future connector rules) and remove the authConfigs construction from
validateAuthorizeConnectorRequest so it only returns validated account_id and
connector; then update authorizeConnectorHandler (or the handler that calls
authorizeConnector) to call buildConnectorAuthConfigs(validated.connector) and
attach the returned authConfigs before invoking authorizeConnector.

Comment on lines 5 to 8
export const disconnectConnectorBodySchema = z.object({
connected_account_id: z.string().min(1, "connected_account_id is required"),
account_id: z.string().uuid("account_id must be a valid UUID").optional(),
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

account_id in request body conflicts with coding guidelines.

The coding guidelines state: "Never use account_id in request bodies or tool schemas - always derive account ID from authentication." This schema accepts account_id in the DELETE body, which flows through app/api/connectors/route.ts. The same pattern appears in the authorize body schema.

If the intent is to allow acting on behalf of another entity (e.g., an artist), consider using a query parameter or a differently-named field (e.g., target_entity_id) to distinguish it from the authenticated account's ID and clarify that this isn't the caller's identity. Otherwise, an explicit exemption or guideline update would help keep things consistent.

As per coding guidelines, {app/api/**/*.ts,lib/mcp/tools/**/*.ts}: "Never use account_id in request bodies or tool schemas - always derive account ID from authentication".

🤖 Prompt for AI Agents
In `@lib/composio/connectors/validateDisconnectConnectorBody.ts` around lines 5 -
8, The schema disconnectConnectorBodySchema currently accepts account_id which
violates the guideline to never accept account_id in request bodies; remove
account_id from disconnectConnectorBodySchema (and the analogous authorize body
schema) and ensure the code paths that consumed it in
app/api/connectors/route.ts derive the account ID from authentication instead;
if you truly need to act on a different entity, replace account_id with a
clearly-named field like target_entity_id or move it to a query parameter and
update consumers to treat it as a deliberate target (not the caller's account).

- API is now unopinionated: any account can connect any service
- Tool router handles collision prevention in createToolRouterSession
- Account connections always win over artist connections (no duplicates)
- Remove allowedToolkits from GET/authorize validation
- Update tests for new architecture
sweetmantech and others added 3 commits February 11, 2026 23:40
Consolidates the authorize endpoint into the main connectors route
to match the updated API docs.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…Access

SRP: each query is now its own file with dedicated tests:
- selectAccountArtistId (account_artist_ids)
- selectArtistOrganizationIds (artist_organization_ids)
- selectAccountOrganizationIds (account_organization_ids)

Co-Authored-By: Claude Opus 4.6 <[email protected]>
const { artist_id, connector } = validated;

// Verify connector is allowed
if (!isAllowedArtistConnector(connector)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SRP

  • actual: multiple validations happening in the API endpoint definition file.
  • required: move any verification of input params into the validateAuthorizeArtistConnectorBody function.


// Verify user has access to this artist
const hasAccess = await checkAccountArtistAccess(accountId, artist_id);
if (!hasAccess) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SRP

  • actual: multiple validations happening in the API endpoint definition file.
  • required: move any verification of input params into the validateAuthorizeArtistConnectorBody function.
  • Check out other example endpoints like /api/pulses to see a cleaner implementation.

* @param request - The incoming request
* @returns List of connectors with connection status
*/
export async function GET(request: NextRequest): Promise<NextResponse> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SRP

  • actual: endpoint file also defines the handler
  • required: standalone handler function similar to /api/pulses

const { searchParams } = new URL(request.url);
const artistId = searchParams.get("artist_id");

if (!artistId) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SRP

  • actual: multiple validations happening in the API endpoint definition file.
  • required: move any verification of input params into a verify function.
  • Check out other example endpoints like /api/pulses to see a cleaner implementation.


// Verify user has access to this artist
const hasAccess = await checkAccountArtistAccess(accountId, artistId);
if (!hasAccess) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SRP

  • actual: multiple validations happening in the API endpoint definition file.
  • required: move any verification of input params into a verify function.
  • Check out other example endpoints like /api/pulses to see a cleaner implementation.

});
vi.mocked(checkAccountArtistAccess).mockResolvedValue(true);

const request = new NextRequest(`http://localhost/api/connectors?entity_id=${mockEntityId}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we using entity_id instead of account_id?

* Custom auth configs for toolkits that require custom OAuth credentials.
* e.g., { tiktok: "ac_xxxxx" }
*/
authConfigs?: Record<string, string>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is authConfigs a generic Record<string, string> type instead of a more explicit typing?

*
* Why: Used by the /api/connectors/authorize endpoint to let users
* connect from the settings page (not in-chat).
* The entityId is an account ID - either the caller's own account or
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you using entityId instead of accountId?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are any changes needed to this file?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lib/supabase/account_workspace_ids/checkAccountWorkspaceAccess.ts -
refactor to selectAccountWorkspaces

…Access

SRP: standalone supabase query with dedicated tests.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Use "account" terminology consistently per codebase conventions.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
No caller uses this option — YAGNI.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
The comment claimed ownership verification that the function doesn't do.
Updated to reflect it only validates request shape.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Not a direct Supabase query — it aggregates supabase calls, so it
belongs in the domain layer.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…ntWorkspaceId directly

YAGNI — the wrapper was just !!data. The sole consumer now calls
the supabase query directly.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
*/
export async function authorizeConnector(
userId: string,
entityId: string,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lib/composio/connectors/authorizeConnector.ts is there any reason
this needs to be called entityId? We prefer accountId

Comment on lines 60 to 78

// If filtering, ensure we return all allowed toolkits (even if not in Composio response)
if (allowedToolkits) {
const existingSlugs = new Set(connectors.map(c => c.slug));
for (const slug of allowedToolkits) {
if (!existingSlugs.has(slug)) {
connectors.push({
slug,
name: displayNames[slug] || slug,
isConnected: false,
connectedAccountId: undefined,
});
}
}
// Filter to only allowed and maintain order
return allowedToolkits.map(slug => connectors.find(c => c.slug === slug)!);
}

return connectors;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are the bottom filtering doing in
lib/composio/connectors/getConnectors.ts

Image

Adds custom Zod messages and missing_fields/status fields to error
responses for both POST and DELETE /api/connectors validation, matching
the standard pattern used across other API endpoints.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@sweetmantech sweetmantech merged commit 06a8b9a into test Feb 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants