md2link

Terasoft CMS API — Mobile Integration Guide

DraftMay 5, 2026

Terasoft CMS API — Mobile Integration Guide

Note: This guide is for the development environment only.

Base URL: https://{host}/api/v1/terasoft Base Path: api/v1/terasoft Version: v1

Protocol: REST (GET only)


Host

DEV: https://cms-ads-proxy.dev.aperogroup.ai PROD: https://cms-ads-proxy.aperogroup.ai

Authentication & Headers

Header Required Description
x-api-bundleid Yes App bundle ID (e.g., com.example.terasoft)
country-code No ISO country code (e.g., VN, US). Resolves localized content and filters by country targeting. If omitted, only default (non-targeted) items are returned.
app-version No Semantic version string (e.g., 1.2.0). Filters by targeting version range. If omitted, only default (non-targeted) items are returned.

Endpoints

1. Get App Configuration

Retrieves the full app config including screens, banners, categories, and styles.

GET /api/v1/terasoft/app

Example Request

curl 'https://{host}/api/v1/terasoft/app' \
  -H 'x-api-bundleid: com.example.terasoft' \
  -H 'country-code: VN' \
  -H 'app-version: 1.2.0'

Example Response 200 OK

{
  "statusCode": 200,
  "message": "Success",
  "data": {
    "id": 1,
    "documentId": "hc38djwakn6k0nnr32ktm5jg",
    "code": "terasoft-app",
    "name": "Terasoft App",
    "bundleId": "com.example.terasoft",
    "screens": [
      {
        "id": 1,
        "title": "Home",
        "banners": [
          {
            "id": 4,
            "action": "STYLE",
            "tool": null,
            "categoryCode": null,
            "thumbnail": {
              "id": 5,
              "documentId": "awyw2g4dtdr4k86oxcg4t286",
              "ext": ".jpg",
              "mime": "image/jpeg",
              "url": "https://dev-static.apero.vn/content/uploads/banner.jpg",
              "path": "/content/uploads/banner.jpg",
              "width": 640,
              "height": 959
            },
            "style": { "documentId": "sueljb97n89rbw6wf2f1prqk" },
            "title": "Banner VN",
            "subTitle": "Subtitle VN"
          },
          {
            "id": 7,
            "action": "CATEGORY",
            "tool": null,
            "categoryCode": "category.1",
            "thumbnail": {
              "id": 6,
              "documentId": "yojvtj7b23sy7oqkzkk8zk8e",
              "ext": ".jpg",
              "mime": "image/jpeg",
              "url": "https://dev-static.apero.vn/content/uploads/banner2.jpg",
              "path": "/content/uploads/banner2.jpg",
              "width": 640,
              "height": 960
            },
            "style": null,
            "title": "Banner Category VN",
            "subTitle": "Subtitle Category VN"
          },
          {
            "id": 8,
            "action": "STYLE_BLOCK",
            "tool": null,
            "categoryCode": null,
            "thumbnail": {
              "id": 7,
              "documentId": "xyz789abc012def345",
              "ext": ".jpg",
              "mime": "image/jpeg",
              "url": "https://dev-static.apero.vn/content/uploads/banner3.jpg",
              "path": "/content/uploads/banner3.jpg",
              "width": 640,
              "height": 960
            },
            "style": { "documentId": "sb_abc123def456ghijk" },
            "title": "Curated Collection",
            "subTitle": "Hand-picked styles"
          }
        ],
        "categories": [
          {
            "id": 1,
            "code": "1",
            "title": "Category VN",
            "subTitle": "Subtitle",
            "layout": null,
            "tags": ["NEW"],
            "cardPreviews": [
              {
                "id": 16,
                "role": "BEFORE",
                "media": {
                  "id": 6,
                  "documentId": "yojvtj7b23sy7oqkzkk8zk8e",
                  "ext": ".jpg",
                  "mime": "image/jpeg",
                  "url": "https://dev-static.apero.vn/content/uploads/preview.jpg",
                  "path": "/content/uploads/preview.jpg",
                  "width": 640,
                  "height": 960
                }
              }
            ],
            "styles": [
              {
                "title": null,
                "subTitle": null,
                "tags": ["NEW"],
                "layout": null,
                "promptParams": { "color": "red" },
                "cardPreviews": [],
                "examples": [
                  { "id": 1, "type": "GOOD", "description": "Clear portrait, natural lighting", "thumbnail": { "id": 20, "documentId": "f020", "ext": ".jpg", "mime": "image/jpeg", "url": "/uploads/good.jpg", "path": "/uploads/good.jpg", "width": 640, "height": 960 } },
                  { "id": 2, "type": "BAD", "description": "Blurry, poor composition", "thumbnail": null }
                ],
                "usecases": [
                  { "code": "portrait", "name": "Portrait" }
                ],
                "documentId": "sueljb97n89rbw6wf2f1prqk",
                "code": "terasoft-style-example",
                "serviceUrl": "https://service.example.com",
                "configuration": {},
                "adsCount": 1,
                "monetization": "FREE",
                "workflowId": "workflow-anime-v1",
                "isUserPromptRequired": false,
                "requiredImageCount": 1,
                "styleType": "IMAGE"
              }
            ]
          }
        ]
      }
    ]
  },
  "timestamp": "10/03/2026 10:30:00",
  "path": "/api/v1/terasoft/app",
  "traceId": "8d8a89f5-5913-42e6-916d-1abd8b612a65"
}

