Skip to main content
Backpocket’s API is built on Convex, a real-time backend platform. Unlike traditional REST or GraphQL APIs, Convex provides reactive queries that automatically update when underlying data changes.

Architecture

Convex functions are organized into three types:
TypePurposeReal-timeAuth required
QueriesRead data✅ Auto-subscribes to changesVaries
MutationsWrite data✅ Triggers re-renders✅ Yes
ActionsSide effects (HTTP calls, etc.)❌ One-shot✅ Yes

Namespaces

The API is organized into namespaces:

Authentication

API calls are authenticated using Clerk JWT tokens. The Convex client handles token management automatically:
  1. User signs in via Clerk (web, mobile, or extension)
  2. Clerk issues a JWT token
  3. The Convex client includes the token in every request
  4. Convex validates the token server-side
import { ConvexProvider, ConvexReactClient } from "convex/react";
import { ClerkProvider, useAuth } from "@clerk/clerk-react";

const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);

function App() {
  return (
    <ClerkProvider publishableKey="pk_...">
      <ConvexProviderWithClerk client={convex} useAuth={useAuth}>
        <YourApp />
      </ConvexProviderWithClerk>
    </ClerkProvider>
  );
}

Real-time subscriptions

Convex queries are reactive by default. When you use a query in your UI, it automatically re-runs whenever the underlying data changes:
import { useQuery } from "convex/react";
import { api } from "../convex/_generated/api";

function SavesList() {
  // This automatically updates when saves change
  const saves = useQuery(api.saves.list, {
    visibility: "public",
    limit: 20,
  });

  return saves?.map((save) => <SaveCard key={save._id} save={save} />);
}
No polling, no WebSocket management, no manual refetching. Data updates propagate instantly to all connected clients.

Error handling

Convex functions throw typed errors that the client can catch:
import { useMutation } from "convex/react";
import { api } from "../convex/_generated/api";

function CreateSave() {
  const createSave = useMutation(api.saves.create);

  async function handleSave(url: string) {
    try {
      await createSave({ url, visibility: "private" });
    } catch (error) {
      // Handle specific errors
      console.error("Failed to save:", error.message);
    }
  }
}

Rate limiting

Some operations have rate limits to ensure fair usage:
OperationLimit
Snapshot requests50 per user per 24 hours
Public list queries5-100 items per request

Shared types

All API inputs and outputs use types from the @backpocket/types package:
import type {
  Save,
  Tag,
  Collection,
  Space,
  SaveSnapshot,
  ClientSource,
} from "@backpocket/types";
View all type definitions →