LoudminkLoudmink
REST API

Plans and actionables

Read and control the content calendar, generate monthly actionables, and record completion.

These routes accept a Loudmink API key or signed-in session unless a specific website-change action is marked session-only. They use the product resolved by authentication. See Authentication for shared authentication and paid-access errors.

Endpoint inventory

Method and pathEffect
GET /api/content-planRead the calendar view model
PATCH /api/content-planChange mode, pause, or resume; website-approval revocation is session-only
POST /api/content-plan/generateCreate or rebuild scheduled work
POST /api/content-plan/approveConsent to autonomous execution; keys cannot approve website change groups
PATCH /api/content-plan/items/{itemId}Skip, restore, retry, reschedule, swap, or edit
GET /api/actionablesRead the monthly issue, done ledger, and receipts
POST /api/actionables/generateCreate or return this month's issue
POST /api/actionables/items/{itemId}/completeRecord eligible manual completion and outcome watch
POST /api/actionables/receipts/seenMark all unseen product receipts seen

All writes require active workspace access. Keys can perform the supported writes within their own workspace; there are no selectable read-only key scopes. Website-change approvals must be managed by an authorised user in Loudmink.

Every content-plan write requires JSON productId exactly equal to the authenticated product, including key-authenticated calls. Missing, non-string, and mismatched IDs return 409:

{
  "error": "Your active product changed in another tab. Reload this page before making changes.",
  "code": "product_context_changed"
}

Read productId from GET /api/content-plan and send it back unchanged. It confirms context; it does not select a different product. Actionables writes do not use this body-level guard.

Read the content plan

GET /api/content-plan?start=YYYY-MM-DD

The optional start is normalized to the Monday containing that date. Valid dates must round-trip as UTC dates in years 2000–2200. Invalid values fall back to the default window rather than returning 400. The default begins near one week before today and shifts forward if necessary to include the full 30-day planning horizon. The response always contains six seven-day weeks, not a paginated list, with Cache-Control: no-store.

curl 'https://loudmink.ai/api/content-plan' \
  -H "Authorization: Bearer $LOUDMINK_API_KEY"

The response is the view model itself, with no success or data wrapper:

FieldShape/meaning
productId, today, windowStart, monthLabelProduct ID, UTC dates, display label
plannull or { id, status, mode, approvedAt, approvedBy, generatedAt }
weeksSix arrays of seven day objects
rollingActive, rollingNoteMaintenance eligibility and nullable { text, ctaLabel, ctaHref }
counters{ done, review, scheduled }, across the plan, not only the visible window
quotaNoteNullable { text, ctaLabel, ctaHref }
workMix{ maintain, improve, create, measure }
executiveReceiptNullable review-cycle summary
websiteBaselineNullable website-review baseline/freshness
siteWorkWebsite groups and execution activity, or null when unavailable

Each day has date, dayOfMonth, isToday, isPast, nullable item, quietRows, and overflow. Quiet rows have { kind: "receipt" | "recheck", text }. At most two visible rows are budgeted per day, including a scheduled item; overflow counts the hidden quiet rows.

Each item has id, date, kind, format, title, why, status, pinned, outputUrl, outputSummary, droppedReason, contentIndexId, threadUrl, and alternates. An alternate has { queryId, queryText, title, format }. Nullable fields remain nullable; URLs may be application-relative links.

ValueCurrent variants
Plan statusproposed, active, paused
Plan modeautopilot, draft
Item kindwrite_article, optimize, reddit_reply, check_seo, check_facts, check_links
Item statusscheduled, running, done, ready_review, skipped, dropped, failed
Article formatguide, comparison, listicle, howto; non-article items can have null

Reviewed drafts can be displayed as done without changing their stored item status. outputSummary: "Approved." is not publication proof; "Published." requires a remotely published mapping. approvedBy is a display label, including "an organization API key", not necessarily a user ID. rollingActive indicates a non-paused plan with paid access, not necessarily approved execution.

executiveReceipt, when present, has cycleNumber, reviewedAt, newOrChanged, improved, verified, inconclusive, and mixReason. websiteBaseline has state (not_started, running, ready, failed), reviewState, unverifiedChecks, completedAt, freshness (current, aging, stale, unknown), and freshnessNote.

