This guide describes the complete workflow for programmatically publishing article content to a Gan Jing World channel account using the Platform API.
Publishing an article to Gan Jing World is a 5-step process that spans two API hosts:
| Host | Purpose | Authorization |
|---|---|---|
gw.ganjingworld.com | Platform API — authentication, content management | Raw ACCESS_TOKEN (no Bearer prefix) |
imgapi.cloudokyo.cloud | Image CDN — poster and inline image upload and resizing | Bearer <UPLOAD_TOKEN> |
Important: The Platform API (
gw.ganjingworld.com) expects the raw access token in theAuthorizationheader. The CDN API (cloudokyo.cloud) expects aBearerprefix. Mixing these up will cause 401 errors.
1. Authenticate → Obtain ACCESS_TOKEN + REFRESH_TOKEN
2. Get Upload Token → Exchange ACCESS_TOKEN for UPLOAD_TOKEN
3. Upload Images → Upload poster image and all inline article images
4. Create Article Content → Create article entry with HTML body via /add-content
5. Update Article Content → (Optional) Update the article via /update-content
Key Difference from Video Upload: Articles do not require video file upload (TUS protocol), upload ID registration, or transcoding status polling. The article is published as soon as the
/add-contentcall succeeds.
Obtain your ACCESS_TOKEN and REFRESH_TOKEN through the Gan Jing World login flow. These tokens are typically extracted from the browser session after logging into your channel account.
Please log in or create a new account to get the token.
Sign InAccess tokens expire after approximately 10 days. Refresh tokens remain valid for 3 months. Use the refresh endpoint to obtain a new access token before it expires.
curl -X POST 'https://gw.ganjingworld.com/v1.1/auth/token/refresh' \
-H 'Content-Type: application/json' \
-d '{"refresh_token": "<REFRESH_TOKEN>"}'
Response:
{
"access_token": "<NEW_ACCESS_TOKEN>",
"refresh_token": "<NEW_REFRESH_TOKEN>",
"expires_in": 864000
}
Note: The response may wrap tokens in a
bodyordatafield depending on the API version. Checkdata.access_token,body.access_token, and the top-levelaccess_tokenfield. Theexpires_invalue is in seconds (864000 = 10 days). Always store and use the new refresh token from the response, as the previous one may be invalidated.
Best Practice: Implement proactive token refresh — refresh the token when it is within 1 hour of expiry rather than waiting for a 401 error. If you do receive a 401, refresh the token and retry the request once.
Exchange your access token for a short-lived upload token used with the CDN APIs.
curl -X GET 'https://gw.ganjingworld.com/v1.0c/get-vod-token' \
-H 'Authorization: <ACCESS_TOKEN>' \
-H 'Accept: application/json'
Note: The
Authorizationheader uses the raw access token — do NOT include aBearerprefix.
Response:
{
"body": {
"token": "<UPLOAD_TOKEN>"
},
"status_code": 200
}
Note: The token may appear at different paths in the response:
body.token,data.token,body.upload_token, orbody.vod_token. Check multiple paths for robustness.
The UPLOAD_TOKEN is used for all subsequent CDN image upload operations.
Articles require image uploads for two purposes:
Both types use the same image upload endpoint on the CDN.
curl -X POST 'https://imgapi.cloudokyo.cloud/api/v2/image' \
-H 'Authorization: Bearer <UPLOAD_TOKEN>' \
-H 'resizing-list: 140,240,360,380,480,580,672,960,1280,1920' \
-F 'file=@"image.jpg"'
Important notes:
- The endpoint is
/api/v2/image(not/api/v1/image).- Only send the
filefield in the multipart form — do NOT include anamefield.- Authorization uses
Bearer <UPLOAD_TOKEN>.
Response:
{
"body": {
"image_id": "abc123",
"image_url": [
"https://image5-us-west.cloudokyo.cloud/.../140.jpg",
"https://image5-us-west.cloudokyo.cloud/.../240.webp",
"https://image5-us-west.cloudokyo.cloud/.../480.jpg",
"https://image5-us-west.cloudokyo.cloud/.../480.webp",
"https://image5-us-west.cloudokyo.cloud/.../672.webp",
"https://image5-us-west.cloudokyo.cloud/.../960.webp",
"https://image5-us-west.cloudokyo.cloud/.../1280.webp",
"https://image5-us-west.cloudokyo.cloud/.../1920.webp",
"https://image5-us-west.cloudokyo.cloud/.../origin.webp"
]
},
"file_upload": "abc123",
"status_code": 200
}
From the image_url array of the poster image upload, select two URLs:
| Field | Size Key | Purpose |
|---|---|---|
poster_url | 672.webp | Standard definition poster (used in listings) |
poster_hd_url | origin.webp | High definition poster (used on article page) |
Fallback order for poster_url: 672 → 480 → 580 → 360 → 240 → 140
Fallback order for poster_hd_url: origin → 1920 → 1280 → 960 → 672
Prefer .webp over .jpg when both are available at the same size.
For images embedded in the article HTML body, use the appropriate size URL from the upload response. Reference these URLs directly in <img> tags within the meta.text HTML content (see Step 4).
Example inline image tag:
<img src="https://image1-us-east.cloudokyo.cloud/image/v14/.../960.webp" alt="Description" />
Important: All images that need to be embedded in the article body must be uploaded through the
imgapi.cloudokyo.cloudimage upload process first. Do not reference external image URLs that have not been uploaded to the CDN.
Create the article content entry with metadata and the HTML body. This uses the same /add-content endpoint as video upload, but with type set to "News" and the article body provided in the meta.text field.
curl -X POST 'https://gw.ganjingworld.com/v1.0c/add-content' \
-H 'Authorization: <ACCESS_TOKEN>' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Cache-Control: no-cache' \
-H 'Origin: https://studio.ganjingworld.com' \
-d '{
"user_id2": "<CHANNEL_ID>",
"type": "News",
"mode": "ready",
"lang": "en-US",
"category_id": "cat22",
"title": "Article Title Here",
"description": "Optional short description or summary",
"visibility": "public",
"poster_url": "https://image1-us-east.cloudokyo.cloud/.../672.webp",
"poster_hd_url": "https://image1-us-east.cloudokyo.cloud/.../origin.webp",
"meta": {
"text": "<p style=\"margin-left:0px;\">Your article HTML content here...</p>"
},
"keywords": [],
"hashtags": ["#topic1", "#topic2"],
"premium": 0,
"use_schedule": false,
"made_for_kids": false,
"user_made_for_kids": false,
"protected_type": "",
"slug": "",
"deleteTranslations": []
}'
Key Differences from Video Upload:
typeis"News"(not"Video").modeis set to"ready"— this indicates the article content is complete and ready to publish. (For video uploads,modeis typically omitted to allow auto-publish after transcoding.)- The
metaobject with atextfield contains the full article body as HTML.- No video file upload or transcoding is needed — the article is published once this call succeeds.
| Field | Type | Required | Description |
|---|---|---|---|
user_id2 | string | Yes | Your GJW channel ID |
type | string | Yes | Always "News" for articles |
mode | string | Yes | Set to "ready" for immediate publish |
lang | string | Yes | Language code (e.g., "en-US") |
category_id | string | Yes | Content category (e.g., "cat22") |
title | string | Yes | Article title |
description | string | No | Short description or summary (can be empty string) |
visibility | string | Yes | "public", "unlisted", or "private" |
poster_url | string | Yes | Standard poster URL (from Step 3) |
poster_hd_url | string | Yes | HD poster URL (from Step 3) |
meta | object | Yes | Contains the text field with article HTML body |
meta.text | string | Yes | Full article content as HTML (see format below) |
keywords | array | No | List of keyword strings |
hashtags | array | No | List of hashtag strings (e.g., ["#topic"]) |
premium | number | No | 0 for free content, 1 for premium |
use_schedule | boolean | No | Whether to use scheduled publishing |
time_scheduled | number | No | Unix timestamp in milliseconds for scheduled publish time (only used if use_schedule is true) |
made_for_kids | boolean | Yes | Whether content is made for children |
user_made_for_kids | boolean | Yes | Whether the creator marked it as for children |
protected_type | string | No | Content protection type (empty string for none) |
slug | string | No | Custom URL slug (empty string for auto-generated) |
deleteTranslations | array | No | List of translation language codes to delete |
thumbnail_score | object | No | Thumbnail quality score with raw_score (float) and score (int) |
meta.text)The meta.text field accepts HTML content. The following HTML elements and patterns are supported:
Paragraphs:
<p style="margin-left:0px;">Paragraph text here.</p>
Links:
<a target="_blank" rel="noopener noreferrer" href="https://example.com">Link text</a>
Inline images (uploaded via Step 3):
<img src="https://image1-us-east.cloudokyo.cloud/image/v14/.../960.webp" alt="Description" />
Bold / Italic / Underline:
<strong>Bold text</strong>
<em>Italic text</em>
<u>Underlined text</u>
HTML entities: Use & for &, for non-breaking spaces, etc.
Example full meta.text value:
<p style="margin-left:0px;">
First paragraph of the article with <strong>bold text</strong> and a
<a target="_blank" rel="noopener noreferrer" href="https://example.com">link</a>.
</p>
<p style="margin-left:0px;"> </p>
<p style="margin-left:0px;">Second paragraph continues the article content.</p>
Note: Empty paragraphs with
can be used as spacers between content blocks.
Response:
{
"content_id": "<CONTENT_ID>",
"owner_id": "<CHANNEL_ID>",
"type": "News",
"title": "Article Title Here",
"visibility": "public",
"created_at": "2025-01-15T10:30:00Z"
}
Note: The
content_idmay also appear atdata.content_id,data.id, oriddepending on the response structure.
To update an existing article (e.g., fix typos, update content, change metadata), use the /update-content endpoint with the CONTENT_ID from Step 4.
curl -X POST 'https://gw.ganjingworld.com/v1.0c/update-content' \
-H 'Authorization: <ACCESS_TOKEN>' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d '{
"content_id": "<CONTENT_ID>",
"user_id2": "<CHANNEL_ID>",
"type": "News",
"mode": "ready",
"title": "Updated Article Title",
"description": "Updated description",
"meta": {
"text": "<p style=\"margin-left:0px;\">Updated article HTML content...</p>"
},
"poster_url": "https://image1-us-east.cloudokyo.cloud/.../672.webp",
"poster_hd_url": "https://image1-us-east.cloudokyo.cloud/.../origin.webp",
"visibility": "public",
"category_id": "cat22",
"lang": "en-US",
"keywords": [],
"hashtags": ["#updated"],
"made_for_kids": false,
"user_made_for_kids": false
}'
Note: Include all fields you want to preserve in the update request — fields not included may be reset to defaults.
After successful creation, the article is accessible at:
https://www.ganjingworld.com/news/<CONTENT_ID>
| Aspect | Article (News) | Video (Video) |
|---|---|---|
| Content type | "News" | "Video" |
| Mode | "ready" | Omit (auto-publish after transcode) |
| Body content | meta.text (HTML) | Video file (TUS upload) |
| Image upload | Poster + inline images via CDN | Poster only via CDN |
| Video upload | Not required | TUS resumable upload required |
| Upload ID registration | Not required | Required (Step 5 in video guide) |
| Transcoding / polling | Not required | Required (poll until .m3u8 URL) |
| Steps to publish | 4 (auth → token → images → create) | 8 (auth → token → thumbnail → create → register → upload → poll → finalize) |
| Published URL path | /news/<CONTENT_ID> | /video/<CONTENT_ID> |
image_url array contains the expected sizes before proceeding.meta.text point to successfully uploaded CDN images. Broken image references will result in missing images in the published article.meta.text HTML body.& for & in HTML, and escape quotes in the JSON string (\").