Create, Retrieve, and List Links

Purpose and Scope

This page orients developers who are using Dub’s Links API family to create, retrieve, and list short links for an authenticated workspace. In Dub terminology, a link is the core short-link resource that connects a destination URL, a short domain and key, attribution metadata, and downstream analytics or conversion tracking. The official API documentation presents the Links family under the production API server, with operations such as listing all links through GET /links, retrieving counts through GET /links/count, and bulk creation through POST /links/bulk. Those docs establish the public reader-facing contract, while the repository sources supplied here ground the implementation conventions that Dub uses for authenticated collection APIs.

The most important operational idea is that Dub API routes are workspace-scoped. A caller is not just asking for a global list of resources; they are asking for resources visible to the authenticated workspace, optionally filtered and sorted by query parameters. The provided route implementation for bounty submissions demonstrates this pattern in a neighboring API family: it resolves the workspace’s default program, validates the parent resource, parses query parameters with a Zod schema, performs a Prisma query, and returns JSON shaped by an explicit response schema. The Links API follows the same product model of authenticated workspace resources, even though the concrete source snippets for this worker are from the Bounties area.

Sources: apps/web/app/(ee)/api/bounties/[bountyId]/submissions/route.ts, apps/web/lib/openapi/bounties/list-bounty-submissions.ts

Relevant Source Files

  • apps/web/lib/openapi/bounties/list-bounty-submissions.ts — Defines a generated OpenAPI operation object with an operationId, summary, path parameter schema, query schema, JSON response schema, tags, and token security. It is useful here as a source-backed example of how Dub describes list-style API operations for generated reference docs.
  • apps/web/app/(ee)/api/bounties/[bountyId]/submissions/route.ts — Implements a workspace-authenticated GET collection route that validates a parent resource, parses filters, applies pagination and sorting, queries Prisma, normalizes related data, and returns NextResponse.json.
  • apps/web/app/(ee)/api/bounties/[bountyId]/submissions/[submissionId]/approve/route.ts — Implements a workspace-authenticated mutation route with optional body parsing, body schema validation, parent-resource verification, role and plan requirements, and JSON response behavior.
  • apps/web/app/(ee)/api/bounties/[bountyId]/submissions/[submissionId]/reject/route.ts — Mirrors the approve route for rejection workflows, showing how Dub validates optional body fields and applies role-gated review actions in the same route style.
  • apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/bounties/[bountyId]/bounty-submissions-table.tsx — Shows the partner-side table that turns API-shaped bounty submission data into a status-oriented user interface with periods, badges, dates, and actions.
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submissions-table.tsx — Shows the program-admin table that fetches a paginated API collection with SWR, workspace and route parameters, sorting query parameters, filters, and dashboard-specific columns.

For the Links API, the official documentation identifies GET /links as the operation for listing all links in the authenticated workspace. The operation is named getLinks and returns a paginated list. Common query filters include domain, deprecated tagId, tagIds, and tagNames; the docs describe domain as a way to restrict results to links on a specific short domain, while tag filters narrow the response by assigned tag identifiers or case-insensitive tag names. When building client integrations, treat listing as a queryable collection read rather than a full export: request only the slice of links needed for the current screen or workflow, and preserve pagination state in your client.

Creation and retrieval sit next to listing in the same Links family. The official snippets provided for this worker include POST /links/bulk, which bulk creates up to 100 links for the authenticated workspace, and they note that webhook events are not currently sent for bulk link creation. The same family also exposes count retrieval through GET /links/count, named getLinksCount, which shares filter concepts with GET /links. In practice, list and count are often paired: a dashboard can fetch rows with GET /links and separately fetch the total number matching the same filters for pagination, reporting, or empty-state decisions.

A link creation payload is centered on the destination url, plus optional short-link placement fields such as domain, key, and keyLength. The official bulk-create schema describes url as the destination URL, domain as the short-link domain without protocol, key as the short-link slug, and keyLength as the generated slug length when a key is not supplied. If domain is omitted, the API uses the workspace’s primary domain or falls back to dub.sh. This distinction matters for clients: a link can be created with minimal input, but deterministic branded URLs require callers to supply or configure the desired domain and key behavior.

System-to-Code Mapping

The supplied OpenAPI source shows how Dub expresses an API operation for generated documentation and SDK tooling. listBountySubmissions is a ZodOpenApiOperationObject with a stable operationId, a Speakeasy name override, a human summary and description, request parameters split into path and query schemas, a 200 response schema, shared error responses, tags, and token security. For Links operations, the same documentation shape is what lets consumers discover endpoint names, query fields, response types, and authentication expectations without reverse-engineering route handlers.

Sources: apps/web/lib/openapi/bounties/list-bounty-submissions.ts

The collection route source shows the runtime side of that contract. The GET handler is wrapped in withWorkspace, receives workspace, params, and searchParams, resolves a program identifier from the workspace, verifies that the parent bounty exists, parses query parameters through getBountySubmissionsQuerySchema, and then builds a Prisma findMany query. It filters by parent ID and optional status, group, or partner values; applies dynamic ordering; calculates skip from page and pageSize; and limits the result with take. That combination is the concrete repository pattern behind a paginated, filtered API collection.

Sources: apps/web/app/(ee)/api/bounties/[bountyId]/submissions/route.ts

The route does not return raw database rows directly. It includes related records such as user, commission, partner, and program enrollment, then maps each row into BountySubmissionExtendedSchema.parse. That schema step normalizes the partner object, preserves the partner ID, derives enrollment status when present, and attaches commission and user data before returning JSON. For Links API consumers, the lesson is that response schemas are intentional API surfaces: clients should depend on documented response fields, not on internal persistence structure or incidental relational joins.