siteWork has readyToRun, needsApproval, waitingForAccess, recommendations, groups, and activity. A group contains id, findingIds, title, state (ready, approval, access, approved, recommendation), affectedPages, actionCount, scopeComplete, approvedAt, approvedBy, and actions. Each action has url, findingTitle, proposedAction, evidenceLevel, confidence, impact, risk, and rollbackAvailable.

An activity entry contains executionId, actionId, url, title, status, statusLabel, explanation, updatedAt, rollbackEligible, approvedAt, and approvedBy. Statuses are queued, applying, applied, verified, unconfirmed, reconciliation, failed, rollback_running, rolled_back, or rollback_failed. These fields are conditional evidence, not a promise that API keys can execute or approve website changes.

This handler and its calendar builder read existing data; they do not create a plan. Loading failure returns 500 { error: "Failed to load the content plan" }.

Generate or rebuild a plan

POST /api/content-plan/generate

JSON: required productId, optional force (only boolean true enables rebuild). Invalid or missing JSON is treated as no body and consequently fails the product-context check.

curl -X POST 'https://loudmink.ai/api/content-plan/generate' \
  -H "Authorization: Bearer $LOUDMINK_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"productId":"PRODUCT_ID","force":false}'

Without force, an existing plan returns { kind: "existing", planId, itemCount }. Otherwise success is { kind: "created", planId, itemCount }. A new plan begins proposed in autopilot mode and schedules from tomorrow across a 30-day horizon. Generation builds a schedule from evidence; it does not immediately write the scheduled articles. Planning is deterministic by default. Where AI-assisted title generation is enabled, planning can involve a model call. Creating the schedule is separate from generating the articles.

force: true replaces future scheduled and skipped rows, including pinned edits and restored rows. Today/past rows and other statuses survive. The existing plan row, mode, status, and approval survive, so an already approved autopilot plan continues executing after rebuild without another consent request. Treat force as a consequential replacement, not a harmless refresh. For a rebuild, itemCount reports the proposed replacement schedule length, not the total count including preserved history.

No usable initial evidence returns 400 { error, code: "empty_workspace" }. Shared permission/subscription/context errors also apply. Unexpected failure returns 500 { error: "Failed to generate the content plan" }.

Approve a plan

POST /api/content-plan/approve

JSON: required productId. For API keys, omit bundleIds or send an empty array. A nonempty filtered string-ID list requests website-change approval and returns 403 "API keys cannot approve website change groups.". The handler filters non-string/blank IDs and deduplicates strings; it does not trim retained IDs.

curl -X POST 'https://loudmink.ai/api/content-plan/approve' \
  -H "Authorization: Bearer $LOUDMINK_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"productId":"PRODUCT_ID"}'

Approval changes a proposed calendar to active, records its approval time and actor, and authorizes the daily execution workflow. In autopilot mode, scheduled article creation/refresh can consume AI credits and article quota and produces drafts awaiting review. Publication is a separate review/approval flow; approving this calendar does not bypass it. draft mode does not execute scheduled items. Do not use approval as a read/check operation. Use PATCH resume for a paused plan.

Success: { success: true, approvedAt, approvedChangeGroups, queuedSiteFixes }. For API-key calendar approval, the two website counts are zero. Missing plan is 404 "No content plan to approve yet."; a non-proposed plan without requested bundles is 409 "This plan is already approved.". Invalid/missing JSON becomes {} and fails product context. Unexpected errors are 500 "Failed to approve the plan".

Website-change approval is a separate action in Loudmink. API keys cannot approve website-change groups, either explicitly or as part of calendar approval.

Change plan mode or pause state

PATCH /api/content-plan

Send productId and one control:

Body controlBehavior
mode: "autopilot" or mode: "draft"Update execution mode; does not itself approve a proposed plan
action: "pause"Only active can become paused
action: "resume"Only paused can become active
curl -X PATCH 'https://loudmink.ai/api/content-plan' \
  -H "Authorization: Bearer $LOUDMINK_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"productId":"PRODUCT_ID","action":"pause"}'