Response Data Model

Field Type Description
code string App code
name string App display name
bundleId string App bundle identifier
screens Screen[] App screens

Screen

Field Type Description
title string Screen name (e.g., "Home")
banners Banner[] Promotional banners
categories Category[] Style categories

Banner

Field Type Description
action string Banner action type: STYLE, CATEGORY, TOOL, or STYLE_BLOCK
tool string? Tool identifier (has value when action=TOOL, else null)
categoryCode string? CategoryBlock code (has value when action=CATEGORY, else null)
thumbnail Media? Banner image
style StyleRef? Style reference { documentId }. When action=STYLE, documentId is a collection documentId. When action=STYLE_BLOCK, documentId is the style block code (e.g., sb_abc123). null for other actions.
title string? Resolved localized title (string or null)
subTitle string? Resolved localized subtitle (string or null)

Category

Field Type Description
id number Entry ID
code string Category unique code identifier (e.g., category.1)
title string? Resolved localized title based on country-code header (string or null)
subTitle string? Resolved localized subtitle based on country-code header (string or null)
layout string? Visual layout type for client rendering (e.g., grid, list). null if not configured.
tags string[] Display tags: NEW, HOT, TRENDING (multi-select)
cardPreviews CardPreview[] Preview images for this category (used as category thumbnail/cover)
styles StyleBlock[] Styles belonging to this category

StyleBlock (flattened — StyleBlock merged with inner style relation)

Field Type Description
title string? Resolved title (outer override → inner style fallback)
subTitle string? Resolved subtitle (outer override → inner style fallback)
tags string[] Display tags: NEW, HOT, TRENDING (multi-select)
layout string? Visual layout type (optional)
promptParams object? JSON prompt parameter configuration (nullable)
cardPreviews CardPreview[] Preview images (outer override → inner style fallback)
examples Example[] Good/bad examples (outer override → inner style fallback)
usecases UsecaseListItem[] Associated usecases [{ code, name }]
documentId string? Strapi documentId of the referenced style
code string? Unique style code
serviceUrl string? Backend processing service URL
configuration object? Opaque JSON config for processing service
adsCount number? Number of ads to show
monetization string? FREE, PAID, or SUB_ONLY
workflowId string? Workflow identifier for processing
isUserPromptRequired boolean? Whether user prompt is required
requiredImageCount number? Number of images required
styleType string? IMAGE or VIDEO

2. Get Categories

Returns a flat list of categories from the app.

GET /api/v1/terasoft/categories

Query Parameters

Parameter Required Description
categoryCode No Filter to a single category by its code
categoryTags No Filter by category tags (comma-separated: NEW,HOT,TRENDING). OR logic.
variant No Variant label for A/B testing (e.g., A, B, control)

Example Request

curl 'https://{host}/api/v1/terasoft/categories' \
  -H 'x-api-bundleid: com.example.terasoft' \
  -H 'country-code: VN' \
  -H 'app-version: 1.2.0'

Example Response 200 OK

{
  "statusCode": 200,
  "message": "Success",
  "data": [
    {
      "id": 3,
      "code": "category.1",
      "layout": "grid",
      "tags": ["NEW"],
      "cardPreviews": [
        {
          "id": 25,
          "role": "BEFORE",
          "media": {
            "id": 6,
            "documentId": "yojvtj7b23sy7oqkzkk8zk8e",
            "ext": ".jpg",
            "mime": "image/jpeg",
            "url": "https://dev-static.apero.vn/content/uploads/preview.jpg",
            "path": "/content/uploads/preview.jpg",
            "width": 640,
            "height": 960
          }
        }
      ],
      "title": "title category VN",
      "subTitle": "sub-title category VN"
    }
  ],
  "timestamp": "10/03/2026 15:44:45",
  "path": "/api/v1/terasoft/categories",
  "traceId": "..."
}

