Skip to Content
BacklinksBacklinks — API Routes

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.


Router file: apps/api/src/routers/dm/campaigns.ts

List backlinks for a tenant with optional filtering and cursor pagination.

Query params:

ParamRequiredNotes
tenantIdYes
outreachStatusNoFilter by status
campaignIdNoScope to a single campaign
cursorNoCursor-based pagination
limitNoDefault: 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:

FieldRequiredNotes
tenantIdYesIn body only
outreachStatusNoAny valid status string
notesNoDM internal notes

Side effects:

  • Sets completedAt = now() when outreachStatus = "completed", otherwise sets it to null.
  • Enqueues a search__sync job 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" and sentAt = now().
  • Sets Backlink.outreachStatus = "outreach_sent" and outreachSentAt = 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.

ProxyFastify 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]/sendPOST /dm/v1/campaigns/:campaignId/emails/:emailId/send
POST /api/campaigns/[campaignId]/send-allPOST /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.

List all BacklinkDirectory entries. Query: isActive, category, subcategory, region, cursor, limit.

Single directory entry.

POST /admin/v1/backlink-directories

Create a new directory entry. Body: all BacklinkDirectory fields.

Update a directory entry. Does not retroactively update Backlink.steps copies that were already created from this directory.

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

RoutePurpose
GET /dm/v1/backlinks/unlinked-mentionsList web mentions without a link (feeds mention-monitor output)
POST /tenant/v1/haro-queries/:id/respondSubmit a drafted HARO response
GET /dm/v1/haro-queriesList HARO queries with draft responses pending DM review
POST /admin/v1/backlink-directories/bulk-importBulk CSV import; multipart/form-data; returns { created, skipped, errors }

© 2026 Leadmetrics — Internal use only