Success is { success: true }. If mode is present, it takes precedence over pause/resume. Invalid mode/unknown action returns 400 ("Unknown mode" or "Nothing to change"); malformed JSON returns 400 "Invalid JSON body"; missing plan returns 404 "No content plan yet."; invalid state transition returns 409. Unexpected failure returns 500 "Failed to update the content plan".

action: "revoke_site_fix" is not supported with an API key and returns 403. Manage website-change approvals inside Loudmink; this endpoint is not a rollback mechanism.

Edit a plan item

PATCH /api/content-plan/items/{itemId}

Required JSON: productId, action. Items are product-scoped. Normal edits operate on today/future scheduled or skipped rows. Past or executed/resolved rows return 409, with narrow exceptions: a reddit_reply awaiting ready_review can be skipped even in the past; future dropped work can be restored; failed work can be retried even in the past. These exceptions do not enable arbitrary edits on those statuses.

ActionOther fieldsResult/constraints
skipNoneScheduled work becomes skipped and pinned; an already skipped eligible item succeeds
restoreNoneSkipped or future dropped work becomes scheduled and pinned; clears a drop reason and preserves an explicit duplicate override when relevant
retryNoneFailed work becomes scheduled on the first free date from today through today+30; clears output URL/summary/drop reason and pins it
rescheduledateYYYY-MM-DD, tomorrow through today+30; updates date, preserves status, pins item
swapqueryTextArticle days only; exact trimmed match to an available saved alternate, checked against the current content library
edittitle and/or whyString fields, at least one; trims both; title nonempty, changed title maximum 120 characters; why maximum 400 and may be cleared
curl -X PATCH 'https://loudmink.ai/api/content-plan/items/ITEM_ID' \
  -H "Authorization: Bearer $LOUDMINK_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"productId":"PRODUCT_ID","action":"edit","title":"A revised topic","why":"Address the audience question directly."}'

Success is { success: true }, except retry additionally returns date. Retry reschedules; it does not synchronously execute. Rescheduling is not restoring a skipped item. An unchanged legacy title longer than 120 characters is allowed. A material topic change clears the old query target; a wording tweak may retain it. A swap updates title/format/query/brief, makes the outgoing topic an alternate, and removes the selected topic from sibling article alternatives. Edits are pinned against ordinary replanning, but force rebuild still replaces eligible future rows.

Errors: malformed JSON/unknown action/invalid action fields are 400; unknown or out-of-product item is 404 "Plan item not found"; immutable state, conditional-update races, occupied dates, no free retry date, or topic already covered are 409. Two concurrent edits retaining the same status are last-write-wins, not version-checked. Unexpected failure returns 500 { error: "Failed to edit the plan item" }.

Read actionables

GET /api/actionables

No parameters or pagination. Returns the view model directly:

FieldShape
productName, headerSentenceDisplay strings
gate{ gated, daysCollected, daysNeeded }
belowFloor, setupSteps, trackedQueryCountEvidence-floor/setup state; setup steps are nullable
issuenull or { id, issueMonth, monthLabel, basedOnDays, earlyCaveat, items }
doneLedgerArray of { month, monthLabel, items }, newest month first
resultsStripReceipt entries, newest first, at most 50
doneCount30dCount of completed items in the last 30 days

Issue items have id, rank, playbook, tier, whoPhrase, title, whySentence, sourceUrl, status, droppedReason, priority, evidence, and expectationWindow. Playbooks are article, update, reddit_reply, social_post, outreach. Statuses are pending, started, done, dropped; priority is high, medium, or low. Evidence includes sourceUrl, domain, channel, citationCount, queryIds, and optional priority, priorityScore, competitorName, topQueryText, and totalQueries. Expectation windows are [minDays, maxDays], not a guarantee of an outcome. Setup steps, when present, contain label, why, and optional href.

Ledger items additionally contain completedAt, completionKind, artifactUrl, nullable receipts (count, citedCount, namedCount, firstObservedAt, lastObservedAt), and at most 20 newest observations (date, engine, queryText, sentence) per item. The ledger itself has no pagination or overall item limit. Results-strip entries include id, itemId, observedAt, winSentence, itemTitle, playbook, engine, queryId, seen, completedAt, and stillWatching; eligible receipts are unseen or observed in the last 30 days.