CategoryListItem (same as Category in app response, without styles)

Field Type Description
id number Entry ID
code string Category unique code identifier (e.g., category.1)
title string? Resolved localized title based on country-code header (string or null)
subTitle string? Resolved localized subtitle based on country-code header (string or null)
layout string? Visual layout type for client rendering (e.g., grid, list). null if not configured.
tags string[] Display tags: NEW, HOT, TRENDING (multi-select)
cardPreviews CardPreview[] Preview images for this category (used as category thumbnail/cover)

3. Get Category Tags

Returns distinct tags present across all visible categories.

GET /api/v1/terasoft/category-tags

Query Parameters

Parameter Required Description
variant No Variant label for A/B testing (e.g., A, B, control)

Example Request

curl 'https://{host}/api/v1/terasoft/category-tags' \
  -H 'x-api-bundleid: com.example.terasoft' \
  -H 'country-code: VN' \
  -H 'app-version: 1.2.0'

Example Response 200 OK

{
  "statusCode": 200,
  "message": "Success",
  "data": ["NEW", "HOT", "TRENDING"],
  "timestamp": "10/03/2026 10:31:10",
  "path": "/api/v1/terasoft/category-tags",
  "traceId": "..."
}

Note: data is a flat string[] array of unique tag values from visible categories.


4. Get Tags (style block tags)

Returns unique tags from all styles in the app.

GET /api/v1/terasoft/tags

Query Parameters

Parameter Required Description
categoryCode No Filter by category code
usecase No Filter by usecase code
variant No Variant label for A/B testing (e.g., A, B, control)

Example Request

curl 'https://{host}/api/v1/terasoft/tags?categoryCode=1&usecase=portrait' \
  -H 'x-api-bundleid: com.example.terasoft' \
  -H 'country-code: VN' \
  -H 'app-version: 1.2.0'

Example Response 200 OK

{
  "statusCode": 200,
  "message": "Success",
  "data": ["NEW", "HOT", "TRENDING"],
  "timestamp": "10/03/2026 10:31:15",
  "path": "/api/v1/terasoft/tags",
  "traceId": "..."
}

Note: data is a flat string[] array of unique tag values.


5. Get Usecases

Returns unique usecases from all styles in the app.

GET /api/v1/terasoft/usecases

Query Parameters

Parameter Required Description
categoryCode No Filter by category code
tags No Filter by tags (comma-separated: NEW,HOT,TRENDING). OR logic.
usecase No Filter by usecase code
variant No Variant label for A/B testing (e.g., A, B, control)

Example Request

curl 'https://{host}/api/v1/terasoft/usecases?categoryCode=1&tags=NEW' \
  -H 'x-api-bundleid: com.example.terasoft' \
  -H 'country-code: VN' \
  -H 'app-version: 1.2.0'

Example Response 200 OK

{
  "statusCode": 200,
  "message": "Success",
  "data": [
    { "code": "portrait", "name": "Portrait" },
    { "code": "landscape", "name": "Landscape" }
  ],
  "timestamp": "10/03/2026 10:31:30",
  "path": "/api/v1/terasoft/usecases",
  "traceId": "..."
}

UsecaseListItem

Field Type Description
code string Usecase code
name string? Usecase display name

6. Get Styles (with filters)

Returns all styles from the app, with optional query filters.

GET /api/v1/terasoft/styles

Query Parameters

Parameter Required Description
categoryCode No Filter by category code
tags No Filter by tags (comma-separated: NEW,HOT,TRENDING). OR logic.
usecaseCode No Filter by usecase code
variant No Variant label for A/B testing (e.g., A, B, control)

Example Request

curl 'https://{host}/api/v1/terasoft/styles?categoryCode=1&tags=NEW&usecaseCode=portrait' \
  -H 'x-api-bundleid: com.example.terasoft' \
  -H 'country-code: VN' \
  -H 'app-version: 1.2.0'

Example Response 200 OK

