Download OpenAPI specification:
The ShortDrama Integration API gives licensed partners programmatic access to our short-drama catalogue: the tag vocabulary, series metadata, the episodes within each series, and signed credentials for playing an episode.
It is a read-only, JSON-over-HTTPS API. Every request is authenticated with a signed HMAC signature.
GET /tags. It takes no parameters, so it is
the simplest way to confirm your signing implementation is correct.GET /contents and GET /items, then request
playback credentials per episode with POST /item/{item_id}/playback.Keep your API secret server-side. It must never be embedded in a mobile app, a browser, or any client you do not control.
Send these three headers on every request:
| Header | Value |
|---|---|
x-api-key |
Your API key. |
x-api-timestamp |
The current time, as an RFC 3339 UTC timestamp, e.g. 2026-09-05T09:41:00Z. |
x-api-signature |
The request signature - see below. |
Concatenate four values, separated by newline (\n) characters, and compute
an HMAC-SHA256 over the result using your API secret. Send the digest as
lowercase hexadecimal.
stringToSign = METHOD + "\n" + PATH + "\n" + QUERY + "\n" + TIMESTAMP
signature = lowercase_hex( HMAC_SHA256( stringToSign, your_api_secret ) )
| Part | Rule |
|---|---|
METHOD |
The HTTP method, uppercase: GET or POST. |
PATH |
The path component of the request URI, with no query string and no scheme or host. If your base URL includes a path prefix, that prefix is part of PATH. |
QUERY |
The query string without the leading ?. Empty for requests that have no query parameters, and always empty for POST requests - see the note below. |
TIMESTAMP |
Byte-for-byte the same string you send in x-api-timestamp. |
The query string is part of the signature, so it must be built identically on both sides. When a request has query parameters:
name=value pairs joined with &.application/x-www-form-urlencoded
rules - in particular, encode a space as +, not as %20.For POST requests the QUERY line is always empty, even if the request
URI carries query parameters. Do not put parameters in the query string of a
POST.
Check your implementation against these before contacting support. All four
use the example secret sk_test_51H9xQmLp7vNfR2kY and the timestamp
2026-09-05T09:41:00Z. This secret is for testing your signing code only and
will not authenticate against any environment.
GET /tags
stringToSign: "GET\n/tags\n\n2026-09-05T09:41:00Z"
signature: 23120606736e9388670c2a48d47c88644292a4418c8568441db03bd057225b36
GET /contents?page=1&page_size=50
stringToSign: "GET\n/contents\npage=1&page_size=50\n2026-09-05T09:41:00Z"
signature: b3d00e63a5583af5e65c43459c5f5388aca3d9746f41e701c8dcd7e8a40f0c58
GET /contents?keywords=lost+heiress&page=1 - note the space encoded as +
stringToSign: "GET\n/contents\nkeywords=lost+heiress&page=1\n2026-09-05T09:41:00Z"
signature: 9770792d56eba77edccb5dd5aaa7958002a109e44dcbe22beeca902c1e1f221a
POST /item/9001/playback - note the empty query line
stringToSign: "POST\n/item/9001/playback\n\n2026-09-05T09:41:00Z"
signature: cf14407cd322c2a81ab665cdf90c8bb90e6042ab1c30e7bc0d3b064b7552e6e1
A request is rejected if its timestamp is more than 5 minutes from server time, in either direction. Keep your servers synchronised with NTP. Generate a fresh timestamp and signature per request rather than reusing them.
GET /contents and GET /items return only titles that are approved and
published. Depending on how your key was provisioned, they return either the
titles licensed to your account or the full partner catalogue. Your scope is
fixed when the key is issued and is not reported in responses - if the
catalogue looks smaller or larger than you expect, contact your account
manager.
Every response, successful or not, uses the same three-key envelope:
{ "code": 2000, "messages": [], "payload": { } }
| Key | Meaning |
|---|---|
code |
Application status code. 2000 on success; 4011 for authentication failures; 4041 for a resource that is unavailable. |
messages |
Empty on success. On failure, one or more error objects. |
payload |
The result on success. An empty array ([]) on failure. |
Check the HTTP status code first. On a non-2xx response, branch on
messages[0].error_type, which is a stable machine-readable key. Do not
branch on messages[0].text - that wording may change without notice.
GET /contents and GET /items accept page and page_size, and return a
pagination object alongside the results.
Always send an explicit page_size. If you omit it, you receive the
maximum page size of 1000 records, which is rarely what you want. Values
above 1000 are capped, and values below 1 are treated as 1.
Both list endpoints are ordered by last-modified, most recent first, and
accept updated_after and updated_before as Unix epoch seconds. To sync
incrementally, record the highest timestamp you have seen and pass it as
updated_after on your next run.
Poll no more than once every 5 minutes. Request playback credentials at the moment of playback rather than caching them - see the endpoint description for why.
created_at, updated_at, publish_at) are Unix epoch
seconds, not date strings.*_view_url field, which is a ready-to-use URL. The
corresponding image_path is an internal storage key that is not directly
resolvable and may change.null. New fields may be added to any
response without a version change, so parse permissively and ignore fields
you do not recognise.Returns the full tag vocabulary with every translation, most recently updated first.
The list is small and changes rarely. Cache it and refresh periodically
rather than requesting it alongside every catalogue call. Titles
reference tags by id in the tags array on each content record.
This endpoint takes no parameters, which makes it the simplest request to verify your signing implementation against.
| code required | integer Application status code. |
required | Array of objects (ErrorMessage) Empty on success. |
required | Array of objects The result. Shape depends on the endpoint. |
{- "code": 2000,
- "messages": [ ],
- "payload": [
- {
- "tag_id": 12,
- "status": "active",
- "primary_title": "Romance",
- "type": "genre",
- "created_at": 1750000000,
- "updated_at": 1757000000,
- "translations": [
- {
- "tag_id": 12,
- "language_code": "EN",
- "title": "Romance",
- "description": "Love stories.",
- "seo_title": "Romance drama",
- "seo_description": "Watch romance short dramas.",
- "seo_keywords": "romance,drama",
- "created_at": 1750000000,
- "updated_at": 1757000000
}
]
}
]
}Returns a paginated list of series available to your account, most recently updated first. Each record includes its tag ids, all translations, and any supporting documents.
To retrieve the episodes of a series, call GET /items with
content_ids set to the series' content_id.
| page | integer >= 1 Default: 1 Example: page=1 The page to return, starting at 1. |
| page_size | integer [ 1 .. 1000 ] Default: 1000 Example: page_size=50 Records per page, from 1 to 1000. Omitting this returns the maximum of 1000 records, so always send an explicit value. Larger values are capped at 1000. |
| keywords | string Example: keywords=lost heiress Free-text search across translated titles and descriptions, in every language. Two behaviours to handle when using this parameter: a series that
matches in more than one language may appear more than once in
|
| content_ids | string Example: content_ids=101,102,103 Restrict the result to specific series. Comma-separated |
| updated_after | integer <int64> Example: updated_after=1756000000 Return only records modified at or after this time, as Unix epoch seconds. Use this to sync incrementally. |
| updated_before | integer <int64> Example: updated_before=1757000000 Return only records modified strictly before this time, as Unix epoch seconds. |
| code required | integer Application status code. |
required | Array of objects (ErrorMessage) Empty on success. |
required | object The result. Shape depends on the endpoint. |
{- "code": 2000,
- "messages": [ ],
- "payload": {
- "results": [
- {
- "content_id": 101,
- "primary_title": "The Heiress Returns",
- "image_path": "content/101/cover.jpg",
- "director": "Jane Doe",
- "actor": "A. Lee, B. Chan",
- "region": "CN",
- "view_restriction": "public",
- "content_type": "series",
- "status": "active",
- "publish_at": 1756000000,
- "created_at": 1750000000,
- "updated_at": 1757000000,
- "tags": [
- 12,
- 14
], - "documents": [
- {
- "doc_type": "synopsis",
- "created_at": 1750000000,
- "updated_at": 1750000000
}
], - "translations": [
- {
- "content_id": 101,
- "language_code": "EN",
- "title": "The Heiress Returns",
- "description": "She came back for what was hers.",
- "seo_title": "The Heiress Returns",
- "seo_description": "Watch The Heiress Returns.",
- "seo_keywords": "heiress,revenge",
- "created_at": 1750000000,
- "updated_at": 1757000000
}
]
}
], - "pagination": {
- "total": 137,
- "page": 1,
- "limit": 50,
- "total_pages": 3
}
}
}Returns a paginated list of episodes available to your account, most recently updated first, each with its translations.
Episodes belong to a series through content_id. Filter by
content_ids to fetch the episodes of one or more specific series.
| page | integer >= 1 Default: 1 Example: page=1 The page to return, starting at 1. |
| page_size | integer [ 1 .. 1000 ] Default: 1000 Example: page_size=50 Records per page, from 1 to 1000. Omitting this returns the maximum of 1000 records, so always send an explicit value. Larger values are capped at 1000. |
| content_ids | string Example: content_ids=101,102 Return only episodes belonging to these series. Comma-separated
|
| item_ids | string Example: item_ids=9001,9002 Return only these episodes. Comma-separated |
| updated_after | integer <int64> Example: updated_after=1756000000 Return only records modified at or after this time, as Unix epoch seconds. Use this to sync incrementally. |
| updated_before | integer <int64> Example: updated_before=1757000000 Return only records modified strictly before this time, as Unix epoch seconds. |
| code required | integer Application status code. |
required | Array of objects (ErrorMessage) Empty on success. |
required | object The result. Shape depends on the endpoint. |
{- "code": 2000,
- "messages": [ ],
- "payload": {
- "results": [
- {
- "item_id": 9001,
- "content_id": 101,
- "primary_title": "Episode 1",
- "media_type": "video",
- "category": "uncategorized",
- "image_path": "items/9001/thumb.jpg",
- "duration": 92,
- "view_restriction": "public",
- "status": "active",
- "publish_at": 1756000000,
- "created_at": 1750000000,
- "updated_at": 1757000000,
- "translations": [
- {
- "item_id": 9001,
- "language_code": "EN",
- "title": "Episode 1",
- "description": "The return.",
- "created_at": 1750000000,
- "updated_at": 1757000000
}
]
}
], - "pagination": {
- "total": 480,
- "page": 1,
- "limit": 50,
- "total_pages": 10
}
}
}Returns the credentials needed to play one episode through the Tencent Cloud VOD player SDK.
Pass the returned fileID, appID and psign to the player. Playback
is delivered as protected adaptive streaming with Widevine DRM.
The credentials expire 60 seconds after they are issued. Request them at the point of playback, in response to a viewer pressing play. Do not fetch them ahead of time, cache them, or request them in bulk while syncing your catalogue - they will have expired by the time they are used.
The request has no body. Any query parameters on the request URI are ignored, and are not part of the signature.
A 404 is returned if the episode does not exist, is not published, or
is not available for playback.
| item_id required | integer <int64> Example: 9001 The |
| code required | integer Application status code. |
required | Array of objects (ErrorMessage) Empty on success. |
required | object The result. Shape depends on the endpoint. |
{- "code": 2000,
- "messages": [ ],
- "payload": {
- "fileID": "1397757911008782056",
- "appID": 1500000000,
- "psign": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxx.yyy"
}
}