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?
- API Issues: GitHub Issues
- Convex Docs: docs.convex.dev
- Support: support@specmarket.dev
This reference is auto-generated from the Convex schema. Report inaccuracies or outdated information via GitHub issues.