{
  "statusCode": 200,
  "message": "Success",
  "data": [
    {
      "title": null,
      "subTitle": null,
      "tags": ["NEW"],
      "layout": null,
      "promptParams": null,
      "cardPreviews": [],
      "examples": [],
      "usecases": [
        { "code": "portrait", "name": "Portrait" }
      ],
      "documentId": "sueljb97n89rbw6wf2f1prqk",
      "code": "terasoft-style-example",
      "serviceUrl": "https://service.example.com",
      "configuration": {},
      "adsCount": 1,
      "monetization": "FREE",
      "workflowId": "workflow-anime-v1",
      "isUserPromptRequired": false,
      "requiredImageCount": 1,
      "styleType": "IMAGE"
    }
  ],
  "timestamp": "10/03/2026 10:32:00",
  "path": "/api/v1/terasoft/styles",
  "traceId": "..."
}

Note: data is an array of StyleBlock objects (same shape as in app response).


7. Get Style Detail

Retrieves a single style by its identifier. The identifier can be:

  • A collection documentId (e.g., wt2dx8v10ry610d2b8g66yq9) — fetches from the terasoft-style collection
  • A style block code (e.g., sb_abc123def456ghijk) — fetches the style block with override resolution

Both return the same response shape. Use the documentId from banner.style.documentId or category.styles[].documentId.

GET /api/v1/terasoft/styles/{documentId}

Path Parameters

Parameter Type Description
documentId string Style collection documentId or style block code (starts with sb_)

Example Request

# Collection style
curl 'https://{host}/api/v1/terasoft/styles/wt2dx8v10ry610d2b8g66yq9' \
  -H 'x-api-bundleid: com.example.terasoft' \
  -H 'country-code: VN'

# Style block
curl 'https://{host}/api/v1/terasoft/styles/sb_abc123def456ghijk' \
  -H 'x-api-bundleid: com.example.terasoft' \
  -H 'country-code: VN'

Example Response 200 OK

{
  "statusCode": 200,
  "message": "Success",
  "data": {
    "id": 15,
    "documentId": "wt2dx8v10ry610d2b8g66yq9",
    "code": "terasoft-style-example",
    "styleType": "IMAGE",
    "serviceUrl": "https://service.example.com",
    "configuration": { "prompt": "test" },
    "promptParams": { "color": "red" },
    "workflowId": "workflow-anime-v1",
    "adsCount": 1,
    "monetization": "FREE",
    "isUserPromptRequired": false,
    "requiredImageCount": 1,
    "title": "Example Style",
    "subTitle": "Example Style (sub)",
    "cardPreviews": [
      {
        "id": 14,
        "role": "BEFORE",
        "media": {
          "id": 6,
          "documentId": "yojvtj7b23sy7oqkzkk8zk8e",
          "ext": ".jpg",
          "mime": "image/jpeg",
          "url": "https://dev-static.apero.vn/content/uploads/before.jpg",
          "path": "/content/uploads/before.jpg",
          "width": 640,
          "height": 960
        }
      }
    ],
    "examples": [
      { "id": 1, "type": "GOOD", "description": "Clear portrait", "thumbnail": { "id": 20, "documentId": "f020", "ext": ".jpg", "mime": "image/jpeg", "url": "/uploads/good.jpg", "path": "/uploads/good.jpg", "width": 640, "height": 960 } }
    ]
  },
  "timestamp": "10/03/2026 10:33:00",
  "path": "/api/v1/terasoft/styles/wt2dx8v10ry610d2b8g66yq9",
  "traceId": "0abde934-cce5-4115-bc6d-29e5cfe2da4b"
}

TerasoftStyle Data Model

Field Type Description
code string Unique style code
styleType string? IMAGE or VIDEO
serviceUrl string? Backend processing service URL
configuration object? Opaque JSON config for processing service
promptParams object? JSON prompt parameter configuration (nullable)
workflowId string Workflow identifier for processing
adsCount number? Number of ads to show
monetization string? FREE, PAID, or SUB_ONLY
isUserPromptRequired boolean? Whether user prompt is required
requiredImageCount number? Number of images required
title string? Resolved localized title
subTitle string? Resolved localized subtitle
cardPreviews CardPreview[] Preview images
examples Example[] Good/bad example images

Shared Types

StyleRef

Field Type Description
documentId string Strapi document ID

Usecase

Field Type Description
id number Entry ID
documentId string Strapi document ID
code string Usecase code
name string Usecase display name

CardPreview

Field Type Description
id number Entry ID
role string BEFORE, AFTER, or SINGLE
media Media Image asset

Example

Field Type Description
id number Entry ID
type string GOOD or BAD
description string? Description of the example
thumbnail Media? Example image (nullable)

