paperclipai/paperclip · error · ToolGatewayHttpError

organization_authorization_required

organization_authorization_required

Error message

Organization authorization is required

What it means

This ToolGatewayHttpError (HTTP 409) is thrown by the tool gateway's grant resolution when a connection's credential policy requires an organization-level grant, but no active default organization grant (kind='organization', isDefault=true, status='active') exists in connection_grants for that connection. The gateway refuses to proceed because there is no consent boundary authorizing the shared credential. It is a setup/completeness problem, not a runtime failure.

Solutions

  1. Re-authorize the connection at the organization level (complete the connection OAuth/install flow so an active default organization grant is created in connection_grants).
  2. Inspect connection_grants for the connectionId: SELECT * FROM connection_grants WHERE connection_id = ... AND kind = 'organization'; confirm is_default=true and status='active', and re-activate or recreate the grant if it was revoked.
  3. If the connection should be per-user instead, change the connection's credentialPolicy so grant resolution looks for a user grant rather than an organization grant.
  4. Verify the connectionId in the failing tool call matches the connection the organization actually authorized (mis-scoped connection lookups surface as a missing grant).

Example fix

// before: grant row exists but inactive
UPDATE connection_grants SET status = 'active' WHERE connection_id = $1 AND kind = 'organization';
// after (preferred): re-run the connection authorization flow so the app inserts
// { kind: 'organization', isDefault: true, status: 'active' } itself
await connectionService.authorizeForOrganization(connectionId, actorUserId);
Defensive patterns

Strategy: try-catch

Validate before calling

const [orgGrant] = await db.select().from(connectionGrants).where(and(eq(connectionGrants.connectionId, connectionId), eq(connectionGrants.kind, 'organization'), eq(connectionGrants.isDefault, true), eq(connectionGrants.status, 'active'))).limit(1);
if (!orgGrant) throw new Error(`Connection ${connectionId} has no active organization grant; authorize it first.`);

Type guard

function hasActiveOrgGrant(g: ConnectionGrant | undefined): g is ConnectionGrant {
  return !!g && g.kind === "organization" && g.isDefault === true && g.status === "active";
}

Try / catch

try {
  await callGovernedTool(session, connectionId, toolName, args);
} catch (e) {
  if (e instanceof ToolGatewayHttpError && e.code === "organization_authorization_required") {
    return { status: "needs_authorization", connectionId: e.details.connectionId };
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling a governed MCP tool through the tool gateway for a connection whose credential policy is 'organization' while the company has never completed (or has deactivated/removed) the default organization grant for that connection: e.g. the owner started OAuth connect but never finalized the org-wide authorization, or the grant row's status was flipped from 'active' to 'revoked'/'inactive'.

Common situations: An admin shared a connection at the organization level but the grant creation transaction never ran; a teammate removed or disabled the org grant while agents were still using the connection; a database restore/seeding dropped connection_grants rows so the connection exists but its grant does not.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/dd767cc6af6b9711. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/tool-gateway.ts:4423

        .limit(1);
      return grant;
    };
    const findOrganizationGrant = async () => {
      const [grant] = await db
        .select()
        .from(connectionGrants)
        .where(
          and(
            eq(connectionGrants.companyId, connection.companyId),
            eq(connectionGrants.connectionId, connection.id),
            eq(connectionGrants.kind, "organization"),
            eq(connectionGrants.isDefault, true),
            eq(connectionGrants.status, "active"),
          ),
        )
        .limit(1);
      if (!grant) {
        throw new ToolGatewayHttpError(
          409,
          "Organization authorization is required",
          "organization_authorization_required",
          {
            connectionId: connection.id,
          },
        );
      }
      const members = await db
        .select({ subjectId: connectionGrantMembers.subjectId })
        .from(connectionGrantMembers)
        .where(
          and(
            eq(connectionGrantMembers.companyId, connection.companyId),
            eq(connectionGrantMembers.grantId, grant.id),
            eq(connectionGrantMembers.subjectType, "user"),
          ),
        );

View on GitHub (pinned to 3f1d897a7c)