tag enum Pin tags[0] AND the vocabulary the title, excerpt and body are drawn from. Omit for a mix of all six topics.
Fake blog posts with markdown bodies plus a threaded comment tree whose every parentId resolves inside the page — with working tag, status and date filters.
Generated test data for fixtures and development
Each record is an independent post: its own topic vocabulary, an exact wordCount, an excerpt derived from the body, and a date/metrics mask that matches its status.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
tag | enum | allowed: engineering | design | product | security | data | career example: security | Pin tags[0] AND the vocabulary the title, excerpt and body are drawn from. Omit for a mix of all six topics. |
status | enum | allowed: draft | scheduled | published | archived example: published | Pin status on every record, and therefore its whole date and metrics mask. Omitted, you get a weighted mix: published 60%, archived 15%, draft 15%, scheduled 10%. |
tagCount | int | allowed: 1 – 3 example: 1 | Exact number of entries in tags. Omit for a per-post mix of 1–3 (not echoed in meta.params, because it varies per record). |
minWords | int | default: 60 allowed: 20 – 160 example: 120 | Inclusive lower bound on wordCount. |
maxWords | int | default: 140 allowed: 20 – 160 example: 160 | Inclusive upper bound on wordCount. Must be ≥ minWords. The 160 ceiling is what keeps count=100 inside the 256 KiB response budget. |
excerptChars | int | default: 160 allowed: 40 – 200 example: 200 | Maximum length of excerpt in UTF-16 code units, the trailing … included. The excerpt is always a word-boundary prefix of the body's plain text. |
from | date | example: 2026-01-01 | Earliest createdAt, inclusive (YYYY-MM-DD = midnight UTC, or a full ISO 8601 timestamp). Defaults to 180 days before the resolved 'to', clamped to 0001-01-01. Must be ≤ to. |
to | date | example: 2026-03-31 | Latest createdAt/updatedAt, inclusive, and the publish boundary: published/archived posts publish at or before it, scheduled posts after it. Defaults to the final millisecond of the current UTC day. Must be ≤ 9998-12-31. |
tag enum Pin tags[0] AND the vocabulary the title, excerpt and body are drawn from. Omit for a mix of all six topics.
status enum Pin status on every record, and therefore its whole date and metrics mask. Omitted, you get a weighted mix: published 60%, archived 15%, draft 15%, scheduled 10%.
tagCount int Exact number of entries in tags. Omit for a per-post mix of 1–3 (not echoed in meta.params, because it varies per record).
minWords int Inclusive lower bound on wordCount.
maxWords int Inclusive upper bound on wordCount. Must be ≥ minWords. The 160 ceiling is what keeps count=100 inside the 256 KiB response budget.
excerptChars int Maximum length of excerpt in UTF-16 code units, the trailing … included. The excerpt is always a word-boundary prefix of the body's plain text.
from date Earliest createdAt, inclusive (YYYY-MM-DD = midnight UTC, or a full ISO 8601 timestamp). Defaults to 180 days before the resolved 'to', clamped to 0001-01-01. Must be ≤ to.
to date Latest createdAt/updatedAt, inclusive, and the publish boundary: published/archived posts publish at or before it, scheduled posts after it. Defaults to the final millisecond of the current UTC day. Must be ≤ 9998-12-31.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
count | int | default: 10 allowed: 1 – 100 example: 3 | How many records to generate (1–100). |
seed | int | example: 42 | Deterministic output: the same seed always returns the same records. Omit for random (the used seed is echoed in meta.seed). |
fields | list | example: id,slug | Return only these fields (comma-separated). Mutually exclusive with 'exclude'. |
exclude | list | example: coverImageUrl | Return all fields except these (comma-separated). |
format | enum | default: json allowed: json | ndjson | csv example: csv | Response format: json envelope, ndjson (one record per line) or csv. |
pretty | boolean | default: false example: true | Pretty-print the JSON response. |
unwrap | boolean | default: false example: true | Drop the envelope: return the raw array/object without data/meta wrapper. |
count int How many records to generate (1–100).
seed int Deterministic output: the same seed always returns the same records. Omit for random (the used seed is echoed in meta.seed).
fields list Return only these fields (comma-separated). Mutually exclusive with 'exclude'.
exclude list Return all fields except these (comma-separated).
format enum Response format: json envelope, ndjson (one record per line) or csv.
pretty boolean Pretty-print the JSON response.
unwrap boolean Drop the envelope: return the raw array/object without data/meta wrapper.
| Field | Type | Description | Example |
|---|---|---|---|
id | string | 'post_' + 12 hex characters + 2 hex of the record index, so ids are unique inside a response. | post_9f3a2c018d3104 |
slug | string | ASCII kebab-case slug derived from the title, at most 60 characters, suffixed with the id's last 4 hex characters so it is unique inside a response. | notes-on-hardening-the-default-path-3104 |
title | string | At most 80 characters, assembled from the topic's own noun phrases. | Notes on hardening the default path |
excerpt | string | Word-boundary prefix of the body's PARAGRAPH text (paragraphs joined by a single space; '## ' heading blocks and '- ' bullet blocks are skipped, because neither carries sentence punctuation and splicing them in produced run-together teasers), at most excerptChars UTF-16 code units, with a trailing … only when truncated. Never generated separately from the body. | We drew the threat model on one page. The team agreed to keep it simple. Hardening the default saved every later argument. The exploit chain was three boring… |
body | string | CommonMark subset: blank-line-separated paragraphs, at most one '## ' subheading and at most one '- ' bullet list. A subheading needs a wordCount of at least 60 and a bullet list at least 85, so short bodies are plain paragraphs. No code fences, tables, links, images, emphasis or inline code — use /api/markdown for those. | We drew the threat model on one page. The team agreed to keep it simple. Hardening the default saved every later argument. The exploit chain was three boring steps. ## Hardening steps - rotate every long lived credential - fix the boring vulnerability first - log what the sandbox blocks The vulnerability had been open for months. Least privilege broke two internal tools first. We wrote it down so the next person could follow. It took longer than we expected. Rotation is easy once nothing depends on a secret. |
tags | string[] | 1–3 labels. tags[0] is the topic id and the only vocabulary the title and body draw from; the rest are that same topic's sub-labels. Posts are never cross-tagged. | ["security","appsec"] |
author | object | Nested object: name, username (the ASCII slug of the name as first.last) and avatarUrl (a /api/avatars/image URL on this platform). | {"name":"Maria Ortiz","username":"maria.ortiz","avatarUrl":"https://randomapi.dev/api/avatars/image?style=initials&seed=maria.ortiz&name=Maria%20Ortiz"} |
status | string | draft | scheduled | published | archived. | published |
createdAt | string (ISO 8601) | Sampled inside slot i of 100 equal slots across [from, to], so records come oldest-first and record i depends only on the seed, the resolved params and i — never on count. | 2026-02-11T08:14:02.311Z |
publishedAt nullable | string (ISO 8601) | null for draft. published/archived: inside [createdAt, to]. scheduled: 1–30 days after to (clamped to 9999-12-31T23:59:59.999Z). | 2026-02-12T09:00:00.000Z |
updatedAt | string (ISO 8601) | published/archived: inside [publishedAt, to]. draft/scheduled: inside [createdAt, to]. Never before createdAt. | 2026-03-02T16:45:10.004Z |
wordCount | integer | Exactly the number of whitespace-separated tokens in body containing at least one ASCII letter — inside [minWords, maxWords]. The '##' and '-' markers contain no letters, so they never count. | 85 |
metrics | object | Nested object: views ≥ likes ≥ commentCount ≥ 0, all exactly 0 for draft and scheduled. commentCount is the post's own display counter — NOT the number of rows /api/posts/comments returns for that id. | {"views":18420,"likes":640,"commentCount":71} |
coverImageUrl nullable | string | A /api/images/placeholder URL labelled with the topic; null for roughly 1 post in 5 so card layouts have to handle the fallback. | https://randomapi.dev/api/images/placeholder?width=1200&height=630&text=security |
id string 'post_' + 12 hex characters + 2 hex of the record index, so ids are unique inside a response.
example: post_9f3a2c018d3104
slug string ASCII kebab-case slug derived from the title, at most 60 characters, suffixed with the id's last 4 hex characters so it is unique inside a response.
example: notes-on-hardening-the-default-path-3104
title string At most 80 characters, assembled from the topic's own noun phrases.
example: Notes on hardening the default path
excerpt string Word-boundary prefix of the body's PARAGRAPH text (paragraphs joined by a single space; '## ' heading blocks and '- ' bullet blocks are skipped, because neither carries sentence punctuation and splicing them in produced run-together teasers), at most excerptChars UTF-16 code units, with a trailing … only when truncated. Never generated separately from the body.
example: We drew the threat model on one page. The team agreed to keep it simple. Hardening the default saved every later argument. The exploit chain was three boring…
body string CommonMark subset: blank-line-separated paragraphs, at most one '## ' subheading and at most one '- ' bullet list. A subheading needs a wordCount of at least 60 and a bullet list at least 85, so short bodies are plain paragraphs. No code fences, tables, links, images, emphasis or inline code — use /api/markdown for those.
example: We drew the threat model on one page. The team agreed to keep it simple. Hardening the default saved every later argument. The exploit chain was three boring steps. ## Hardening steps - rotate every long lived credential - fix the boring vulnerability first - log what the sandbox blocks The vulnerability had been open for months. Least privilege broke two internal tools first. We wrote it down so the next person could follow. It took longer than we expected. Rotation is easy once nothing depends on a secret.
tags string[] 1–3 labels. tags[0] is the topic id and the only vocabulary the title and body draw from; the rest are that same topic's sub-labels. Posts are never cross-tagged.
example: ["security","appsec"]
author object Nested object: name, username (the ASCII slug of the name as first.last) and avatarUrl (a /api/avatars/image URL on this platform).
example: {"name":"Maria Ortiz","username":"maria.ortiz","avatarUrl":"https://randomapi.dev/api/avatars/image?style=initials&seed=maria.ortiz&name=Maria%20Ortiz"}
status string draft | scheduled | published | archived.
example: published
createdAt string (ISO 8601) Sampled inside slot i of 100 equal slots across [from, to], so records come oldest-first and record i depends only on the seed, the resolved params and i — never on count.
example: 2026-02-11T08:14:02.311Z
publishedAt string (ISO 8601) nullable null for draft. published/archived: inside [createdAt, to]. scheduled: 1–30 days after to (clamped to 9999-12-31T23:59:59.999Z).
example: 2026-02-12T09:00:00.000Z
updatedAt string (ISO 8601) published/archived: inside [publishedAt, to]. draft/scheduled: inside [createdAt, to]. Never before createdAt.
example: 2026-03-02T16:45:10.004Z
wordCount integer Exactly the number of whitespace-separated tokens in body containing at least one ASCII letter — inside [minWords, maxWords]. The '##' and '-' markers contain no letters, so they never count.
example: 85
metrics object Nested object: views ≥ likes ≥ commentCount ≥ 0, all exactly 0 for draft and scheduled. commentCount is the post's own display counter — NOT the number of rows /api/posts/comments returns for that id.
example: {"views":18420,"likes":640,"commentCount":71}
coverImageUrl string nullable A /api/images/placeholder URL labelled with the topic; null for roughly 1 post in 5 so card layouts have to handle the fallback.
example: https://randomapi.dev/api/images/placeholder?width=1200&height=630&text=security
/api/posts?tag=engineering&status=published&count=10&seed=42 {
"data": [
{
"id": "post_7a958016cfb600",
"slug": "what-we-learned-about-tail-latency-b600",
"title": "What we learned about tail latency",
"excerpt": "Tail latency is the number that mattered here. The compiler flagged it long before we did. We deleted more code than we added. Concurrency made the simple path…",
"body": "Tail latency is the number that mattered here. The compiler flagged it long before we did. We deleted more code than we added. Concurrency made the simple path much harder. The benchmark only lied when we wanted it to. The second attempt went much better. The profiler pointed straight at the hot loop. We read the notes from last year first. Refactoring in small steps kept the suite green.\n\n## Numbers that mattered\n\n- keep the benchmark in the suite\n- write the invariant down\n\nNobody wanted to own this at first. We kept the old path around for a month. The numbers agreed with the hallway conversation. One invariant explained three separate bugs. Concurrency was fine until the retry loop arrived. One allocation per request adds up quickly. We shipped the refactor already.",
"tags": [
"engineering",
"refactoring"
],
"author": {
"name": "Kaylie Gorczany-Considine",
"username": "kaylie.gorczanyconsidine",
"avatarUrl": "https://randomapi.dev/api/avatars/image?style=initials&seed=kaylie.gorczanyconsidine&name=Kaylie%20Gorczany-Considine"
},
"status": "published",
"createdAt": "2026-02-01T12:37:23.646Z",
"publishedAt": "2026-05-06T10:14:33.048Z",
"updatedAt": "2026-06-30T03:12:43.221Z",
"wordCount": 131,
"metrics": {
"views": 90,
"likes": 5,
"commentCount": 0
},
"coverImageUrl": "https://randomapi.dev/api/images/placeholder?width=1200&height=630&text=engineering"
},
{
"id": "post_0078f248abec01",
"slug": "a-benchmark-that-lies-without-the-guesswork-ec01",
"title": "A benchmark that lies without the guesswork",
"excerpt": "The compiler caught what review missed. Concurrency made the simple path much harder. Our benchmark numbers moved by a third. Refactoring in small steps kept…",
"body": "The compiler caught what review missed. Concurrency made the simple path much harder. Our benchmark numbers moved by a third. Refactoring in small steps kept the suite green. The benchmark only lied when we wanted it to. We read the notes from last year first.\n\n## The regression story\n\nThe team agreed to keep it simple. The numbers agreed with the hallway conversation.\n\nThe second attempt went much better. Two people carried most of the work. The fix was smaller than the argument about it. One invariant explained three separate bugs. We shipped the refactor. Nobody wanted to own this at first. Half the work was deciding what not to do. The compiler flagged it long before we did. Concurrency was fine until the retry loop arrived.",
"tags": [
"engineering",
"refactoring",
"code-review"
],
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/posts?status=draft&count=5&seed=7 {
"data": [
{
"id": "post_ad92c7e2436f00",
"slug": "positioning-in-a-crowded-market-without-the-guesswork-6f00",
"title": "Positioning in a crowded market without the guesswork",
"excerpt": "Churn dropped once we fixed the boring parts. Discovery saved us a quarter of wasted effort. One afternoon of reading saved a week of typing. The second…",
"body": "Churn dropped once we fixed the boring parts. Discovery saved us a quarter of wasted effort. One afternoon of reading saved a week of typing. The second attempt went much better.\n\n## What we cut\n\nThe team agreed to keep it simple. Onboarding lost people at the second screen. The roadmap held. We wrote the positioning down before anything else. We kept a short list of open questions.",
"tags": [
"product"
],
"author": {
"name": "Bell Treutel",
"username": "bell.treutel",
"avatarUrl": "https://randomapi.dev/api/avatars/image?style=initials&seed=bell.treutel&name=Bell%20Treutel"
},
"status": "draft",
"createdAt": "2026-02-01T01:28:50.785Z",
"publishedAt": null,
"updatedAt": "2026-02-08T09:06:49.920Z",
"wordCount": 67,
"metrics": {
"views": 0,
"likes": 0,
"commentCount": 0
},
"coverImageUrl": "https://randomapi.dev/api/images/placeholder?width=1200&height=630&text=product"
},
{
"id": "post_995c0ed5cab801",
"slug": "a-closer-look-at-one-quiet-vulnerability-b801",
"title": "A closer look at one quiet vulnerability",
"excerpt": "The threat model helped. We wrote it down so the next person could follow. The result was boring in the best way. Hardening the defaults removed most of the…",
"body": "The threat model helped. We wrote it down so the next person could follow. The result was boring in the best way. Hardening the defaults removed most of the guesswork. It took longer than we expected.\n\n## What we found\n\nThe threat model told us what to ignore. One privilege boundary was doing all the work. Rotation is easy once nothing depends on a secret. We kept a short list of open questions.\n\nThe rollout was quiet and that was the goal. We kept the old path around for a month. The sandbox held already.",
"tags": [
"security",
"appsec"
],
"author": {
"name": "Jermaine Schumm",
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/posts?status=scheduled&from=2026-06-01&to=2026-06-30&count=5&seed=11 {
"data": [
{
"id": "post_15f67bcd2c7600",
"slug": "the-promotion-packet-in-practice-7600",
"title": "The promotion packet in practice",
"excerpt": "Half the work was deciding what not to do. Burnout looked like boredom for a while. One afternoon of reading saved a week of typing. The promotion came a…",
"body": "Half the work was deciding what not to do. Burnout looked like boredom for a while. One afternoon of reading saved a week of typing.\n\n## What I would skip\n\nThe promotion came a quarter later than it should have. Burnout arrived slowly and then all at once. The apprenticeship needed a real project not a toy one. The plan changed twice before it stuck. Mentoring taught me more than it taught them. Feedback nobody can act on is just noise. The feedback landed in practice.",
"tags": [
"career"
],
"author": {
"name": "Gregg Haag",
"username": "gregg.haag",
"avatarUrl": "https://randomapi.dev/api/avatars/image?style=initials&seed=gregg.haag&name=Gregg%20Haag"
},
"status": "scheduled",
"createdAt": "2026-06-01T05:08:10.674Z",
"publishedAt": "2026-07-29T00:00:00.000Z",
"updatedAt": "2026-06-04T08:37:08.833Z",
"wordCount": 85,
"metrics": {
"views": 0,
"likes": 0,
"commentCount": 0
},
"coverImageUrl": "https://randomapi.dev/api/images/placeholder?width=1200&height=630&text=career"
},
{
"id": "post_8f5fd3bb40c101",
"slug": "a-closer-look-at-typography-that-survives-translation-c101",
"title": "A closer look at typography that survives translation",
"excerpt": "The plan changed twice before it stuck. The affordance was there but nobody saw it. The second attempt went much better. The palette survived a second review.…",
"body": "The plan changed twice before it stuck. The affordance was there but nobody saw it.\n\n## What the prototype showed\n\nThe second attempt went much better. The palette survived a second review. Iconography needs a second look at the smallest size. We cut the palette in half.\n\nWe wrote it down so the next person could follow. The palette shrank everywhere.",
"tags": [
"design",
"accessibility",
"typography"
],
"author": {
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/posts?tag=security&minWords=120&maxWords=160&excerptChars=200&count=20&format=csv id,slug,title,excerpt,body,tags,author.name,author.username,author.avatarUrl,status,createdAt,publishedAt,updatedAt,wordCount,metrics.views,metrics.likes,metrics.commentCount,coverImageUrl
post_7a958016cfb600,what-we-learned-about-hardening-the-default-path-b600,What we learned about hardening the default path,Credential sprawl was the real problem. The exploit chain was three boring steps. Hardening the defaults removed most of the guesswork. Hardening the default saved every later argument. We rotated…,"Credential sprawl was the real problem. The exploit chain was three boring steps. Hardening the defaults removed most of the guesswork. Hardening the default saved every later argument. We rotated every credential in one afternoon. The second attempt went much better. The sandbox held but the logging did not. We read the notes from last year first. We drew the threat model on one page. The sandbox held since then.
## What we would do sooner
- draw the threat model on one page
- rotate every long lived credential
The numbers agreed with the hallway conversation. We shipped it on a quiet friday. The team agreed to keep it simple. The threat model told us what to ignore. Two people carried most of the work. The vulnerability had been open for months. Rotation is easy once nothing depends on a secret. Small steps beat one large leap here. We wrote it down so the next person could follow.","[""security"",""appsec""]",Kaylie Gorczany-Considine,kaylie.gorczanyconsidine,https://randomapi.dev/api/avatars/image?style=initials&seed=kaylie.gorczanyconsidine&name=Kaylie%20Gorczany-Considine,draft,2026-02-02T06:48:02.245Z,,2026-04-08T06:00:09.104Z,156,0,0,0,https://randomapi.dev/api/images/placeholder?width=1200&height=630&text=security
post_0078f248abec01,credential-sprawl-without-the-guesswork-ec01,Credential sprawl without the guesswork,We rotated every credential in one afternoon. Hardening the default saved every later argument. Credential sprawl was the real problem. The exploit needed no clever trick at all. Hardening the…,"We rotated every credential in one afternoon. Hardening the default saved every later argument. Credential sprawl was the real problem. The exploit needed no clever trick at all. Hardening the defaults removed most of the guesswork. We kept the old path around for a month.
## Hardening steps
We shipped it on a quiet friday. The numbers agreed with the hallway conversation. The second attempt went much better. The plan changed twice before it stuck. We asked for help earlier than usual. The threat model helped.
The sandbox held. The fix was smaller than the argument about it. We read the notes from last year first. The sandbox held but the logging did not. The exploit chain was three boring steps. Least privilege broke two internal tools first. Rotation is easy once nothing depends on a secret. Two people carried most of the work. We wrote it down so the next person could follow.","[""security"",""appsec"",""threat-modeling""]",Waino Marvin,waino.marvin,https://randomapi.dev/api/avatars/image?style=initials&seed=waino.marvin&name=Waino%20Marvin,published,2026-02-04T06:48:27.010Z,2026-05-28T09:30:42.731Z,2026-06-06T22:19:11.100Z,153,33238,2573,7,https://randomapi.dev/api/images/placeholder?width=1200&height=630&text=security
post_d15790902c0502,notes-on-an-exploit-chain-we-missed-0502,Notes on an exploit chain we missed,We kept a short list of open questions. Least privilege broke two internal tools first. The threat model helped. Hardening the default saved every later argument. The sandbox held but the logging did…,"We kept a short list of open questions. Least privilege broke two internal tools first. The threat model helped.
Hardening the default saved every later argument. The sandbox held but the logging did not. We kept the old path around for a month. We shipped it on a quiet friday. The vulnerability had been open for months. The threat model told us what to ignore. The sandbox held. Rotation is easy once nothing depends on a secret. Credential sprawl was the real problem. It took longer than we expected.
The result was boring in the best way. The exploit chain was three boring steps. The rollout was quiet and that was the goal. Half the work was deciding what not to do. One privilege boundary was doing all the work. The fix was smaller than the argument about it. The numbers agreed with the hallway conversation. We rotated every credential in one afternoon. The threat model helped twice.","[""security"",""appsec""]",Hailee Streich,hailee.streich,https://randomapi.dev/api/avatars/image?style=initials&seed=hailee.streich&name=Hailee%20Streich,published,2026-02-05T15:33:44.197Z,2026-03-02T20:12:43.480Z,2026-04-08T03:50:55.057Z,158,177172,1158,53,https://randomapi.dev/api/images/placeholder?width=1200&height=630&text=security
post_2902fda52e1e03,rethinking-key-rotation-without-downtime-1e03,Rethinking key rotation without downtime,Least privilege broke two internal tools first. The threat model helped. The exploit chain was three boring steps. One afternoon of reading saved a week of typing. Hardening the default saved every…,"Least privilege broke two internal tools first. The threat model helped.
## Hardening steps
- give each job the least privilege it needs
- draw the threat model on one page
- log what the sandbox blocks
The exploit chain was three boring steps. One afternoon of reading saved a week of typing. Hardening the default saved every later argument. The second attempt went much better. The plan changed twice before it stuck.
We shipped it on a quiet friday. It took longer than we expected. Small steps beat one large leap here. The vulnerability had been open for months. Rotation is easy once nothing depends on a secret. The sandbox held but the logging did not. We rotated every credential in one afternoon. Nobody wanted to own this at first. Credential sprawl was the real problem. The sandbox held for now.","[""security"",""secrets"",""threat-modeling""]",Otis Hodkiewicz,otis.hodkiewicz,https://randomapi.dev/api/avatars/image?style=initials&seed=otis.hodkiewicz&name=Otis%20Hodkiewicz,published,2026-02-07T19:31:29.204Z,2026-06-23T20:36:51.146Z,2026-07-22T01:46:11.425Z,138,26791,1320,0,https://randomapi.dev/api/images/placeholder?width=1200&height=630&text=security
post_db640beed4d804,a-closer-look-at-sandbox-escapes-d804,A closer look at sandbox escapes,The exploit chain was three boring steps. Hardening the defaults removed most of the guesswork. Rotation is easy once nothing depends on a secret. Hardening the default saved every later argument.…,"The exploit chain was three boring steps. Hardening the defaults removed most of the guesswork. Rotation is easy once nothing depends on a secret. Hardening the default saved every later argument. Credential sprawl was the real problem. One privilege boundary was doing all the work. It took longer than we expected. Small steps beat one large leap here. The threat model helped.
The rollout was quiet and that was the goal. The sandbox held for now.
Two people carried most of the work. We wrote it down so the next person could follow. The fix was smaller than the argument about it. We read the notes from last year first. The sandbox held but the logging did not. The vulnerability had been open for months. The threat model told us what to ignore. Half the work was deciding what not to do.","[""security"",""secrets"",""threat-modeling""]",Melisa Funk,melisa.funk,https://randomapi.dev/api/avatars/image?style=initials&seed=melisa.funk&name=Melisa%20Funk,draft,2026-02-09T05:44:46.707Z,,2026-04-03T02:16:50.760Z,142,0,0,0,https://randomapi.dev/api/images/placeholder?width=1200&height=630&text=security
post_066407ec74cc05,key-rotation-without-downtime-in-practice-cc05,Key rotation without downtime in practice,Hardening the default saved every later argument. The threat model helped. Rotation is easy once nothing depends on a secret. The rollout was quiet and that was the goal. Two people carried most of…,"Hardening the default saved every later argument. The threat model helped. Rotation is easy once nothing depends on a secret. The rollout was quiet and that was the goal. Two people carried most of the work. We rotated every credential in one afternoon. The vulnerability had been open for months. The sandbox held but the logging did not. It took longer than we expected.
## Hardening steps
…
The threat model told us what to ignore. One privilege boundary was doing all the work. Credential sprawl was the real problem. The sandbox held in practice.","[""security""]",Susan Doyle,susan.doyle,https://randomapi.dev/api/avatars/image?style=initials&seed=susan.doyle&name=Susan%20Doyle,published,2026-03-08T18:27:21.752Z,2026-04-16T18:04:25.579Z,2026-05-12T21:08:10.317Z,123,157871,8299,2879,
/api/posts?tag=career&minWords=20&maxWords=40&tagCount=1&count=8&seed=3 {
"data": [
{
"id": "post_5300fbb5997d00",
"slug": "notes-on-interviewing-from-the-other-side-7d00",
"title": "Notes on interviewing from the other side",
"excerpt": "The promotion came a quarter later than it should have. We wrote it down so the next person could follow. The apprenticeship needed a real project not a toy…",
"body": "The promotion came a quarter later than it should have.\n\nWe wrote it down so the next person could follow.\n\nThe apprenticeship needed a real project not a toy one.",
"tags": [
"career"
],
"author": {
"name": "Joana Marvin",
"username": "joana.marvin",
"avatarUrl": "https://randomapi.dev/api/avatars/image?style=initials&seed=joana.marvin&name=Joana%20Marvin"
},
"status": "published",
"createdAt": "2026-02-02T12:42:37.628Z",
"publishedAt": "2026-03-26T06:19:36.512Z",
"updatedAt": "2026-07-01T23:44:11.525Z",
"wordCount": 30,
"metrics": {
"views": 34751,
"likes": 2835,
"commentCount": 281
},
"coverImageUrl": "https://randomapi.dev/api/images/placeholder?width=1200&height=630&text=career"
},
{
"id": "post_ff829ddf9c8601",
"slug": "mentoring-without-a-template-in-practice-8601",
"title": "Mentoring without a template in practice",
"excerpt": "Feedback nobody can act on is just noise. Mentoring worked best with a standing half hour. The feedback landed twice. Burnout looked like boredom for a while.…",
"body": "Feedback nobody can act on is just noise. Mentoring worked best with a standing half hour. The feedback landed twice.\n\nBurnout looked like boredom for a while. The mentoring helped already.",
"tags": [
"career"
],
"author": {
"name": "Molly Jacobi",
"username": "molly.jacobi",
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} One pool of 100 rows built parent-first; count returns a prefix of it. Row 0 is always a root, and every non-null parentId names a row printed earlier in the same response.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
postId | string | allowed: 1 – 64 example: post_9f3a2c018d3104 | Pin postId on every row so you can attach a comment page to a post id you already hold. Letters, digits, '.', '_', ':' and '-' only. Supplying it resolves postCount to 1. |
postCount | int | allowed: 1 – 10 example: 6 | Number of distinct synthetic post ids the pool is spread across. Resolves to 3, or to 1 when postId is supplied (in which case an explicit value is ignored with a warning). The resolved value is echoed in meta.params. |
maxDepth | int | default: 2 allowed: 0 – 4 example: 4 | Maximum depth of the reply tree. 0 gives a flat list: every parentId null, every depth 0. |
status | enum | allowed: published | pending | spam example: pending | Pin the moderation status on every row. Omitted, you get published 75%, pending 15%, spam 10%. Status never affects tree structure. Note this enum differs from the post route's status. |
minWords | int | default: 8 allowed: 3 – 60 example: 20 | Inclusive lower bound on wordCount. |
maxWords | int | default: 30 allowed: 3 – 60 example: 60 | Inclusive upper bound on wordCount. Must be ≥ minWords. |
from | date | example: 2026-01-01 | Earliest createdAt, inclusive (YYYY-MM-DD = midnight UTC, or a full ISO 8601 timestamp). Defaults to 90 days before the resolved 'to', clamped to 0001-01-01. Must be ≤ to. |
to | date | example: 2026-01-31 | Latest createdAt/updatedAt, inclusive. Defaults to the final millisecond of the current UTC day. Must be ≤ 9998-12-31. |
postId string Pin postId on every row so you can attach a comment page to a post id you already hold. Letters, digits, '.', '_', ':' and '-' only. Supplying it resolves postCount to 1.
postCount int Number of distinct synthetic post ids the pool is spread across. Resolves to 3, or to 1 when postId is supplied (in which case an explicit value is ignored with a warning). The resolved value is echoed in meta.params.
maxDepth int Maximum depth of the reply tree. 0 gives a flat list: every parentId null, every depth 0.
status enum Pin the moderation status on every row. Omitted, you get published 75%, pending 15%, spam 10%. Status never affects tree structure. Note this enum differs from the post route's status.
minWords int Inclusive lower bound on wordCount.
maxWords int Inclusive upper bound on wordCount. Must be ≥ minWords.
from date Earliest createdAt, inclusive (YYYY-MM-DD = midnight UTC, or a full ISO 8601 timestamp). Defaults to 90 days before the resolved 'to', clamped to 0001-01-01. Must be ≤ to.
to date Latest createdAt/updatedAt, inclusive. Defaults to the final millisecond of the current UTC day. Must be ≤ 9998-12-31.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
count | int | default: 20 allowed: 1 – 100 example: 3 | Limit the number of returned records (1–100). Defaults to 20. |
seed | int | example: 42 | Deterministic output: the same seed always returns the same records. Omit for random (the used seed is echoed in meta.seed). |
fields | list | example: id,postId | Return only these fields (comma-separated). Mutually exclusive with 'exclude'. |
exclude | list | example: status | Return all fields except these (comma-separated). |
format | enum | default: json allowed: json | ndjson | csv example: csv | Response format: json envelope, ndjson (one record per line) or csv. |
pretty | boolean | default: false example: true | Pretty-print the JSON response. |
unwrap | boolean | default: false example: true | Drop the envelope: return the raw array/object without data/meta wrapper. |
count int Limit the number of returned records (1–100). Defaults to 20.
seed int Deterministic output: the same seed always returns the same records. Omit for random (the used seed is echoed in meta.seed).
fields list Return only these fields (comma-separated). Mutually exclusive with 'exclude'.
exclude list Return all fields except these (comma-separated).
format enum Response format: json envelope, ndjson (one record per line) or csv.
pretty boolean Pretty-print the JSON response.
unwrap boolean Drop the envelope: return the raw array/object without data/meta wrapper.
| Field | Type | Description | Example |
|---|---|---|---|
id | string | 'comment_' + 12 hex characters + 2 hex of the row index, so ids are unique inside a response. | comment_4c81be02a71d05 |
postId | string | Your postId, or one of postCount synthetic 'post_…' ids. A reply always shares its parent's postId. | post_9f3a2c018d3104 |
parentId nullable | string | null for a root comment; otherwise the id of a row printed EARLIER in the same response. | comment_4c81be02a71d02 |
depth | integer | 0 for a root, otherwise parent.depth + 1; never above maxDepth. | 1 |
author | object | Nested object: name, email (<first.last>@example.com/.net/.org — RFC 2606 §3 reserved, so it can never deliver mail) and avatarUrl (a /api/avatars/image URL on this platform). | {"name":"Jonas Weber","email":"jonas.weber@example.net","avatarUrl":"https://randomapi.dev/api/avatars/image?style=initials&seed=jonas.weber&name=Jonas%20Weber"} |
body | string | Plain text — no markdown at all, because a comment box is not a CMS editor. | This matches what we saw in staging. We hit the same thing again. |
wordCount | integer | Whitespace-separated tokens in body containing at least one ASCII letter; inside [minWords, maxWords]. | 13 |
createdAt | string (ISO 8601) | Sampled inside slot i of 100 equal slots across [from, to], so rows are chronological AND parent-first at once — a reply is never dated before the comment it answers. | 2026-01-18T11:02:44.918Z |
updatedAt | string (ISO 8601) | Exactly createdAt when edited is false; strictly later (and ≤ to) when it is true. | 2026-01-18T11:02:44.918Z |
edited | boolean | True for roughly 1 row in 6 — true exactly when updatedAt > createdAt. | false |
likes | integer | 0–999, skewed low the way real comment sections are. | 4 |
status | string | published | pending | spam. Note this enum differs from the post route's status. | published |
id string 'comment_' + 12 hex characters + 2 hex of the row index, so ids are unique inside a response.
example: comment_4c81be02a71d05
postId string Your postId, or one of postCount synthetic 'post_…' ids. A reply always shares its parent's postId.
example: post_9f3a2c018d3104
parentId string nullable null for a root comment; otherwise the id of a row printed EARLIER in the same response.
example: comment_4c81be02a71d02
depth integer 0 for a root, otherwise parent.depth + 1; never above maxDepth.
example: 1
author object Nested object: name, email (<first.last>@example.com/.net/.org — RFC 2606 §3 reserved, so it can never deliver mail) and avatarUrl (a /api/avatars/image URL on this platform).
example: {"name":"Jonas Weber","email":"jonas.weber@example.net","avatarUrl":"https://randomapi.dev/api/avatars/image?style=initials&seed=jonas.weber&name=Jonas%20Weber"}
body string Plain text — no markdown at all, because a comment box is not a CMS editor.
example: This matches what we saw in staging. We hit the same thing again.
wordCount integer Whitespace-separated tokens in body containing at least one ASCII letter; inside [minWords, maxWords].
example: 13
createdAt string (ISO 8601) Sampled inside slot i of 100 equal slots across [from, to], so rows are chronological AND parent-first at once — a reply is never dated before the comment it answers.
example: 2026-01-18T11:02:44.918Z
updatedAt string (ISO 8601) Exactly createdAt when edited is false; strictly later (and ≤ to) when it is true.
example: 2026-01-18T11:02:44.918Z
edited boolean True for roughly 1 row in 6 — true exactly when updatedAt > createdAt.
example: false
likes integer 0–999, skewed low the way real comment sections are.
example: 4
status string published | pending | spam. Note this enum differs from the post route's status.
example: published
/api/posts/comments?postId=post_9f3a2c018d3104&count=20&seed=42 {
"data": [
{
"id": "comment_a958016cfb6a00",
"postId": "post_9f3a2c018d3104",
"parentId": null,
"depth": 0,
"author": {
"name": "Dorian Bahringer",
"email": "dorian.bahringer@example.net",
"avatarUrl": "https://randomapi.dev/api/avatars/image?style=initials&seed=dorian.bahringer&name=Dorian%20Bahringer"
},
"body": "Worth a follow up post. Adding this to my notes. The tooling part matters more than people admit. I would love to see the raw logs.",
"wordCount": 26,
"createdAt": "2026-05-02T07:54:44.633Z",
"updatedAt": "2026-05-02T07:54:44.633Z",
"edited": false,
"likes": 642,
"status": "pending"
},
{
"id": "comment_d40fb15863c101",
"postId": "post_9f3a2c018d3104",
"parentId": "comment_a958016cfb6a00",
"depth": 1,
"author": {
"name": "Pauline Dare",
"email": "pauline.dare@example.com",
"avatarUrl": "https://randomapi.dev/api/avatars/image?style=initials&seed=pauline.dare&name=Pauline%20Dare"
},
"body": "The second half is the useful part. Sharing the raw numbers would help a lot. Still thinking about this. This is the clearest write up I have read on it.",
"wordCount": 30,
"createdAt": "2026-05-03T15:57:12.582Z",
"updatedAt": "2026-05-03T15:57:12.582Z",
"edited": false,
"likes": 273,
"status": "published"
},
{
"id": "comment_0b56b87c243102",
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/posts/comments?maxDepth=0&count=25&seed=8 {
"data": [
{
"id": "comment_c8e6c2ed0e0e00",
"postId": "post_7ed05d64418b02",
"parentId": null,
"depth": 0,
"author": {
"name": "Etha Weissnat",
"email": "etha.weissnat@example.org",
"avatarUrl": "https://randomapi.dev/api/avatars/image?style=initials&seed=etha.weissnat&name=Etha%20Weissnat"
},
"body": "We ended up going the other way. The tooling part matters more than people admit. We solved it differently.",
"wordCount": 19,
"createdAt": "2026-05-02T04:38:16.387Z",
"updatedAt": "2026-05-02T04:38:16.387Z",
"edited": false,
"likes": 523,
"status": "published"
},
{
"id": "comment_8dd634067d4501",
"postId": "post_98125864a0ce01",
"parentId": null,
"depth": 0,
"author": {
"name": "General Schimmel",
"email": "general.schimmel@example.org",
"avatarUrl": "https://randomapi.dev/api/avatars/image?style=initials&seed=general.schimmel&name=General%20Schimmel"
},
"body": "We ran into the same wall. I am not sure I agree with the second point. I read this twice everywhere instead.",
"wordCount": 22,
"createdAt": "2026-05-03T11:47:16.013Z",
"updatedAt": "2026-05-03T11:47:16.013Z",
"edited": false,
"likes": 851,
"status": "pending"
},
{
"id": "comment_ef251140dc7702",
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/posts/comments?maxDepth=4&postCount=5&count=100&seed=13 {
"data": [
{
"id": "comment_f04daec2f14600",
"postId": "post_1093aaf880df03",
"parentId": null,
"depth": 0,
"author": {
"name": "Judith Breitenberg",
"email": "judith.breitenberg@example.com",
"avatarUrl": "https://randomapi.dev/api/avatars/image?style=initials&seed=judith.breitenberg&name=Judith%20Breitenberg"
},
"body": "We solved it differently. Noted and shared. Bookmarking this for the next time it comes up.",
"wordCount": 16,
"createdAt": "2026-05-02T00:51:42.418Z",
"updatedAt": "2026-05-02T00:51:42.418Z",
"edited": false,
"likes": 761,
"status": "pending"
},
{
"id": "comment_d09b0c7674d001",
"postId": "post_1093aaf880df03",
"parentId": "comment_f04daec2f14600",
"depth": 1,
"author": {
"name": "Elisabeth Kris",
"email": "elisabeth.kris@example.org",
"avatarUrl": "https://randomapi.dev/api/avatars/image?style=initials&seed=elisabeth.kris&name=Elisabeth%20Kris"
},
"body": "Curious whether it held up after a month. Noted and shared. I am not sure I agree with the second point.",
"wordCount": 21,
"createdAt": "2026-05-02T21:43:57.619Z",
"updatedAt": "2026-05-02T21:43:57.619Z",
"edited": false,
"likes": 162,
"status": "pending"
},
{
"id": "comment_709dfdc7cd9d02",
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/posts/comments?status=spam&count=15&seed=5 {
"data": [
{
"id": "comment_f09c06aab35e00",
"postId": "post_e26f9066eacd02",
"parentId": null,
"depth": 0,
"author": {
"name": "Craig Lemke",
"email": "craig.lemke@example.org",
"avatarUrl": "https://randomapi.dev/api/avatars/image?style=initials&seed=craig.lemke&name=Craig%20Lemke"
},
"body": "The measurement approach is the interesting part. Bookmarking this for the next time it comes up. Good timing on this one. We solved it differently.",
"wordCount": 25,
"createdAt": "2026-05-02T15:43:59.968Z",
"updatedAt": "2026-05-02T15:43:59.968Z",
"edited": false,
"likes": 36,
"status": "spam"
},
{
"id": "comment_698bced3f90801",
"postId": "post_e26f9066eacd02",
"parentId": "comment_f09c06aab35e00",
"depth": 1,
"author": {
"name": "Julia Russel",
"email": "julia.russel@example.org",
"avatarUrl": "https://randomapi.dev/api/avatars/image?style=initials&seed=julia.russel&name=Julia%20Russel"
},
"body": "I would love to see the raw logs. That is a good point.",
"wordCount": 13,
"createdAt": "2026-05-03T16:09:30.360Z",
"updatedAt": "2026-05-03T16:09:30.360Z",
"edited": false,
"likes": 18,
"status": "spam"
},
{
"id": "comment_7c8f3238a8d902",
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/posts/comments?from=2026-01-01&to=2026-01-31&count=30&seed=6 {
"data": [
{
"id": "comment_cbce5cf2258f00",
"postId": "post_bee281fc10ed01",
"parentId": null,
"depth": 0,
"author": {
"name": "Alphonso Walsh",
"email": "alphonso.walsh@example.org",
"avatarUrl": "https://randomapi.dev/api/avatars/image?style=initials&seed=alphonso.walsh&name=Alphonso%20Walsh"
},
"body": "Worth a follow up post. We ran into the same wall. Sharing the raw numbers would help a lot.",
"wordCount": 19,
"createdAt": "2026-01-01T02:29:04.672Z",
"updatedAt": "2026-01-01T02:29:04.672Z",
"edited": false,
"likes": 101,
"status": "published"
},
{
"id": "comment_3cbdf641ceae01",
"postId": "post_bee281fc10ed01",
"parentId": null,
"depth": 0,
"author": {
"name": "Sonja Windler",
"email": "sonja.windler@example.net",
"avatarUrl": "https://randomapi.dev/api/avatars/image?style=initials&seed=sonja.windler&name=Sonja%20Windler"
},
"body": "Worth a follow up post. Bookmarking this for the next time it comes up.",
"wordCount": 14,
"createdAt": "2026-01-01T14:02:20.548Z",
"updatedAt": "2026-01-01T14:02:20.548Z",
"edited": false,
"likes": 678,
"status": "published"
},
{
"id": "comment_bb5e49fc7ca002",
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} Two routes: CMS-shaped blog posts, and a threaded comment page for them.
/api/posts/comments builds one pool of 100 rows parent-first — a reply's parent is always a row at a lower index — and count returns a prefix of that pool. Every prefix is therefore closed under parentId: row 0 is always a root, every non-null parentId names a row printed above it in the same response, depth is exactly parent.depth + 1, and a reply always inherits its parent's postId. count=1 is a valid tree; so is count=100.
maxDepth genuinely bounds the nesting: maxDepth=0 gives you a flat, one-level list (every parentId null, every depth 0), maxDepth=4 produces real reply chains four levels deep and never five.
Each of the six topics — engineering, design, product, security, data, career — owns a disjoint vocabulary. ?tag=security pins tags[0] and the pool the title, excerpt and body are drawn from, so a security post contains security words (threat, exploit, credential, hardening, privilege, rotation…) and zero words from the other five topics' sets. Neutral connective vocabulary is shared, so the prose still reads like prose. Sub-labels in tags[1..] always belong to the same topic — posts are deliberately never cross-tagged, because a foreign tag would be a label the body never earns.
minWords/maxWords bound wordCount (20–160), and wordCount is exactly the number of whitespace-separated tokens in body that contain an ASCII letter — the ## and - markers contain none, so they never count. The body is a deliberate CommonMark subset: blank-line-separated paragraphs, at most one ## subheading (from wordCount 60 up) and at most one - bullet list (from 85 up). No code fences, tables, links, images, emphasis or inline code; that is /api/markdown's job, where each of them is a separately honored flag.
No body repeats a sentence. Every clause pool holds far more distinct words than the 160-word ceiling, and a clause already used in a body is never chosen again while an unused one still fits the remaining budget — so even minWords=160&maxWords=160 gives you 160 words of non-repeating prose rather than the same paragraph twice.
excerpt is never generated separately from the body — it is a word-boundary prefix of the body's paragraph text (the paragraphs joined by single spaces), at most excerptChars UTF-16 code units, with a trailing … only when it was actually truncated. ## headings and - bullets are skipped rather than spliced in: neither carries sentence punctuation, so including them ran a heading straight into the next sentence. That … is the only non-ASCII character in the endpoint's own vocabulary: titles, slugs, bodies, usernames and comment emails are pure ASCII, and author names are ASCII-folded before a username, e-mail or slug is derived from them.
status |
publishedAt |
updatedAt |
metrics |
|---|---|---|---|
draft |
null |
inside [createdAt, to] |
all exactly 0 |
scheduled |
1–30 days after to |
inside [createdAt, to] |
all exactly 0 |
published |
inside [createdAt, to] |
inside [publishedAt, to] |
views ≥ 1, views ≥ likes ≥ commentCount ≥ 0 |
archived |
inside [createdAt, to] |
inside [publishedAt, to] |
same as published |
Omit status for a weighted mix (published 60 %, archived 15 %, draft 15 %, scheduled 10 %). likes and commentCount are derived by multiplying down from views, so the ordering is structural rather than lucky. metrics.commentCount is a display counter and is unrelated to how many rows /comments returns for that id.
The two routes' status enums are different on purpose: a post is draft | scheduled | published | archived, a comment is published | pending | spam (moderation states). A pending reply to a published comment is intentional — status never affects tree structure.
Record i is dated inside slot i of 100 equal slots across [from, to], so records come out oldest-first and record i depends only on the seed, the resolved params and i — never on count. The first three rows of ?seed=42&count=3 are byte-identical to the first three of ?seed=42&count=100 on both routes. Changing a param that a record consumes randomness for (tag, status, tagCount, minWords/maxWords) shifts that record's whole draw sequence, so its createdAt moves inside the window too — pin every param you care about, not just the seed.
Both routes read the clock only to default to (the final millisecond of the current UTC day), so a pinned seed drifts once per UTC day; the resolved window is echoed in meta.params. Pin from and to for a frozen fixture. A window narrower than 100 ms has fewer milliseconds than slots, so slots collapse: timestamps become non-decreasing instead of strictly increasing, and a warning says so.
/api/posts. /comments mints its own synthetic post_… ids; they are not the ids /api/posts returns for the same seed, and row 0's author is not post 0's author. That independence is deliberate work, not luck: the platform hands both routes the same seeded generators for record 0, so the comment route steps them past the prefix the post route consumes before it mints anything. The supported mechanism for tying the two together is ?postId= — pass any post id you already hold and every row is pinned to it (and postCount is then ignored, with a warning).replyCount on comments. It could only be counted over the full 100-row pool, so a smaller count would claim more replies than it shows. Rows are self-describing instead.readingMinutes. With bodies capped at 160 words it would always be 1. Compute it from wordCount if you need it.?locale=. The topic pools are English; a locale param would translate author names only and leave every title and body in English — a half-honored param./api/pagination covers cursors and page envelopes./api/conversations is private chat threads with participants and unread counts. /api/reviews is star-rated product opinions with sentiment-coherent text. /api/relational-seed?schema=blog emits a whole five-table blog schema for database seeding — with flat, one-level comment rows (post_id only, no parentId), pst_/cmt_ id prefixes and a /sql route of INSERT statements. This endpoint is the CMS article plus its public, genuinely threaded comment tree, which is why its ids use post_/comment_ instead — nobody should assume the two interchange.
format=csv flattens nested objects to dot-keys (author.name, author.email, metrics.views) and JSON-encodes arrays inside one cell (tags → "[""security"",""appsec""]"). null renders as an empty cell, so status=draft&format=csv gives you empty publishedAt cells.
Yes. Rows are emitted parent-first, so any count returns a prefix that is closed under parentId: a root's parentId is null, and every reply names a row printed above it in the same response.
Yes — maxDepth=0 returns roots only: every parentId is null and every depth is 0.
No. /api/posts/comments mints its own synthetic post_… ids, and they are not the ids /api/posts returns for the same seed — the comment route deliberately steps its generators past the prefix the post route consumes, so a shared seed cannot line the two up by accident. Pass postId= to attach a comment page to any post id you already hold.
minWords/maxWords bound wordCount between 20 and 160 words, so count=100 stays inside the platform's 256 KiB response budget. For a long multi-section document with code fences, tables and images, use /api/markdown.
posts is a CMS article plus its public comment tree; conversations is private chat threads with unread counts; reviews is star-rated product opinions; relational-seed emits a whole blog schema for database seeding, with flat one-level comment rows and SQL inserts.
No — every one uses an RFC 2606 §3 reserved domain (example.com/.net/.org), which can never deliver mail.
Complete fake people — name, age-consistent birth date, email, phone, job and address — with working filters for gender, age range and email domain.
Sentiment-coherent fake product reviews — 1–2★ complain, 3★ shrug, 4–5★ rave — with star-rating and date-range filters and full seed determinism.
Bounded fake direct, group and support conversations with valid participant references, chronological messages and exact unread counts.
Multi-section Markdown documents where every feature flag is a hard guarantee — code blocks, tables, lists, links and images appear when on, never when off.
A multi-table seed dataset where every foreign key resolves — JSON tables plus topologically ordered SQL that applies with FK checks left on.
Hotlinkable SVG placeholder images at any size from 8 to 4000 px — custom label, colors and an optional gradient. Deterministic and crisp at every scale.