Media

Field Type Description
id number Media ID
documentId string Strapi document ID
ext string File extension (e.g., .jpg)
mime string MIME type (e.g., image/jpeg)
url string Full CDN URL
path string Path extracted from url (domain stripped)
width number Image width (px)
height number Image height (px)

Error Responses

All errors follow the same format:

{
  "correlationId": "uuid",
  "statusCode": 404,
  "timestamp": "ISO-8601",
  "path": "/api/v1/terasoft/...",
  "message": "Error description",
  "errorCode": "ERR24"
}
Status Condition
400 Missing x-api-bundleid header
404 App not found for given bundleId
404 Style not found for documentId
500 Upstream CMS error

Integration Flow

1. GET /api/v1/terasoft/app  (with x-api-bundleid + app-version + country-code headers)
   ├── Full app config (screens, banners, categories, style blocks)
   ├── Server returns: default items (no targeting) + items matching your headers
   └── Localized content (title, subTitle) resolved to strings based on country-code

2. GET /api/v1/terasoft/categories  (optional)
   └── Flat list of categories [{ code, title, subTitle, tags }] — targeting applied
       Optional: ?categoryCode=1&categoryTags=NEW,HOT to filter

3. GET /api/v1/terasoft/category-tags  (optional)
   └── Unique tags ["NEW", "HOT", "TRENDING"] from visible categories

4. GET /api/v1/terasoft/tags  (optional)
   └── Unique tags ["NEW", "HOT", "TRENDING"] from visible styles
       Optional: ?categoryCode=1&usecase=portrait to filter

5. GET /api/v1/terasoft/usecases  (optional)
   └── Unique usecases [{ code, name }] from visible styles
       Optional: ?categoryCode=1&tags=NEW to filter

6. GET /api/v1/terasoft/styles?categoryCode=1&tags=NEW&usecaseCode=portrait  (optional)
   └── Filter styles by category, tags (OR logic), and/or usecase — targeting applied

7. GET /api/v1/terasoft/styles/{documentId}
   ├── Full style detail when user selects a style
   ├── Use style.configuration for processing params
   └── No targeting filter — always returns the style if it exists

Targeting Behavior

Categories and styles can be assigned targeting rules on the CMS (country and/or version range). The server evaluates these rules and only returns items that match the client context. Targeting fields are not exposed in the response.

Rules

Scenario Headers sent? Result
Item has no targeting (default item) Any Always included
Item targets country VN country-code: VN Included
Item targets country VN country-code: US Excluded
Item targets country VN No country-code (null) Excluded
Item targets version 1.2.0–2.0.0 app-version: 1.5.0 Included
Item targets version 1.2.0–2.0.0 app-version: 0.9.0 Excluded
Item targets version 1.2.0–2.0.0 No app-version (null) Excluded
Item targets country VN + version 1.2.0–2.0.0 country-code: VN, app-version: 1.5.0 Included (both match)
Item targets country VN + version 1.2.0–2.0.0 country-code: VN, app-version: 0.9.0 Excluded (version mismatch)

Summary

  • Default items (no targeting) are always returned, regardless of headers.
  • Targeted items are only returned when the client sends matching country-code and/or app-version headers.
  • If you don't send country-code, it is null — all country-targeted items are excluded.
  • If you don't send app-version, it is null — all version-targeted items are excluded.
  • When both country and version targeting exist on an item, both must match (AND logic).
  • Targeting applies to the Get App, Get Categories, Get Category Tags, Get Tags, Get Usecases, Get Styles endpoints. Get Style Detail (by documentId) does not apply targeting.

Notes

  • All responses are cached server-side (configurable TTL, default 10 minutes). Cache keys are partitioned by bundleId, country-code, and app-version to prevent cross-context pollution.
  • Localized content: title and subTitle are resolved server-side to a single string based on the country-code header. Resolution order: country-specific match → global default (no country) → null. No arbitrary fallback to the first entry.
  • configuration is opaque JSON — parse it as-is without schema validation.
  • configuration usage: parse the configuration object and merge/inject it into the body of the execute workflow request when invoking the processing service. The CMS does not transform it; the client/service is responsible for applying it as workflow input parameters.
  • StyleBlock fields (documentId, code, serviceUrl, etc.) can be null if the referenced style was unpublished or deleted.
  • Media objects include both url (original full URL) and path (domain stripped). Use path when constructing custom CDN URLs.
  • Tags filter uses OR logic: a style is included if it has at least one of the requested tags.