API Reference
Ideas API

Ideas API

Create, list, and manage LinkedIn post ideas. All routes require a Cognito JWT and use snake_case request bodies (per the API-wide convention).

For the feature-level model (lifecycle, AI title generation, UI behavior), see Idea Capture & Management. This page is the endpoint-by-endpoint wire contract; the authoritative spec is specs/api/ideas.md in the repo.

Route summary

MethodPathDescription
POST/api/ideasCreate an idea
GET/api/ideas?workspaceId=List ideas
GET/api/ideas/{id}?workspaceId=Get an idea
PUT/api/ideas/{id}Update an idea
DELETE/api/ideas/{id}?workspaceId=Delete an idea
PUT/api/ideas/{id}/statusChange status

Authorization

Every route requires workspace membership: the caller must have a member record in the workspace_id (body field on POST/PUT, workspaceId query param on GET/DELETE), or the route returns 403 Not a workspace member. There is no additional per-role check — member, editor, and owner can all do everything below.

⚠️

Every idea object below also carries raw storage keys — pk, sk, gsi1pk, gsi1sk, entity_type — because the backend returns the persisted DynamoDB item as-is rather than a sanitized response DTO. They're included in the JSON responses shown here for accuracy, but treat them as internal, not part of the contract.

Create

POST /api/ideas
⚠️

The title field in the request body is not stored as the title. It's treated as free-form idea content: the backend calls Bedrock to generate a short title from it, and stores your original text as description. See AI title generation.

Body

{
  "workspace_id": "ws-1",
  "title": "Just realized our onboarding flow loses 40% of users at step 3...",
  "tags": ["growth", "onboarding"],
  "source_url": "https://example.com/analytics-dashboard"
}

tags and source_url are optional (default [] / "").

Response 201

{
  "idea_id": "3f9e...",
  "workspace_id": "ws-1",
  "title": "Fixing Onboarding Drop-off",
  "description": "Just realized our onboarding flow loses 40% of users at step 3...",
  "tags": ["growth", "onboarding"],
  "source_url": "https://example.com/analytics-dashboard",
  "status": "new",
  "created_by": "user-111",
  "linked_post_id": null,
  "created_at": "2026-07-17T12:00:00+00:00",
  "updated_at": "2026-07-17T12:00:00+00:00"
}
StatusMeaning
201Created
400workspace_id missing/blank
400title (content) missing/blank
403Not a workspace member

List

GET /api/ideas?workspaceId=ws-1&status=new&tags=growth,onboarding&authorId=user-111

workspaceId is required; status, tags, and authorId are all optional filters that combine with AND. Results are sorted newest-first by created_at.

Response 200

{ "items": [ /* idea objects, same shape as Create's response */ ], "count": 2 }
StatusMeaning
200OK — count: 0 / items: [] if none match
400workspaceId query param missing/blank
403Not a workspace member

Get

GET /api/ideas/{id}?workspaceId=ws-1

Response 200 — the idea object (same shape as Create's response).

StatusMeaning
200OK
400workspaceId query param missing/blank
403Not a workspace member
404Idea not found

Update

PUT /api/ideas/{id}

Partial update — only the fields you send are changed.

Body

{
  "workspace_id": "ws-1",
  "title": "Fixing Onboarding Drop-off (v2)",
  "description": "Updated context...",
  "source_url": "https://example.com/updated-link",
  "tags": ["growth"]
}
  • title, if present, is trimmed and must be 1–500 characters after trimming, or the request fails with 400.
  • description, source_url, and tags are stored as-is with no validation.
  • status is not accepted here — use Change status instead.

Response 200 — the updated idea object.

StatusMeaning
200OK
400workspace_id missing/blank
400title present but blank, or over 500 characters
403Not a workspace member
404Idea not found

Delete

DELETE /api/ideas/{id}?workspaceId=ws-1

Hard-deletes the idea. No status restriction — a converted idea can be deleted too.

Response 200

{ "deleted": true }
StatusMeaning
200Deleted
400workspaceId query param missing/blank
403Not a workspace member
404Idea not found

Update Status

PUT /api/ideas/{id}/status

Transitions an idea's status. The only allowed transition is new → converted. Everything else — including new → new — is rejected.

Body

{ "workspace_id": "ws-1", "status": "converted" }

Response 200 — the updated idea object, with status changed.

StatusMeaning
200Transitioned
400workspace_id missing/blank
400status missing/blank
400status is not new or converted
403Not a workspace member
404Idea not found
409Transition not allowed from the idea's current status (message: Transition from '{current}' to '{new}' is not allowed)

In practice, most ideas reach converted automatically — as a side effect of POST /api/posts when the new post is created with a source_idea_id pointing at this idea. This endpoint exists for direct/automation use; the web app's "Generate Post" flow never calls it directly. See Idea lifecycle.

See also

  • Idea Capture & Management — product behavior and lifecycle.
  • MCP → Ideas Tools — the AI-agent surface, which behaves differently from this REST API in several places (no title generation, no status-transition validation).
  • Posts API — creating a post from an idea.