Sources: apps/web/app/(ee)/api/bounties/[bountyId]/submissions/route.ts

Execution Flow for List-Style API Reads

A list-style read begins with authentication and workspace resolution. In the provided route, withWorkspace enforces that the request runs in a workspace context and can also enforce plan or role constraints. The submissions route allows a set of paid plans, while the approve and reject routes further require owner or member roles. For Links API calls, authentication is likewise a prerequisite in the public API experience: the caller’s token determines the workspace whose links are listed, created, counted, or retrieved. This model keeps client code simple because workspace scoping is not an afterthought; it is part of route execution.

Sources: apps/web/app/(ee)/api/bounties/[bountyId]/submissions/route.ts, apps/web/app/(ee)/api/bounties/[bountyId]/submissions/[submissionId]/approve/route.ts, apps/web/app/(ee)/api/bounties/[bountyId]/submissions/[submissionId]/reject/route.ts

After workspace resolution, parent-resource validation and query validation happen before any data is returned. In the bounty-submissions route, getBountyOrThrow ensures that the requested bounty belongs to the resolved program before the route applies filters. Then getBountySubmissionsQuerySchema.parse(searchParams) constrains status, groupId, partnerId, sortOrder, sortBy, page, and pageSize. For a Links list endpoint, analogous validation is what turns raw URL parameters such as domain and tags into safe query inputs. Clients should send explicit filters and should expect invalid filter combinations to fail before any partial data is returned.

Sources: apps/web/app/(ee)/api/bounties/[bountyId]/submissions/route.ts

Pagination and sorting are also first-class route concerns. The route computes offset pagination as (page - 1) * pageSize and uses take: pageSize to cap the result set. It builds orderBy dynamically from parsed sortBy and sortOrder, which means the accepted sort fields must be controlled by the validation schema rather than arbitrary user input. For Links listing, carry the same discipline into client integrations: preserve page, page size, sort field, and sort direction in your UI state, and reuse the same filter set when requesting link counts or additional pages.

Sources: apps/web/app/(ee)/api/bounties/[bountyId]/submissions/route.ts

Dashboard Consumption Patterns

The program dashboard table demonstrates how a Dub web client consumes a collection API. It reads the current workspace, slug, bounty ID, pagination state, router query parameters, and sort parameters, then calls SWR with a URL of the form /api/bounties/${bountyId}/submissions plus a generated query string. It passes workspaceId, sortBy, and sortOrder, excludes the sheet-specific submissionId parameter, keeps previous data during revalidation, and dedupes requests for 30 seconds. This is a practical pattern for Links dashboards too: keep collection filters in the URL, avoid refetch churn, and separate row selection from collection identity.

Sources: apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submissions-table.tsx

The same table adapts columns based on the underlying resource type. It starts with partner, group, status, completion, and review columns, then adds performance metrics for performance bounties or social metrics for submission bounties. That illustrates a useful UI principle for Links API clients: list responses often contain enough metadata to drive conditional presentation. A links table might show domain, key, destination URL, tags, UTM metadata, creation time, or attribution metrics depending on the workflow. Avoid hard-coding a single row layout when your product experience may switch between operational management and reporting views.

Sources: apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/[bountyId]/bounty-submissions-table.tsx

The partner-side bounty table shows a different consumption mode: it receives a bounty object with submissions, computes submission periods, decides whether to show a submission column based on maximum submissions, and renders status badges and submitted dates. This is not the Links API, but it highlights the same boundary between API data and user-facing derivations. Client applications should use API responses as durable facts, then derive labels, badge states, empty values, and formatted dates locally. Keeping those display decisions outside the API call makes integrations easier to evolve as the reference schema adds fields.

Sources: apps/web/app/(ee)/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/bounties/[bountyId]/bounty-submissions-table.tsx

Compact Reference

OperationPublic roleImportant inputsImplementation pattern shown by supplied sources
GET /links / getLinksList a paginated set of links in the authenticated workspaceFilters such as domain, tagIds, tagNames, plus pagination and sorting where supportedValidate query parameters, apply filters, sort, page, and return schema-shaped JSON
Link retrievalFetch a specific link resourceA stable link identifier or short-link coordinates, depending on documented endpoint variantValidate identity and workspace ownership before returning a response
Link creationCreate one link or many linksurl, optional domain, optional key, optional generated-key settingsParse body, validate schema, apply workspace defaults such as primary domain, return JSON
POST /links/bulkCreate up to 100 links for the authenticated workspaceArray of link creation objectsTreat bulk operations as explicit API flows; official docs note no webhook events for bulk creation
GET /links/count / getLinksCountCount links matching workspace filtersSimilar filters to list, including domain and tag filtersPair count with list filters for pagination and reporting

Implementation Considerations and Next Steps

When implementing against the Links API, start from the public OpenAPI names and keep your client aligned with the documented endpoint family. Use GET /links for interactive lists, pair it with GET /links/count when a total is needed, and choose single or bulk creation depending on whether the workflow creates one short link at a time or imports many links. Preserve the distinction between local UI state and API filters: the repository dashboard code keeps route parameters, query parameters, sorting, and pagination explicit, which makes browser navigation and data fetching predictable.

The supplied source also reinforces an important reliability rule: never assume that API responses are raw database records. Dub routes validate inputs with schemas, verify workspace ownership or parent-resource access, query only the required records, and parse response objects into explicit schemas before returning JSON. For Links integrations, this means client code should be generated from or checked against the OpenAPI contract where possible. If you are building a dashboard, CLI workflow, or automation script, read the broader Links API overview next, then move to update/upsert/delete and bulk/count operations for lifecycle coverage.