Terasoft CMS API — Mobile Integration Guide
Note: This guide is for the development environment only.
Base URL:
https://{host}/api/v1/terasoftBase Path:api/v1/terasoftVersion: v1Protocol: 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:
datais a flatstring[]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:
datais a flatstring[]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:
datais 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-codeand/orapp-versionheaders. - If you don't send
country-code, it isnull— all country-targeted items are excluded. - If you don't send
app-version, it isnull— 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, andapp-versionto prevent cross-context pollution. - Localized content:
titleandsubTitleare resolved server-side to a single string based on thecountry-codeheader. Resolution order: country-specific match → global default (no country) →null. No arbitrary fallback to the first entry. configurationis opaque JSON — parse it as-is without schema validation.configurationusage: parse theconfigurationobject 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 benullif the referenced style was unpublished or deleted. - Media objects include both
url(original full URL) andpath(domain stripped). Usepathwhen constructing custom CDN URLs. - Tags filter uses OR logic: a style is included if it has at least one of the requested tags.