BIO.RE
Content

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.

GET /api/v1/public/faq โ€” ๐ŸŒ Public ยท Rate limit: 60 req / minute

Returns FAQ groups (categories) with their published items nested inside. Group order is admin-set (FaqCategory.sortOrder ASC); item order inside each group follows FaqItem.sortOrder ASC. Optional ?locale= filter; otherwise returns groups across all locales.

Items are PUBLISHED-only; groups are not filtered. The category-level filter only applies to items (status = PUBLISHED). Categories themselves are returned regardless of any "active" flag โ€” but a category with zero published items will still appear with items: []. Frontend should hide empty groups if that's the desired UX.

Answer body is sanitized. Each item's answer field passes through sanitizeHtml() โ€” strips <script>, <style>, <iframe>, inline on* event handlers. Same caveat as the rest of the content module: admin authors are trusted, sanitization is defense-in-depth.

Request

Query parameters

ParamTypeDefaultNotes
localestringโ€”Filter by FaqCategory.locale. Omit for all locales.

No headers required.

Response headers

HeaderValue
Cache-Controlpublic, s-maxage=300, stale-while-revalidate=600

Response

200 OK โ€” ArrayApiResponseOf<PublicFaqGroupDto>

{
  "success": true,
  "data": [
    {
      "slug": "account",
      "name": "Account",
      "items": [
        {
          "question": "How do I reset my password?",
          "answer": "<p>Click Forgot Password on the login screen...</p>"
        },
        {
          "question": "How do I change my email?",
          "answer": "<p>Go to Settings...</p>"
        }
      ]
    },
    {
      "slug": "payments",
      "name": "Payments",
      "items": []
    }
  ]
}

Group fields

FieldTypeNotes
slugstringFaqCategory.slug (use as anchor / nav target)
namestringDisplay name
itemsarrayPublished items in this group, ordered by FaqItem.sortOrder ASC. Possibly empty.

Item fields

FieldTypeNotes
questionstringPlain text question (not sanitized โ€” admin-typed plain)
answerstringServer-side sanitized HTML answer

Stripped fields

id, categoryId, status (always PUBLISHED here), sortOrder (used for ordering, not exposed), createdAt, updatedAt, locale (used for filter, not exposed) โ€” all internal-only, excluded.

Errors

HTTPcode / i18nKeyReason
429(throttle)Rate limit exceeded (60 req/min)

Side effects

  1. prisma.faqCategory.findMany({ where: locale ? { locale } : {}, orderBy: sortOrder asc, include: { items: { where: { status: PUBLISHED }, orderBy: sortOrder asc } } }).
  2. Map each group โ†’ strip internal fields, sanitize each item's answer via sanitizeHtml().
  3. Return the array. No mutations.

Code samples

curl https://api.bio.re/api/v1/public/faq
curl 'https://api.bio.re/api/v1/public/faq?locale=tr'
type FaqItem = {
  question: string;
  answer: string;
};

type FaqGroup = {
  slug: string;
  name: string;
  items: FaqItem[];
};

async function getFaq(locale?: string): Promise<FaqGroup[]> {
  const url = new URL('https://api.bio.re/api/v1/public/faq');
  if (locale) url.searchParams.set('locale', locale);
  const res = await fetch(url);
  const json = await res.json();
  if (!res.ok || !json.success) {
    throw Object.assign(new Error(json?.error?.message ?? 'FAQ fetch failed'), {
      code: json?.error?.code,
    });
  }
  return json.data;
}
import { useQuery } from '@tanstack/react-query';

export const contentKeys = {
  faq: (locale?: string) => ['content', 'faq', locale ?? 'all'] as const,
};

export function useFaq(locale?: string) {
  return useQuery({
    queryKey: contentKeys.faq(locale),
    queryFn: async () => {
      const url = new URL('/api/v1/public/faq', window.location.origin);
      if (locale) url.searchParams.set('locale', locale);
      const res = await fetch(url);
      const json = await res.json();
      if (!res.ok || !json.success) {
        throw Object.assign(new Error(json?.error?.message ?? 'FAQ fetch failed'), {
          code: json?.error?.code,
          i18nKey: json?.error?.i18nKey,
        });
      }
      return json.data as FaqGroup[];
    },
    staleTime: 5 * 60_000,
  });
}

Try it

GET
/api/v1/public/faq

Query Parameters

locale?string

Response Body

application/json

curl -X GET "https://loading/api/v1/public/faq"
{
  "success": true,
  "data": [
    {
      "slug": "account",
      "name": "Account",
      "items": [
        {
          "question": "How do I reset my password?",
          "answer": "<p>Click Forgot Password...</p>"
        }
      ]
    }
  ]
}

Source

SourcePathLines
Controllerapps/api-core/src/modules/content/public-content.controller.ts379โ€“394 (listPublishedFaq), 29โ€“36 (sanitizeHtml)
DTO (response item)apps/api-core/src/modules/content/dto/content-public-response.dto.ts302โ€“311 (PublicFaqGroupDto), 291โ€“297 (PublicFaqItemDto)
Serviceapps/api-core/src/modules/content/content.service.ts877โ€“888 (listPublishedFaq)
Prisma modelspackages/prisma/prisma/schema.prismaFaqCategory (admin-ordered), FaqItem (filter status = PUBLISHED, admin-ordered)

On this page