This builder only reads the current issue: GET does not create one or mark receipts seen. gate.gated only applies when no current issue exists and fewer than three citation-data days have been collected. The evidence floor uses the current 30-day window and plan-allowed engines. Errors return 500 { error: "Failed to load actionables" }.

Generate the monthly issue

POST /api/actionables/generate

Optional JSON { force: true } bypasses the three-day maturity wait only. Other values, missing JSON, and invalid JSON mean force: false. It does not rebuild an existing issue and does not bypass the evidence floor: at least 20 distinct source URLs and 15 queries with citations are required. Selection is deterministic; this endpoint does not invoke an AI model or debit credits, but it is still gated on writable paid access and can create rows.

curl -X POST 'https://loudmink.ai/api/actionables/generate' \
  -H "Authorization: Bearer $LOUDMINK_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"force":false}'

All three normal outcomes return HTTP 200:

OutcomeJSON envelope
Too early{ gated: true, daysCollected, daysNeeded }
Insufficient evidence{ belowFloor: true, distinctSources, queriesWithCitation }
Created or already exists{ issue, existing }

The issue is unique per product and UTC calendar month (YYYY-MM). An existing issue is returned before maturity/floor checks, including when force is true. Generation gathers at most 100,000 raw citation rows; new issues contain at most 18 selected items, with at most four per channel. Concurrent creation adopts the winner's issue.

Here issue is a stored issue object, not the GET view-model issue: it contains id, productId, organizationId, issueMonth, configVersion, basedOnDays, createdAt, and rank-ordered stored items. Stored items contain id, issueId, productId, rank, playbook, tier, whoPhrase, title, whySentence, evidence, sourceUrl, status, completedAt, completionKind, artifactUrl, droppedReason, createdAt, and updatedAt. Dates serialize to ISO strings. Unexpected failure returns 500 { error: "Failed to generate actionables" }.

Complete a manual actionable

POST /api/actionables/items/{itemId}/complete

Item tierRequired JSON
1Rejected: completion belongs to the agent workflow
2{ "kind": "link_added", "artifactUrl": "https://example.com/published-page" }
3{ "kind": "checked" }

The tier-2 URL must parse as HTTP or HTTPS and be no longer than 2,048 characters. Completion sets status: "done", completion time/kind, and artifact URL. A link completion creates a pasted_url watch. Checked outreach creates a query_mention watch only if evidence has query IDs; other checked completions do not create watches. A saved URL is not verified as a publication by this request.

curl -X POST 'https://loudmink.ai/api/actionables/items/ITEM_ID/complete' \
  -H "Authorization: Bearer $LOUDMINK_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"kind":"link_added","artifactUrl":"https://example.com/published-page"}'

Success: { item }, using the stored item fields listed under generation. This is not idempotent success: already done or dropped returns 409 "Item is already closed". An unknown/out-of-product item is 404; tier 1 is 403; invalid kind/link is 400; unexpected failure is 500 "Failed to complete item". Errors have { error } envelopes. There is no AI call or credit debit in this handler.

Mark receipts seen

POST /api/actionables/receipts/seen

No body or parameters are used. Marks all currently unseen receipts for the resolved product, not just the 50 entries visible in the results strip. Success is { marked: count }; repeat calls can return { marked: 0 }. Seen receipts observed in the last 30 days remain eligible for the strip. Unexpected failure returns 500 { error: "Failed to mark receipts seen" }.

Approval-aware workflow

Read the calendar and retain its productId. Generate only when a new schedule is intended, then inspect the returned calendar and make item edits. Approve only after obtaining consent for the autonomous workload. Pause/resume and force rebuild affect future execution; neither is a read-only refresh. For actionables, inspect the generate response before assuming an issue exists, complete only the appropriate manual tiers, and mark receipts seen only when they have actually been reviewed.

Free visibility report

Not sure if AI search engines recommend you?

Get a free report showing who they recommend instead of you, where they get their answers, and what you can fix.