Backlinks — API Routes
All backlink and campaign routes are under the DM portal API (/dm/v1). Directory management is under /admin/v1. Backlink data is always scoped to a tenantId passed as a query or body parameter.
Backlink Routes
Router file: apps/api/src/routers/dm/campaigns.ts
GET /dm/v1/backlinks
List backlinks for a tenant with optional filtering and cursor pagination.
Query params:
| Param | Required | Notes |
|---|---|---|
tenantId | Yes | |
outreachStatus | No | Filter by status |
campaignId | No | Scope to a single campaign |
cursor | No | Cursor-based pagination |
limit | No | Default: 50, max: 100 |
Response: { items: Backlink[], nextCursor: string | null }
GET /dm/v1/backlinks/:id
Get a single backlink with its related campaign and campaign email.
Query params: tenantId (required)
Response: Full Backlink object with campaign (id, name, status, totalEmails) and campaignEmail (id, subject, body, recipientEmail, recipientName, status, sentAt) includes.
PATCH /dm/v1/backlinks/:id
Update a backlink’s outreach status or notes.
Important: tenantId must be in the request body, not the querystring. The params schema only validates id.
Body:
| Field | Required | Notes |
|---|---|---|
tenantId | Yes | In body only |
outreachStatus | No | Any valid status string |
notes | No | DM internal notes |
Side effects:
- Sets
completedAt = now()whenoutreachStatus = "completed", otherwise sets it tonull. - Enqueues a
search__syncjob to keep Typesense in sync. - Logs an audit event.
Campaign Routes
GET /dm/v1/campaigns/:id
Get a single campaign with all emails included. Each email also includes the backlink’s sourceDomain, prospectType, relevanceScore, pitchRationale.
Query params: tenantId (required)
PATCH /dm/v1/campaigns/:campaignId/emails/:emailId
Edit a single campaign email.
Body: { tenantId, subject?, body?, recipientEmail?, recipientName?, status? }
status is restricted to draft, approved, skipped (not sent — sending is a separate action).
POST /dm/v1/campaigns/:campaignId/emails/:emailId/send
Send a single email. Uses the current DB state of the email (subject, body, recipient). Always PATCH the email content first if you want to save edits before sending.
Body: { tenantId }
Side effects:
- Sets
CampaignEmail.status = "sent"andsentAt = now(). - Sets
Backlink.outreachStatus = "outreach_sent"andoutreachSentAt = now(). - Enqueues via notification infrastructure (
enqueueNotification, type:backlink_outreach, template:raw-email).
POST /dm/v1/campaigns/:id/send-all
Send all CampaignEmail rows in approved status for a campaign.
Body: { tenantId }
Side effects: Same per-email side effects as above × N emails. Sets Campaign.status = "sent" when done.
DM Proxy Routes (Next.js)
The DM portal (apps/dm) exposes Next.js route handlers that forward to the Fastify API using the dm_access_token cookie.
| Proxy | Fastify target |
|---|---|
PATCH /api/dm/backlinks/[id] | PATCH /dm/v1/backlinks/:id |
PATCH /api/campaigns/[campaignId]/emails/[emailId] | PATCH /dm/v1/campaigns/:campaignId/emails/:emailId |
POST /api/campaigns/[campaignId]/emails/[emailId]/send | POST /dm/v1/campaigns/:campaignId/emails/:emailId/send |
POST /api/campaigns/[campaignId]/send-all | POST /dm/v1/campaigns/:id/send-all |
Directory Management Routes
Router file: apps/api/src/routers/admin/backlink-directories.ts
Status: [Live]
All routes require superadmin JWT.
GET /admin/v1/backlink-directories
List all BacklinkDirectory entries. Query: isActive, category, subcategory, region, cursor, limit.
GET /admin/v1/backlink-directories/:id
Single directory entry.
POST /admin/v1/backlink-directories
Create a new directory entry. Body: all BacklinkDirectory fields.
PATCH /admin/v1/backlink-directories/:id
Update a directory entry. Does not retroactively update Backlink.steps copies that were already created from this directory.
DELETE /admin/v1/backlink-directories/:id
Soft-delete by setting isActive = false. Does not delete existing Backlink rows that reference this directory.
Manage proxy routes: apps/manage/src/app/api/admin/directories/ — POST + GET/PATCH/DELETE [id]. Reads manage_access_token cookie.
Opportunity Routes (To Build)
PATCH /tenant/v1/backlinks/:id/opportunity-status
Client-facing route to update opportunity status (in_progress, completed, not_applicable). Only applies to sourceType: "directory_opportunity" or "competitor_gap" rows.
Body: { tenantId, status, notes? }
Side effects: Sets completedAt when status = completed. Enqueues search sync.
POST /tenant/v1/backlinks/run-opportunity-matcher
Trigger the opportunity-matcher agent for a tenant.
Body: { tenantId }
Planned Routes
| Route | Purpose |
|---|---|
GET /dm/v1/backlinks/unlinked-mentions | List web mentions without a link (feeds mention-monitor output) |
POST /tenant/v1/haro-queries/:id/respond | Submit a drafted HARO response |
GET /dm/v1/haro-queries | List HARO queries with draft responses pending DM review |
POST /admin/v1/backlink-directories/bulk-import | Bulk CSV import; multipart/form-data; returns { created, skipped, errors } |