Skip to content

SpecMarket’s backend runs on Convex, a serverless backend platform. All API endpoints are defined in TypeScript and accessed via the Convex client SDK.

This reference documents:

  • Queries — Read-only operations (no cost, cacheable)
  • Mutations — Writes and state changes (billable)
  • Actions — External service calls (billable)
  • Internal functions — Marked with _ prefix, called only by other backend functions

Authentication

Most endpoints require authentication via Clerk. Use the Convex client SDK:

import { useQuery, useMutation } from "convex/react";
import { api } from "@/convex/_generated/api";
 
// Call a query
const user = useQuery(api.users.getMe);
 
// Call a mutation
const updateProfile = useMutation(api.users.updateProfile);
await updateProfile({ displayName: "Alice" });

The requireAuth() helper in backend code ensures the authenticated user exists before proceeding.


Users Module

User profiles, authentication state, reputation tracking.

getMe — Query

Returns the authenticated user’s profile.

Arguments: None

Returns:

{
  _id: Id<"users">
  clerkId: string
  username: string
  displayName: string
  email: string
  avatarUrl?: string
  role: "user" | "creator" | "admin"
  totalSpecsPublished: number
  totalRunsOfSpecs: number
  totalBountiesWon: number
  totalEarningsUsd: number
  reputationScore: number
  createdAt: number
}

Throws: Error if not authenticated.

Example:

const user = useQuery(api.users.getMe);
console.log(`Hello ${user?.displayName}`);

getProfile — Query

Returns a public user profile by username or user ID.

Arguments:

{
  identifier: string  // username or user ID
}

Returns: User profile object (same shape as getMe) or null if not found.

Example:

const alice = useQuery(api.users.getProfile, { identifier: "alice" });

updateProfile — Mutation

Updates the authenticated user’s profile.

Arguments:

{
  displayName?: string      // Full name
  avatarUrl?: string        // Avatar image URL
  // other fields may be added in future versions
}

Returns: Updated user ID

Authentication: Required

Example:

await updateProfile({
  displayName: "Alice Chen",
  avatarUrl: "https://example.com/avatar.jpg"
});

getLeaderboard — Query

Returns top creators ranked by reputation score.

Arguments:

{
  limit?: number  // Max results (default: 20, max: 100)
}

Returns:

Array<{
  username: string
  displayName: string
  reputationScore: number
  totalSpecsPublished: number
  totalEarningsUsd: number
}>

Example:

const topCreators = useQuery(api.users.getLeaderboard, { limit: 10 });

Specs Module

Spec publication, search, discovery, and versioning.

publish — Mutation

Publishes a new spec or updates an existing one.

Arguments:

{
  slug: string                    // URL-safe name (e.g., "my-awesome-spec")
  displayName: string             // Human-readable name
  description: string             // Short description
  replacesSaas?: string          // SaaS tool it replaces (e.g., "Docusign")
  replacesPricing?: string       // Pricing claim (e.g., "$6000/yr")
  tags: string[]                  // Search tags (e.g., ["pdf", "async"])
  outputType: enum                // "web-app" | "cli-tool" | "api-service" | "library" | "mobile-app"
  primaryStack: enum              // "nextjs-typescript" | "astro-typescript" | "python-fastapi" | "go" | "rust" | "other"
  version: string                 // Semver (e.g., "1.0.0")
  specStorageId: Id<"_storage">  // Storage ID of uploaded spec ZIP
  readme?: string                 // Optional README markdown
  runner: string                  // Agent type (e.g., "claude-opus-4-6")
  minModel: string                // Minimum required model
  estimatedTokens: number         // API tokens for a full run
  estimatedCostUsd: number        // Estimated total cost in USD
  estimatedTimeMinutes: number    // Expected runtime in minutes
  infrastructure?: object         // Service requirements and costs
}

Returns: Spec ID

Authentication: Required. Auto-promotes user from “user” to “creator” on first publish.

Rate Limit: 5 specs per user per 24 hours.

Business Rules:

  • Slug must be globally unique
  • Version must be valid semver and strictly greater than previous versions
  • Triggers async content security scan
  • Triggers async metric recalculation

Example:

const specId = await publish({
  slug: "docusign-replacement",
  displayName: "DocuSign Replacement",
  description: "Open-source document signing",
  replacesSaas: "DocuSign",
  replacesPricing: "$6,000/year",
  tags: ["pdf", "e-signature", "documents"],
  outputType: "web-app",
  primaryStack: "nextjs-typescript",
  version: "1.0.0",
  specStorageId: storageId,
  runner: "claude-opus-4-6",
  minModel: "claude-opus-4-6",
  estimatedTokens: 50000,
  estimatedCostUsd: 8.50,
  estimatedTimeMinutes: 45,
});

search — Query

Searches published specs with optional filters.

Arguments:

{
  query?: string              // Text search (name, description, tags)
  primaryStack?: string       // Filter by stack
  replacesSaas?: string      // Filter by SaaS tool
  minSuccessRate?: number    // Minimum success rate (0-1)
  maxCostUsd?: number        // Max cost per run
  maxMonthlyCostUsd?: number // Max monthly infrastructure cost
  serviceCategories?: string[] // Filter by service category
  freeTierOnly?: boolean     // Only free services
  tags?: string[]            // Filter by tags (OR logic)
  paginationOpts?: {
    numItems: number
    cursor?: string
  }
}

Returns:

{
  page: Array<{
    _id: Id<"specs">
    scopedName: string          // "@author/spec-name"
    displayName: string
    description: string
    replacesSaas?: string
    authorUsername: string
    successRate: number         // 0-1
    totalRuns: number
    avgCostUsd: number
    avgTimeMinutes: number
    outputType: string
    primaryStack: string
    currentVersion: string
    tags: string[]
    featured: boolean
  }>
  isDone: boolean
  continueCursor: string
}

Example:

const results = useQuery(api.specs.search, {
  query: "docusign",
  primaryStack: "nextjs-typescript",
  minSuccessRate: 0.7,
  paginationOpts: { numItems: 20 }
});

get — Query

Fetches a single spec by scoped name (e.g., “@alice/my-spec”).

Arguments:

{
  scopedName: string  // Full spec name with author
}

Returns:

{
  _id: Id<"specs">
  scopedName: string
  displayName: string
  description: string
  replacesSaas?: string
  replacesPricing?: string
  authorId: Id<"users">
  authorUsername: string
  outputType: string
  primaryStack: string
  currentVersion: string
  tags: string[]
  successRate: number
  totalRuns: number
  avgCostUsd: number
  avgTimeMinutes: number
  featured: boolean
  createdAt: number
  updatedAt: number
  infrastructure?: object
  minModel: string
  estimatedTokens: number
  estimatedCostUsd: number
  estimatedTimeMinutes: number
  // ... additional fields
}

Example:

const spec = useQuery(api.specs.get, { scopedName: "@alice/docusign" });

fork — Mutation

Forks another creator’s spec (creates a copy under your username).

Arguments:

{
  sourceSpecId: Id<"specs">
  newSlug: string             // Slug for your fork
  newDisplayName: string      // Display name for your fork
}

Returns: New spec ID

Authentication: Required

Example:

const forkedId = await fork({
  sourceSpecId: aliceSpec._id,
  newSlug: "docusign-lightweight",
  newDisplayName: "DocuSign Lightweight Edition"
});

getVersions — Query

Lists version history for a spec.

Arguments:

{
  specId: Id<"specs">
  limit?: number  // Default: 20
}

Returns:

Array<{
  version: string
  changelog?: string
  createdAt: number
  creatorId: Id<"users">
  specStorageId: Id<"_storage">
}>

getMySpecs — Query

Lists all specs published by the authenticated user.

Arguments: None

Returns: Array of spec objects

Authentication: Required


Runs Module

Run submissions, telemetry, and performance tracking.

submit — Mutation

Submits a run report after executing a spec.

Arguments:

{
  specId: Id<"specs">
  specVersion: string
  model: string               // e.g., "claude-sonnet-4"
  totalCostUsd: number
  totalTimeSeconds: number
  successCriteria: Array<{
    name: string
    passed: boolean
  }>
  status: "success" | "failed" | "partial"
  errorMessage?: string
}

Returns: Run ID

Authentication: Optional (telemetry is opt-in by default)

Rate Limit: 10 runs per user per hour

Example:

const runId = await submit({
  specId: spec._id,
  specVersion: "1.0.0",
  model: "claude-sonnet-4",
  totalCostUsd: 8.50,
  totalTimeSeconds: 2400,  // 40 minutes
  successCriteria: [
    { name: "app starts", passed: true },
    { name: "database persists", passed: true },
    { name: "email sending", passed: false }
  ],
  status: "partial",
  errorMessage: "Email configuration missing"
});

getForSpec — Query

Lists all runs for a specific spec.

Arguments:

{
  specId: Id<"specs">
  paginationOpts?: { numItems: number; cursor?: string }
}

Returns: Paginated run records with spec metrics


getForUser — Query

Lists all runs submitted by the authenticated user.

Arguments: None

Returns: Array of run records

Authentication: Required


Ratings Module

Community ratings and reviews for specs.

submit — Mutation

Submits a rating for a spec.

Arguments:

{
  specId: Id<"specs">
  rating: number              // 1-5 stars
  review?: string             // Optional review text
}

Returns: Rating ID

Authentication: Required

Example:

await submitRating({
  specId: spec._id,
  rating: 4,
  review: "Great spec, but needs better error messages"
});

getForSpec — Query

Lists all ratings and reviews for a spec.

Arguments:

{
  specId: Id<"specs">
  paginationOpts?: { numItems: number; cursor?: string }
}

Returns:

