API Reference

Convex Functions

Server-side queries, mutations, and actions for operational data

Function Types

Convex provides three types of functions:

TypePurposeCan Read DBCan Write DBCan Call External APIs
QueryRead data
MutationWrite data
ActionExternal APIs❌ (via mutations)

Organizations

Workflows

Tracked Queries

Access Control

All Convex functions enforce access control:

// Admins have access to ALL organizations in their company
const membership = await ctx.db
  .query("company_members")
  .withIndex("by_user_company", q =>
    q.eq("userId", userId).eq("companyId", companyId)
  )
  .unique();

if (membership.role === "admin") {
  // Full company access
}
// Regular users must be in organization_access table
const access = await ctx.db
  .query("organization_access")
  .withIndex("by_company_org_user", q =>
    q.eq("companyId", companyId)
     .eq("organizationId", organizationId)
     .eq("userId", userId)
  )
  .first();

if (!access) {
  throw new Error("Access denied");
}

Best Practices

Query Optimization

Always use indexes - Never use .filter() for lookups that can use indexes.

// ❌ BAD - Full table scan
const records = await ctx.db
  .query("analytics_llm_responses")
  .filter(q => q.eq(q.field("organizationId"), orgId))
  .collect();

// ✅ GOOD - Index-based lookup
const records = await ctx.db
  .query("analytics_llm_responses")
  .withIndex("by_org_week", q =>
    q.eq("organizationId", orgId)
     .eq("weekNumber", weekNum)
  )
  .collect();

Pagination

For large result sets, use pagination:

import { paginationOptsValidator } from "convex/server";

export const getResponses = query({
  args: {
    organizationId: v.id("organizations"),
    paginationOpts: paginationOptsValidator
  },
  handler: async (ctx, args) => {
    return await ctx.db
      .query("analytics_llm_responses")
      .withIndex("by_org", q => q.eq("organizationId", args.organizationId))
      .paginate(args.paginationOpts);
  }
});

Error Handling

Convex functions throw errors that are serialized to the client:

try {
  const result = await client.mutation(api.func, { args });
} catch (error) {
  if (error.message.includes("Access denied")) {
    // Handle permission error
  } else if (error.message.includes("not found")) {
    // Handle not found
  }
}

Next Steps