Skip to content

docs(social): the scrape contract — per-platform posts, what one scrape stores, post engagement, follower history (app#2018) - #316

Merged
sweetmantech merged 3 commits into
mainfrom
feat/scrape-persistence-contract
Aug 27, 2026
Merged

docs(social): the scrape contract — per-platform posts, what one scrape stores, post engagement, follower history (app#2018)#316
sweetmantech merged 3 commits into
mainfrom
feat/scrape-persistence-contract

Conversation

@sweetmantech

@sweetmantechsweetmantech commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Contract PR for recoupable/app#2018 (scrape persistence keystone). Absorbs the docs rows of app#2017 (posts per platform) and app#2026 (history on /socials).

What changes

POST /api/socials/{id}/scrape (social.json)

  • Credits formula (5 + posts), a per-platform table for posts (honored / actor parameter / default when omitted), a "What gets stored" list (profile, daily follower snapshot, posts with engagement, Instagram comments + commenter enrichment) and the one-hop rule for fan discovery.
  • Corrects the old text: startInstagramProfileScraping never forwards posts, so Instagram ignores it and always returns the profile's latest posts (up to 12); TikTok honors it (resultsPerPage). The previous "other platforms ignore this parameter" was wrong for TikTok and silent about Instagram.
  • posts parameter description shortened to point at the table.

GET /api/artists/{id}/posts (social.json)

  • ArtistPost gains nullable views, likes, comments, reposts, each with the per-platform source field (e.g. TikTok playCount, X viewCount); description states they are the values at the last scrape and null where the platform does not report them.

GET /api/artists/{id}/socials (releases.json)

  • New history=<days> query (1–90) returning history: SocialSnapshot[] per profile, newest first, one point per social per day (latest scrape that day wins). New SocialSnapshot schema (captured_at, follower_count, following_count, post_count).
  • Documents page / limit (the validator already accepts them) and the real 401 / 403 / 404 the route returns; previously only 400 was listed.

POST /api/artist/socials/scrape (releases.json) — posts description aligned with the single-social page, credits per profile stated.

GET /api/artists/{id}/fans (releases.json) — states that fans are commenters on scraped posts (Instagram today) and that the commenter profile run fills avatar/bio/follower counts. (The per-platform coverage table stays with app#2024.)

Merge order

docs (this) → database (social_snapshots, posts engagement columns, apify_scraper_runs lineage) → api keystone → api budget. This PR is the contract the api PR is tested against.

Edits were applied as anchored text replacements (the files do not round-trip byte-identically through json.dumps); both files re-parse.

🤖 Generated with Claude Code

https://claude.ai/code/session_012PS8hmiwR1rGD6c41n6gD8


Summary by cubic

Defines the scrape persistence contract in the OpenAPI specs for recoupable/app#2018: per-platform post depth, what one scrape stores, post engagement fields, and follower history.

What changed

  • POST /api/socials/{id}/scrape now documents the credits formula (5 + posts) and a per-platform table; Instagram ignores posts and always returns the latest 12 posts, while TikTok honors it — correcting previously wrong text.
  • ArtistPost gains nullable views, likes, comments, and reposts, each with the per-platform source field.
  • GET /api/artists/{id}/socials adds history=<days> (1–90) returning SocialSnapshot[] per profile and documents the real 401/403/404 responses; following_count and post_count are null where the platform does not report them.
  • GET /api/artists/{id}/fans now states fans are commenters on scraped posts (Instagram today) and that the enrichment pass fills avatar, bio, and follower and following counts.
  • The batch scrape posts description is aligned with the single-social endpoint.

Written for commit b147e63. Summary will update on new commits.

Review in cubic

…e scrape stores, one-hop fan discovery, post engagement fields, history on GET /api/artists/{id}/socials (chat#2018)
- POST /api/socials/{id}/scrape: credits formula, per-platform posts table
(Instagram ignores posts — the start module never forwards it), what gets
stored (profile, daily snapshot, posts with engagement, Instagram comments +
commenter enrichment), one-hop terminal fan discovery.
- ArtistPost gains views/likes/comments/reposts (nullable) with the per-platform
source field for each.
- GET /api/artists/{id}/socials: page/limit (already accepted), history=<days>
returning SocialSnapshot[] per profile; documents the real 401/403/404.
- Batch scrape posts description aligned; /fans states Instagram-only commenter
coverage and the enrichment pass.
Contract for recoupable/app#2018 (absorbs the docs rows of app#2017 and app#2026).
@coderabbitai

coderabbitaiBot commented Aug 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 28 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6f410e62-2e66-4462-9034-63fbe63b458b

📥 Commits

Reviewing files that changed from the base of the PR and between 1bc3f0a and b147e63.

📒 Files selected for processing (2)
  • api-reference/openapi/releases.json
  • api-reference/openapi/social.json

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cubic-dev-aicubic-dev-aiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="api-reference/openapi/releases.json">
<violation number="1" location="api-reference/openapi/releases.json:1187">
P3: The new fans description claims the commenter profile run fills `following_count`, but the Scrape page's "What gets stored" bullet in the same PR says it fills only avatar, bio and follower count. These two statements conflict; a consumer reading either could be misled. Reconcile them so both docs state the same set of filled fan fields.</violation>
<violation number="2" location="api-reference/openapi/releases.json:4483">
P3: SocialSnapshot.following_count and post_count are nullable but not in `required`, so consumers can't rely on them being present, while the `history` parameter description promises every snapshot stores follower, following and post counts. Since the descriptions say these are null (not absent) where a platform doesn't report them, add both to `required` so the schema matches the contract text.</violation>
</file>
<file name="api-reference/openapi/social.json">
<violation number="1" location="api-reference/openapi/social.json:1467">
P3: The field is named `updated_at`, but this description now says the value is when the post was *published* on the platform. That clashes with the other `updated_at` fields in these specs (`SocialProfile.updated_at` = last updated, `SocialPost.updated_at` = last updated). Either keep the field semantic consistent with its name, or rename the property to reflect that it carries the publish timestamp, so consumers aren't misled about what refresh time it represents.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

"/api/artists/{id}/fans": {
"get": {
"description": "Retrieve all social profiles from fans of an artist across all platforms. This endpoint aggregates fan data from all connected social media platforms. Supports pagination for large fan lists.",
"description": "Retrieve the social profiles of an artist's fans: the accounts that commented on the artist's scraped posts (Instagram today). Every Instagram scrape is followed by one batched profile run over the new commenters, which fills `avatar`, `bio`, `follower_count` and `following_count` on each fan; `region` is null unless the platform reports one. Supports pagination for large fan lists.",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The new fans description claims the commenter profile run fills following_count, but the Scrape page's "What gets stored" bullet in the same PR says it fills only avatar, bio and follower count. These two statements conflict; a consumer reading either could be misled. Reconcile them so both docs state the same set of filled fan fields.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At api-reference/openapi/releases.json, line 1187:
<comment>The new fans description claims the commenter profile run fills `following_count`, but the Scrape page's "What gets stored" bullet in the same PR says it fills only avatar, bio and follower count. These two statements conflict; a consumer reading either could be misled. Reconcile them so both docs state the same set of filled fan fields.</comment>
<file context>
@@ -1121,7 +1184,7 @@
"/api/artists/{id}/fans": {
"get": {
- "description": "Retrieve all social profiles from fans of an artist across all platforms. This endpoint aggregates fan data from all connected social media platforms. Supports pagination for large fan lists.",
+ "description": "Retrieve the social profiles of an artist's fans: the accounts that commented on the artist's scraped posts (Instagram today). Every Instagram scrape is followed by one batched profile run over the new commenters, which fills `avatar`, `bio`, `follower_count` and `following_count` on each fan; `region` is null unless the platform reports one. Supports pagination for large fan lists.",
"parameters": [
{
</file context>

"type": "integer",
"description": "Followers (subscribers on YouTube) at capture time"
},
"following_count": {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: SocialSnapshot.following_count and post_count are nullable but not in required, so consumers can't rely on them being present, while the history parameter description promises every snapshot stores follower, following and post counts. Since the descriptions say these are null (not absent) where a platform doesn't report them, add both to required so the schema matches the contract text.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At api-reference/openapi/releases.json, line 4483:
<comment>SocialSnapshot.following_count and post_count are nullable but not in `required`, so consumers can't rely on them being present, while the `history` parameter description promises every snapshot stores follower, following and post counts. Since the descriptions say these are null (not absent) where a platform doesn't report them, add both to `required` so the schema matches the contract text.</comment>
<file context>
@@ -4391,6 +4454,41 @@
+ "type": "integer",
+ "description": "Followers (subscribers on YouTube) at capture time"
+ },
+ "following_count": {
+ "type": "integer",
+ "nullable": true,
</file context>

"type": "string",
"format": "date-time",
"description": "ISO timestamp of when the post was last updated"
"description": "ISO timestamp of when the post was published on the platform"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The field is named updated_at, but this description now says the value is when the post was published on the platform. That clashes with the other updated_at fields in these specs (SocialProfile.updated_at = last updated, SocialPost.updated_at = last updated). Either keep the field semantic consistent with its name, or rename the property to reflect that it carries the publish timestamp, so consumers aren't misled about what refresh time it represents.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At api-reference/openapi/social.json, line 1467:
<comment>The field is named `updated_at`, but this description now says the value is when the post was *published* on the platform. That clashes with the other `updated_at` fields in these specs (`SocialProfile.updated_at` = last updated, `SocialPost.updated_at` = last updated). Either keep the field semantic consistent with its name, or rename the property to reflect that it carries the publish timestamp, so consumers aren't misled about what refresh time it represents.</comment>
<file context>
@@ -1464,7 +1464,27 @@
"type": "string",
"format": "date-time",
- "description": "ISO timestamp of when the post was last updated"
+ "description": "ISO timestamp of when the post was published on the platform"
+ },
+ "views": {
</file context>

@sweetmantech

Copy link
Copy Markdown
CollaboratorAuthor

Local Mintlify verification (2026-08-27, npx mintlify@latest dev on this branch @ 248b0b9)

PageDocumentedRendered
POST /api/socials/{id}/scrapecredits line, per-platform posts table (Instagram ignored, TikTok honored), "What gets stored" list, one-hop rule✅ all render; the markdown table inside the OpenAPI description renders as a table. Mintlify wraps the narrow first-column header ("Platform"): column sizing, not markup
GET /api/artists/{id}/socialspage, limit, history (1–90) params; history[] of SocialSnapshot in the 200 example; 400/401/403/404 tabs✅ params + ranges render; response example shows history[{captured_at, follower_count, following_count, post_count}]; five status tabs
GET /api/artists/{id}/postsviews/likes/comments/reposts on ArtistPost; description states last-scrape semantics✅ example carries the four fields
GET /api/artists/{id}/fanscommenter-enrichment sentence, Instagram-only today

Both OpenAPI files re-parse (json.load); docs.json nav unchanged (no new pages, so llms.txt picks the text up from the existing entries).

Social Scrape

scrape

Get Artist Socials

socials

Get Artist Posts

posts

Get Artist Fans

fans

@sweetmantech

Copy link
Copy Markdown
CollaboratorAuthor

Review triage (b147e63): fixed the two wording conflicts — the scrape page's commenter bullet now says avatar, bio, follower and following counts (the profile scraper returns followsCount), and the snapshot bullet qualifies following/post counts as "where the platform reports them", matching SocialSnapshot's nullable fields. Declined the updated_at rename: the handlers have always written the platform publish timestamp into posts.updated_at and GET /posts orders by it; the description now says what the value is rather than hiding it behind the column name.

@sweetmantech

Copy link
Copy Markdown
CollaboratorAuthor

Preview testing, round 2 (2026-08-27, branch @ b147e63, npx mintlify@latest dev on :3333, driven with Chrome DevTools MCP)

Covers the review-fix commit (b147e63) and exercises the interactive pieces, not just the rendered text.

CheckExpectedObserved
Social Scrape descriptioncredits line, 3-column posts table, "What gets stored" list, one-hop rule✅ 8 table rows (header + 7 platforms), header Platform / posts / When posts is omitted; commenter bullet now says "avatar, bio, follower and following counts" (matches /fans)
Social Scrape cross-linksevery /api-reference/… link on the page resolvesfetch() from the page: artists/socials 200, apify/scraper 200, posts/get 200, fans/get 200, artist/socials-scrape 200
Get Artist Socials paramspage (default 1), limit (default 20, ≤100), history (1–90)✅ all three render with defaults/ranges
Get Artist Socials response tabs200 / 400 / 401 / 403 / 404✅ five tabs; clicking 404 shows { "status": "error", "message": "<string>" } (ArtistSocialsErrorResponse)
Get Artist Socials history schemaSocialSnapshot child attributescaptured_at (required), follower_count (required), following_count (nullable), post_count (nullable) with descriptions
Get Artist Socials playground"Try it" → set history=14 → cURL includes it✅ generated URL: …/api/artists/{id}/socials?page=1&limit=20&history=14
Get Artist Postsviews/likes/comments/reposts in the 200 example and as child attributes with per-platform source fields✅ example shows all four; expanded attributes carry the platform mapping (e.g. views: YouTube viewCount, TikTok playCount, X viewCount)
Get Artist FansInstagram-only commenter coverage + enrichment sentence

Not changed by this PR, confirmed unaffected: docs.json nav (no new pages); both OpenAPI files still parse.

Capture note: Mintlify's sticky navbar and side panel repeat on stitched full-page captures, so #navbar / #content-side-layout were set to position: relative in the page before each shot; content is otherwise untouched.

Social Scrape

scrape

Get Artist Socials (children expanded)

socials

Get Artist Socials: 404 tab, and the playground with history=14

socials 404
socials playground

Get Artist Posts (children expanded)

posts

Get Artist Fans

fans

"/api/artists/{id}/socials": {
"get": {
"description": "Retrieve all social media profiles associated with an artist. This endpoint should be called before using the Social Posts endpoint to obtain the necessary social IDs.",
"description": "Retrieve all social media profiles associated with an artist, with the latest follower counts from the most recent scrape. Call this before the Social Posts or Social Scrape endpoints to obtain the social IDs. Pass `history` to also get each profile's follower snapshots, so week-over-week movement is a read instead of something the caller has to remember.",

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hyperlink "Social Posts" and "Social Scrape" to the respective endpoints.

@sweetmantech
sweetmantech merged commit 7d8f111 into mainAug 27, 2026
3 checks passed
sweetmantech added a commit to recoupable/database that referenced this pull request Aug 27, 2026
…lineage (app#2018) (#65)
* feat: social_snapshots, posts engagement columns, apify_scraper_runs lineage (app#2018)
- social_snapshots: one follower point per social per UTC day, unique
(social_id, captured_on), RLS, backfill from socials.followerCount +
updated_at so history starts today.
- posts: views/likes/comments/reposts (nullable BIGINT); drop the
set_updated_at trigger so re-scrapes that refresh engagement keep the
publish timestamp the handlers write.
- apify_scraper_runs: origin ('artist'|'fan'), parent_run_id, account_id
nullable, indexes for the lineage walk and the per-account hourly count.
Contract: recoupable/docs#316. Tracker: recoupable/app#2018.
* feat: social_snapshots.captured_on derived from captured_at by trigger, never defaulted from now() (review)
sweetmantech added a commit to recoupable/api that referenced this pull request Aug 27, 2026
…m persisted, engagement + follower snapshots (app#2018) (#866)
* feat(apify): scrape persistence keystone — one hop, every dataset item persisted, engagement + follower snapshots (app#2018)
Guard: every run's webhook payload carries origin (artist|fan) + parentRunId
via payloadTemplate; the Instagram profile handler continues (Arweave avatar,
posts, social_posts, comments follow-up) only for origin=artist AND a profile
linked to an account. Fan batches and legacy payloads without origin are
terminal. The dataset.length === 1 heuristic is gone.
Enrichment: the Instagram profile handler upserts EVERY profile in the
dataset (avatar, bio, follower/following/post counts), so a 12-fan batch
enriches 12 fans, not one.
Posts: YouTube videos + Shorts persist via persistPostsForSocial; the LinkedIn
posts actor (A3cAPGpwBEG8RJwse, what ?posts=N runs, previously unregistered)
gets a handler; every post row carries views/likes/comments/reposts where the
platform reports them; upsertPosts merges on post_url so re-scrapes refresh
engagement; GET /api/artists/{id}/posts returns the four fields.
Snapshots: upsertSocialsWithSnapshot is the one socials write path all eight
handlers use; it appends a social_snapshots point per social per day whenever
a follower count is present. GET /api/artists/{id}/socials?history=<days>
returns them newest first.
Lineage: both scrape routes register the root run; every spawned comments /
commenter run is registered with parent_run_id, inheriting the root's account.
Contract: recoupable/docs#316. Schema: recoupable/database#65.
* fix(apify): review — posts merge never nulls a stored count, snapshot captured_at refreshed on same-day re-scrape, chunked history read, Instagram post dates via toIsoDate, root registration never throws, artist path reuses the upsert's rows
* fix(apify): YouTube handler skips the leading /about error item the actor emits with a posts depth (found on preview: run FyKpfOPuDsv4zSeRz persisted nothing)
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@sweetmantech