Get Help Article (by slug)
Public read of a single help article by slug. PUBLISHED-only. HTML body server-side sanitized. Embedded category. 404 on missing/draft.
GET /api/v1/public/help/articles/:slug โ ๐ Public ยท Rate limit: 60 req / minute
Returns a single published help article by slug. PUBLISHED-only โ DRAFT slugs return 404. The HTML content is server-side sanitized (same sanitizeHtml() helper as CMS pages โ strips <script>, <style>, <iframe>, inline on* event handlers).
Sanitization is defensive, not exhaustive. Same caveat as GET /public/pages/:slug and GET /public/blog/:slug โ strips obvious XSS but admin authors are still ultimately trusted. Use client-side sanitization (DOMPurify) for additional defense.
Request
Path parameters
| Param | Type | Notes |
|---|---|---|
slug | string | Help article slug (e.g. how-to-withdraw) |
No body, no query params, no headers required.
Response headers
| Header | Value |
|---|---|
Cache-Control | public, s-maxage=300, stale-while-revalidate=600 |
Response
200 OK โ ApiResponseOf<PublicHelpArticleDto>
{
"success": true,
"data": {
"slug": "how-to-withdraw",
"title": "How to Withdraw Funds",
"content": "<p>Sanitized HTML content...</p>",
"excerpt": "Step-by-step guide to withdrawing earnings.",
"icon": "wallet",
"readTimeMinutes": 5,
"publishedAt": "2026-04-29T20:00:00.000Z",
"category": { "name": "Payments & Billing", "slug": "payments" }
}
}| Field | Type | Notes |
|---|---|---|
slug | string | Echoes the path param |
title | string | Article title |
content | string | Server-side sanitized HTML body |
excerpt | string | null | Short summary |
icon | string | null | Icon identifier |
readTimeMinutes | number | null | Estimated read time |
publishedAt | string (ISO 8601) | null | When published |
category | object | { name, slug } of the parent HelpCategory |
Notable absence
The list endpoint returns isFeatured but the single-article endpoint does not โ the featured flag is a list-discovery concern, not relevant when you're already viewing the article.
Errors
| HTTP | code / i18nKey | Reason |
|---|---|---|
404 | error.content.help_article_not_found | No HelpArticle matches slug AND status = PUBLISHED |
429 | (throttle) | Rate limit exceeded (60 req/min) |
Side effects
prisma.helpArticle.findFirst({ where: { slug, status: PUBLISHED }, include: { category: true } }).- Missing โ throw
help_article_not_found(404). - HTML sanitization โ
sanitizeHtml(article.content)(same helper as CMS pages and blog posts). - Project category to
{ name, slug }. - Return assembled object. No mutations.
Code samples
curl https://api.bio.re/api/v1/public/help/articles/how-to-withdrawtype HelpArticle = {
slug: string;
title: string;
content: string;
excerpt: string | null;
icon: string | null;
readTimeMinutes: number | null;
publishedAt: string | null;
category: { name: string; slug: string };
};
async function getHelpArticle(slug: string): Promise<HelpArticle> {
const res = await fetch(`https://api.bio.re/api/v1/public/help/articles/${encodeURIComponent(slug)}`);
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'Help article fetch failed'), {
code: json?.error?.code,
});
}
return json.data;
}import { useQuery } from '@tanstack/react-query';
export const contentKeys = {
helpArticle: (slug: string) => ['content', 'help', 'article', slug] as const,
};
export function useHelpArticle(slug: string) {
return useQuery({
queryKey: contentKeys.helpArticle(slug),
queryFn: async () => {
const res = await fetch(`/api/v1/public/help/articles/${encodeURIComponent(slug)}`);
const json = await res.json();
if (!res.ok || !json.success) {
throw Object.assign(new Error(json?.error?.message ?? 'Help article fetch failed'), {
code: json?.error?.code,
i18nKey: json?.error?.i18nKey,
});
}
return json.data as HelpArticle;
},
enabled: Boolean(slug),
staleTime: 5 * 60_000,
});
}Try it
curl -X GET "https://loading/api/v1/public/help/articles/string"{
"success": true,
"data": {
"slug": "how-to-withdraw",
"title": "How to Withdraw Funds",
"content": "<p>Sanitized HTML content</p>",
"excerpt": "string",
"icon": "string",
"readTimeMinutes": 0,
"publishedAt": "2019-08-24T14:15:22Z",
"category": {
"slug": "payments",
"name": "Payments & Billing"
}
}
}{
"success": false,
"error": {
"code": "AUTH_UNAUTHORIZED",
"message": "Invalid credentials",
"i18nKey": "auth.login.invalid_credentials",
"i18nVars": {
"field": "email"
},
"details": [
{
"message": "email must be an email"
}
],
"correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}Source
| Source | Path | Lines |
|---|---|---|
| Controller | apps/api-core/src/modules/content/public-content.controller.ts | 352โ373 (getHelpArticle), 29โ36 (sanitizeHtml) |
| DTO (response) | apps/api-core/src/modules/content/dto/content-public-response.dto.ts | 260โ284 (PublicHelpArticleDto), 202โ208 (PublicHelpCategoryEmbedDto) |
| Service | apps/api-core/src/modules/content/content.service.ts | 778โ783 (getPublishedHelpArticle) |
| Prisma models | packages/prisma/prisma/schema.prisma | HelpArticle (status = PUBLISHED), HelpCategory (joined) |
List Help Articles
Public paginated list of published help articles. Filters by category slug, search term (title/content/excerpt ILIKE), featured-only, locale. Each item carries embedded category. CDN 5min/10min SWR.
Get FAQ
Public list of FAQ groups with their published items. Optional locale filter. Group order is admin-set; items inside each group are admin-ordered too. Answers server-side sanitized.