Array<{
  rating: number
  review?: string
  authorUsername: string
  createdAt: number
}>

Bounties Module

Funding requests, contributions, and payouts.

create — Mutation

Creates a bounty for a spec.

Arguments:

{
  specId: Id<"specs">
  title: string               // Bounty title
  description: string         // What the bounty funds
  targetFundingUsd: number   // Goal amount
  deadline?: number           // Unix timestamp
}

Returns: Bounty ID

Authentication: Required. Must be spec author or admin.

Example:

const bountyId = await create({
  specId: spec._id,
  title: "Add Stripe integration",
  description: "Extend the spec to accept credit card payments",
  targetFundingUsd: 1000,
  deadline: Date.now() + 30 * 24 * 60 * 60 * 1000  // 30 days
});

list — Query

Lists all active bounties.

Arguments:

{
  status?: "open" | "funded" | "completed"
  tags?: string[]          // Filter by tags (OR logic)
  replacesSaas?: string   // Filter by SaaS
  paginationOpts?: { numItems: number; cursor?: string }
}

Returns:

Array<{
  _id: Id<"bounties">
  specId: Id<"specs">
  title: string
  description: string
  targetFundingUsd: number
  currentFundingUsd: number
  deadline?: number
  status: string
  posterUsername: string
  createdAt: number
}>

submit — Mutation

Submits a solution to a bounty.

Arguments:

{
  bountyId: Id<"bounties">
  solutionDescription: string
  solutionSpecId: Id<"specs">  // The spec fork that solves it
}

Returns: Submission ID

Authentication: Required


award — Mutation

Awards a bounty to a submission.

Arguments:

{
  bountyId: Id<"bounties">
  submissionId: Id<"bountySubmissions">
}

Returns: Award ID

Authentication: Required. Must be bounty poster.


Stats Module

Platform-wide metrics and analytics.

getGlobal — Query

Returns global platform statistics.

Arguments: None

Returns:

{
  totalSpecs: number
  totalRuns: number
  totalCreators: number
  totalUsers: number
  avgSuccessRate: number      // 0-1
  totalFundingPoolUsd: number
  platformUptime: number      // percentage
  savingsEstimateUsd: number  // Total cost saved vs SaaS
}

Example:

const stats = useQuery(api.stats.getGlobal);
console.log(`${stats.totalSpecs} specs, ${stats.totalRuns} runs`);

Payments Module

Stripe integration for bounties and Managed Runs.

createCheckoutSession — Action

Creates a Stripe checkout session for contributing to a bounty.

Arguments:

{
  bountyId: Id<"bounties">
  amountUsd: number           // Contribution amount
  redirectUrl: string         // Where to redirect after payment
}

Returns:

{
  sessionId: string           // Stripe session ID
  checkoutUrl: string         // Stripe checkout URL
}

Example:

const checkout = await createCheckoutSession({
  bountyId: bounty._id,
  amountUsd: 100,
  redirectUrl: window.location.href
});
window.location.href = checkout.checkoutUrl;

Managed Runs Module

Cloud-hosted spec execution (Phase 2).

request — Mutation

Requests a cloud-hosted spec run.

Arguments:

{
  specId: Id<"specs">
  specVersion?: string
  model?: string
  webhookUrl?: string         // Optional: notify when complete
}

Returns: Managed run ID

Authentication: Required

Note: Currently scaffolded. Full support launches in Phase 2.


cancel — Mutation

Cancels a pending managed run.

Arguments:

{
  runId: Id<"managedRuns">
}

Returns: Success status

Authentication: Required. Must be run requester.


getForUser — Query

Lists managed runs requested by the authenticated user.

Arguments: None

Returns: Array of managed run records

Authentication: Required


Error Handling

All mutations and queries may throw errors. Handle them consistently:

try {
  const result = await publish({ /* ... */ });
  console.log("Published:", result);
} catch (error) {
  if (error instanceof Error) {
    if (error.message.includes("rate limit")) {
      console.error("Too many publishes. Try again in 24 hours.");
    } else if (error.message.includes("authentication")) {
      console.error("Please sign in to publish specs.");
    } else {
      console.error("Error:", error.message);
    }
  }
}

Pagination

Queries that return large result sets use cursor-based pagination:

let cursor: string | undefined;
let allResults: any[] = [];
let isDone = false;
 
while (!isDone) {
  const response = await client.query(api.specs.search, {
    query: "react",
    paginationOpts: {
      numItems: 20,
      cursor: cursor
    }
  });
 
  allResults = allResults.concat(response.page);
  isDone = response.isDone;
  cursor = response.continueCursor;
}

Rate Limits

  • Spec publish: 5 per user per 24 hours
  • Run submission: 10 per user per hour
  • Search queries: 100 per IP per minute

SDK Integration

See the Convex documentation for:


Questions?

This reference is auto-generated from the Convex schema. Report inaccuracies or outdated information via GitHub issues.