SEO Brief Export & Collaboration
[To Build] · API feature ·
provider-googleOAuth scope addition
Enables content briefs to be shared with external writers via a public link (no login required), exported to Google Docs with one click, and validated against the final blog draft via a brief compliance check.
Related: Content Brief Writer · Blog Writer · Content Toolkit Overview
Overview
| Function | Share, export, and validate SEO content briefs |
| Type | API feature — Collaboration |
| Status | To Build |
| Priority | P2 — Differentiating |
| Sub-features | Shareable link · Google Docs export · Brief compliance check |
| Credits | 0 cr (share/export) · 0.25 cr (compliance check) |
| Plan | Free+ (share link) · Pro+ (Google Docs export + compliance check) |
Why This Is Needed
Content briefs are generated by the content-brief-writer agent but live entirely inside Leadmetrics. When a DM agency works with freelance writers or client-side content teams, there is no way to hand off a brief without:
- Either giving the external writer a Leadmetrics login (a billing and security concern)
- Or manually copying the brief into a Google Doc or email
Additionally, once a blog post is written against a brief, there is no automated check to confirm the writer actually followed it — keyword coverage, target word count, and required sections may have been missed.
Sub-Feature 1: Shareable Brief Link
A tokenised public URL that renders the brief in read-only mode. No authentication required.
How It Works
DM clicks "Share" on a content brief
↓
API: POST /tenant/v1/content-briefs/:id/share
- Generates a signed token: HMAC-SHA256(briefId + tenantId + expiresAt, SECRET_KEY)
- Creates BriefShareToken record in DB: { briefId, token, expiresAt, createdBy }
- Returns: { shareUrl: "https://app.leadmetrics.io/brief/share/{token}" }
↓
External writer opens the URL
↓
GET /brief/share/:token (public route, no auth middleware)
- Validate token signature + expiry
- Render brief in a clean read-only view (no sidebar, no navigation, no editable fields)Token Rules
| Property | Value |
|---|---|
| Default expiry | 30 days |
| Max expiry | 90 days |
| Revocable | Yes — DM can revoke from brief settings; token is deleted from DB |
| Scope | Single brief; token cannot be used to access any other resource |
| Regenerable | Yes — “Reset link” creates a new token and invalidates the old one |
Brief Share DB Model
model BriefShareToken {
id String @id @default(cuid())
briefId String
tenantId String
token String @unique
expiresAt DateTime
createdBy String
createdAt DateTime @default(now())
brief ContentBrief @relation(fields: [briefId], references: [id], onDelete: Cascade)
tenant Tenant @relation(fields: [tenantId], references: [id])
}Public Brief View
The public brief view at /brief/share/:token renders:
- Working title + meta description
- Primary and secondary keywords
- Search intent
- Target word count + rationale
- Recommended outline (H2/H3 structure with notes per section)
- Internal link targets (as text, not hyperlinks)
- Tone and style notes
- Brand voice summary (if a brand voice doc is in the RAG dataset)
It does not render competitor URLs, internal pricing, or any tenant account data.
Sub-Feature 2: Google Docs Export
Pushes the brief content into a new Google Doc in the tenant’s connected Google Drive account. Returns a shareable Google Doc link.
How It Works
DM clicks "Export to Google Docs" on a content brief
↓
API: POST /tenant/v1/content-briefs/:id/export/google-docs
- Checks ConnectedChannel for Google account with Drive scope
- If not connected: returns 400 with message "Connect a Google account with Drive access in Settings → Channels"
↓
provider-google: Docs API → documents.create
- Creates a new Doc titled: "[Brief] {brief.title} — Leadmetrics"
- Populates with structured content (headings, keyword table, outline)
- Sets sharing: "anyone with link can comment"
- Returns: { googleDocUrl: string, googleDocId: string }
↓
Stores googleDocId on ContentBrief record
Returns shareUrl to dashboard; DM can copy and send to writerGoogle OAuth Scope Addition
Add https://www.googleapis.com/auth/drive.file to the Google OAuth scope in provider-google. This is a narrower scope that only allows access to files created by the app (not the user’s full Drive).
Doc Structure
The generated Google Doc follows this structure:
# [Brief] {title}
## Brief Details
| Property | Value |
| Primary Keyword | {keyword} |
| Secondary Keywords | {keywords} |
| Search Intent | {intent} |
| Target Word Count | {count} |
## Recommended Outline
### H2: {section title}
Notes: {notes}
Keywords to include: {keywords}
### H2: {section title}
...
## Tone & Style Notes
{notes}
## Internal Link Targets
- {page name}: {url}
---
Generated by Leadmetrics on {date}Sub-Feature 3: Brief Compliance Check
After a blog post draft exists, compares the draft against the original brief to produce a BriefComplianceResult.
What It Checks
| Check | How |
|---|---|
| Primary keyword present in title | Exact or fuzzy match |
| Primary keyword present in body | At least 3 occurrences |
| Secondary keyword coverage | % of secondary keywords that appear at least once in body |
| Word count compliance | Draft word count vs brief’s targetWordCount (pass if within ±15%) |
| Outline section coverage | % of brief H2 sections that have a corresponding H2 in the draft |
| Meta description present | Non-empty, 140–160 chars |
Output Contract
interface BriefComplianceResult {
blogPostId: string;
briefId: string;
checkedAt: string;
overallPass: boolean;
checks: {
primaryKeywordInTitle: { pass: boolean; detail: string };
primaryKeywordInBody: { pass: boolean; detail: string };
secondaryKeywordCoverage: { pass: boolean; coveragePercent: number; missing: string[] };
wordCountCompliance: { pass: boolean; draftWordCount: number; targetWordCount: number; delta: number };
outlineSectionCoverage: { pass: boolean; coveragePercent: number; missingSections: string[] };
metaDescriptionPresent: { pass: boolean; detail: string };
};
}How It Works
DM opens blog post that was generated from a brief
↓
API: POST /tenant/v1/content-briefs/:briefId/compliance-check?blogPostId={id}
- Heuristic comparison: no LLM call needed
- Returns BriefComplianceResult immediately
↓
Blog editor sidebar shows compliance panel:
- "Brief Compliance" section with pass/fail per check
- Overall ✅ / ⚠️ indicator
- Specific guidance for each failing checkThe compliance check is heuristic (string matching, word count), not LLM-based, so it costs 0.25 cr only for the outline section coverage check (which needs a light LLM pass to do fuzzy H2 matching). A free tier with exact matching only is 0 cr.
New API Routes
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /tenant/v1/content-briefs/:id/share | Tenant | Generate share token; return share URL |
DELETE | /tenant/v1/content-briefs/:id/share | Tenant | Revoke share token |
GET | /brief/share/:token | None (public) | Render brief in read-only view |
POST | /tenant/v1/content-briefs/:id/export/google-docs | Tenant | Push brief to Google Docs; return doc URL |
POST | /tenant/v1/content-briefs/:briefId/compliance-check | Tenant | Run compliance check against a blog post |
Key Design Decisions
| Decision | Choice | Rationale |
|---|---|---|
| HMAC token for share links | HMAC-SHA256(briefId + tenantId + expiresAt) | Cryptographically signed; unguessable; verifiable without a DB lookup on every request |
| Narrow Google Drive scope | drive.file not drive | Only access files created by Leadmetrics; does not request access to the tenant’s full Google Drive |
| Compliance check is heuristic | Rule-based string matching, not LLM | Fast (< 200ms), free, deterministic — covers 90% of usefulness; LLM not needed for counting occurrences |
| Compliance check placed in brief, not blog | POST /content-briefs/:briefId/compliance-check?blogPostId= | The brief is the authority document; the blog is checked against it, not the other way around |
Implementation Phases
Phase 1 — Share Link
- Add
BriefShareTokenmodel to Prisma schema (migration) - Implement
POST /tenant/v1/content-briefs/:id/shareroute — HMAC token generation - Implement
GET /brief/share/:tokenpublic route — token validation + brief render page - Dashboard brief detail page: “Share” button + copy-link panel + revoke button
Phase 2 — Google Docs Export
- Add
https://www.googleapis.com/auth/drive.fileto Google OAuth scopes inprovider-google - Implement
createBriefDoc()inprovider-googlepackage - Add
POST /tenant/v1/content-briefs/:id/export/google-docsroute - Store
googleDocIdonContentBriefmodel - Dashboard brief detail page: “Export to Google Docs” button + doc link display
Phase 3 — Brief Compliance Check
- Implement
BriefComplianceResulttype inpackages/common - Implement compliance check service in
apps/api/src/services/ - Add
POST /tenant/v1/content-briefs/:briefId/compliance-checkroute - Blog editor sidebar: “Brief Compliance” panel (shown when blog was generated from a brief)