About acedatacloud
acedatacloud is a skill for managing AceDataCloud accounts and platform resources. It serves account operators and developers with balance, usage, credential, service, and order operations, plus public catalog and documentation browsing through a CLI and REST API.
Key features
- 01Check balances and API usage
- 02Manage credentials, services, and orders
- 03Browse the public catalog and docs
- 04CLI commands with JSON output
- 05Preview write operations by default
Use cases
- 01Review subscription balances and call records
- 02Create or delete API keys
- 03Find service pricing and API documentation
- 04Check recharge order status
MCP support
The skill describes managing account resources, article and video drafts, review threads, and blog publication through MCP / REST. It also supports browsing the public catalog and documentation without a token.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ acedatacloud/ skills --skill acedatacloudAfter installing, type/acedatacloudin Claude Code to confirm it loaded.
Bundled filesscripts/acedatacloud.py
Details
From the project READMEAceDataCloud Platform Management
Programmatically manage your AceDataCloud account: balances, usage records, API keys, services, orders, platform tokens, models, announcements and referral earnings, and manage article/video drafts, review threads and peer-reviewed blog publication through MCP / REST — plus browse the public catalog & docs (service pricing, API specs, datasets, integrations, documentation search, model catalog) without a token.
This is the management / console API at https://platform.acedata.cloud/api/v1
— the same surface the web console uses. It is different from the
data-generation API at api.acedata.cloud (image / video / music / search generation).
Setup — use a PLATFORM token, not a service token
The management API is authenticated with a platform token (or a logged-in
user token), not the per-service API token used for api.acedata.cloud.
- Create one at platform.acedata.cloud/console/platform-tokens
(or
POST /api/v1/platform-tokens/). It starts withplatform-and never expires. - Provide it one of two ways:
- Connector (recommended in studio / chat): install the AceDataCloud
connector at studio.acedata.cloud/console/connectors
and paste the token once — the runtime injects
ACEDATACLOUD_PLATFORM_TOKENinto the sandbox automatically (this skill declaresconnections: [acedatacloud]). - Local
.env:
- Connector (recommended in studio / chat): install the AceDataCloud
connector at studio.acedata.cloud/console/connectors
and paste the token once — the runtime injects
ACEDATACLOUD_PLATFORM_TOKEN=platform-v1-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
curl -H "authorization: Bearer $ACEDATACLOUD_PLATFORM_TOKEN" \
https://platform.acedata.cloud/api/v1/applications/
A normal token only ever sees its own account data. A superuser token sees every user's data and is required for admin operations (announcements).
OAuth account access
OAuth access tokens authorize account-resource access; they are not model API Keys. OAuth consent does not create a Key, subscribe to a service, or add balance. For a third-party account connection:
- Register the app at
https://auth.acedata.cloud/user/oauth-apps. Readavailable_scopesfrom the authenticatedGET https://auth.acedata.cloud/api/v1/oauth2/applications/, not the broader Discoveryscopes_supportedlist. Request only registered permissions the user has: e.g.,profile:read credentials:read, addingcredentials:writeonly for confirmed Key changes andoffline_accessonly if refresh is needed. Oldprofile,email,phone, andplatformscopes are not accepted aliases; use explicit current scopes such asprofile:readandemail:read. Theaccountselector is reserved for the designated official account MCP, not a third-party all-permissions scope. - Open
https://auth.acedata.cloud/oauth2/authorizewithresponse_type=code,client_id, the exact registeredredirect_uri, space-separatedscope, a fresh randomstate, and S256 PKCE. Public clients must use S256, notplain; keep the verifier in the application's own session. Verify callback state before exchanging the single-use code (valid for 10 minutes). UsePOST https://auth.acedata.cloud/oauth2/tokenwith form fieldsgrant_type=authorization_code,client_id,redirect_uri,code, andcode_verifier. Confidential clients also send server-sideclient_secret(client_secret_post, not HTTP Basic); never put a secret in a browser page. Read the actual grantedscopeandexpires_in; noid_tokenis returned. - With the OAuth Bearer token, read
GET https://auth.acedata.cloud/api/v1/users/me, then use its realidinGET https://platform.acedata.cloud/api/v1/credentials/?user_id=<id>&limit=100&offset=0if grantedcredentials:read. Readitems[*].token, page withoffset/limit, and choose a valid Key for the intended application/service rather than taking the first item.user_id=meis invalid. An empty list is not an authorization failure. Never display or log Keys, codes, tokens, or PKCE verifiers. - For a Studio website component using the current Studio account, configure the app ID and callback under Settings → Home → Website. Website and callback must share an HTTPS origin distinct from Studio; never configure a client secret. The page must implement
acedatacloud:oauth:ready,acedatacloud:oauth:init,acedatacloud:oauth:authorize, andacedatacloud:oauth:result, validating the exact parent origin, parent window, and state; retain the verifier locally and exchange the returned code. This component requestsprofile:read credentials:read, notoffline_accessor blog permissions; each visitor must consent separately. It does not inject Studio's login token or a Key. Standard OAuth redirects do not require this message protocol orembed=studio/embed_origin; ordinary iframe first-login continuation has a known gap, so verify logged-out/expired sessions or use an independent page instead. On denial orlogin_required, restore a retryable connection state rather than looping authorization. - Refresh only if
offline_accesswas granted, using the token endpoint withgrant_type=refresh_tokenand the saved refresh token; retain the latest response. Refresh does not guarantee the old refresh token immediately becomes invalid.POST https://auth.acedata.cloud/oauth2/revokewith form fieldtokencurrently returns HTTP 200 without immediate invalidation of issued JWTs. Clear your own saved connection state, but do not report that all tokens are revoked. Copied API Keys must be separately revoked or rotated in credential management.
The bundled CLI does not register OAuth apps or perform the browser/PKCE exchange. For invalid_scope, check the app's registered scopes, delegation restrictions, and user permissions rather than broadening access. Verify denial, state mismatch, expired/consumed code, missing scopes, no Keys, network failures, token expiry, and iframe account changes before relying on a connection.
CLI (preferred)
The skill ships scripts/acedatacloud.py — a self-contained
CLI (stdlib only) for the most common operations.
# $SKILL_DIR can point at another skill loaded this turn — anchor on our own
# script (re-run this at the top of every fresh-shell Bash block).
ADC="$SKILL_DIR/scripts/acedatacloud.py"; [ -f "$ADC" ] || ADC=$(find /tmp -maxdepth 8 -path '*/skills/*/scripts/acedatacloud.py' 2>/dev/null | head -1)
[ -f "$ADC" ] || { echo "acedatacloud script not found (SKILL_DIR=$SKILL_DIR)" >&2; exit 1; }
# Read
python3 $ADC balance # remaining credits per subscription
python3 $ADC services --search suno # list/search subscribed services
python3 $ADC usage --days 7 # recent API call records
python3 $ADC usage-summary --days 30 # spend aggregated by day + API
python3 $ADC keys # list API keys (credentials)
python3 $ADC orders --state Finished # recharge orders
python3 $ADC tokens # platform tokens
python3 $ADC models # available chat models
python3 $ADC distributions # referral status + commission history
# Catalog & docs (PUBLIC — work without a token)
python3 $ADC get-service --service suno # one service's detail
python3 $ADC pricing --service suno # unit, free_amount and cost (dataset reference quotes when present)
python3 $ADC apis --service suno # API endpoints for a service
python3 $ADC spec --path /suno/audios # one API's OpenAPI definition + cost
python3 $ADC datasets # downloadable datasets
python3 $ADC integrations # third-party integrations
python3 $ADC docs-search --query "suno lyrics" --lang en # full-text doc search
python3 $ADC docs-list # browse documentation pages
python3 $ADC doc --id <document-uuid> # one doc's full content
python3 $ADC model-catalog --modality chat # rich model catalog + credit pricing
python3 $ADC model --model claude # look up a model by id/name
# Safe write (require --yes to actually execute)
python3 $ADC create-key --application <app-id> --name "ci" --yes
python3 $ADC delete-key --id <credential-id> --yes
python3 $ADC create-order --application <app-id> --package <package-id> --yes
python3 $ADC pay-order --id <order-id> --pay-way Stripe --yes
python3 $ADC create-token --yes
python3 $ADC delete-token --id <token-id> --yes
# Admin (superuser token only)
python3 $ADC send-announcement --title "..." --content "..." --yes
Every command accepts --json for machine-readable output and --token to
override the env token. Without --yes, write commands print a dry-run preview
and exit without calling the API.
Pagination
List endpoints return { "count": <total>, "items": [ ... ] } and accept
?limit= and ?offset=. (Note: some older docs say results; the live field
is items.)
Endpoints reference
Balance / subscriptions — GET /applications/
An Application is your subscription to one Service; its remaining_amount is
your balance (in Credits) for that service.
curl -H "authorization: Bearer $ACEDATACLOUD_PLATFORM_TOKEN" \
"https://platform.acedata.cloud/api/v1/applications/?limit=1"
{
"count": 3,
"items": [
{
"id": "e9f625f2-cbd5-4254-8264-cfadbd180428",
"service_id": "f2b646d8-3cfd-46ef-969a-1ea9eebde329",
"remaining_amount": 100.9,
"used_amount": 1.1,
"paid": false,
"scope": "Global",
"allow_consume_global": true,
"expired_at": null,
"type": "Usage"
}
]
}
Filters: service_id, scope (Individual/Global), user_id (superuser only).
Usage records — GET /usage/apis/
Per-request call records (status code, latency, credits deducted).
Filters: api_id, application_id, status_code, created_at_from,
created_at_to, user_id (superuser only).
{
"count": 128,
"items": [
{
"id": "c124684b-7188-4c3e-ad53-3fba5150b944",
"api": { "title": "Suno Audios Generation API" },
"status_code": 200,
"original_amount": 0.55,
"deducted_amount": 0.55,
"trace_id": "…",
"elapsed": 12.3,
"created_at": "2026-06-28T09:15:44Z"
}
]
}
- Spend aggregate —
GET /usage/apis/aggregate/?created_at_from=&created_at_to=→{ "items": [{ "date", "api_id", "amount" }], "total": <credits>, "apis": { "<id>": { "title" } } } - Status-code filter values —
GET /usage/apis/status-codes/→{ "items": [200, 400, 502] } - CSV export —
GET /usage/apis/export/?created_at_from=&created_at_to=(downloadsusages.csv)
Services — GET /services/
{ "count": 215, "items": [ { "id": "…", "alias": "suno", "title": "Suno 音乐生成",
"unit": "Credit", "free_amount": 0.0, "applied_count": 121733,
"packages": [ { "id": "…", "type": "Usage", "price": 13.0, "amount": 100.0 } ] } ] }
API keys / credentials — GET /credentials/
{ "count": 2, "items": [ { "id": "dae4899f-…", "name": "ci", "type": "Token",
"token": "<secret>", "limited_amount": null, "used_amount": 1.1,
"host": null, "created_at": "2026-06-28T09:15:44Z" } ] }
- Create —
POST /credentials/{ "application_id": "<app>", "name": "ci", "limited_amount": 100, "expired_at": "2026-12-31T00:00:00Z" }→ returns the credential incl. the newtoken. Save it; it is shown in full only at creation. - Delete —
DELETE /credentials/{id}→204 No Content - Filters:
application_id,host,granted(true/false),user_id(superuser). - To "rotate" a key, delete it and create a new one.
Orders (recharge) — GET /orders/
{ "count": 12, "items": [ { "id": "17c10c9d-…", "application_id": "…",
"package_id": "…", "description": "100 Credits", "price": 13.0,
"state": "Finished", "pay_way": "Stripe", "pay_url": "<url>",
"created_at": "2026-06-28T09:17:25Z" } ] }
state∈PendingPaidFinishedExpiredFailedRefundedpay_wayincludesWechatPay,AliPay,Stripe,X402,PayPal,Reward, andBankTransfer; availability depends on the order and payment configuration.- Create —
POST /orders/{ "application_id": "<app>", "package_id": "<package>" } - Pay —
POST /orders/{id}/pay/{ "pay_way": "Stripe" }→ returns the order with apay_urlto open - Refresh status —
POST /orders/{id}/refresh/(re-checks the PSP and updatesstate) - Filters:
state,pay_way,created_at_from,created_at_to,user_id(superuser).
Bank transfer
- Authenticate as the order owner. Personal and business accounts can use
BankTransferwithout enterprise verification; anonymous requests cannot. Domestic CNY and international USD transfers both require an order of at least USD 300, subject to payment-channel configuration and package eligibility. - Read
GET /api/v1/orders/{order_id}/payment-options/on the management host to check current transfer routes and minimum amounts. The CLI has no payment-options command; use authenticated REST rather than inventing a flag. Do not publish internal route/provider identifiers. - Confirm the order ID, amount and available route with the user before
pay-order --id <order-id> --pay-way BankTransfer --yes. Use only the payment instructions returned for that order; the displayed CNY amount is authoritative, not a locally estimated conversion. A pay response is not proof that funds arrived: reread the order and verify its final state before reporting completion.
Invoice reversal and reapplication
- Read the current invoice with authenticated
GET /api/v1/orders/{order_id}/invoice/. The CLI has no invoice command. - An automatic red-invoice reversal submission leaves the invoice
Issued; inspectred_statefor progress. Submission is not completion. Do not reapply while confirmation, red-invoice issuance or archiving is pending. - Reapply only after successful red-invoice issuance and archiving change the original invoice to
Voidedand release the order's invoicing restriction. Failed or uncertain reversals require finance review; do not bypass the restriction. The original invoice remains in invoice history. Manually issued invoices must first be voided or reversed in the tax/invoicing system, then confirmed by finance in the platform.
X402 network selection
- Read the current order or API payment challenge's
acceptsbefore selecting a network. Arbitrum One (arbitrumin official TypeScript/Python X402 clients, CAIP-2eip155:42161on the wire) has opt-in code support, not guaranteed production availability. If it is absent fromaccepts, do not attempt payment on that chain; a page feature flag does not enable an unconfigured server network. - Arbitrum supports only
exactpayments with native USDC (6 decimals), not bridged USDC orupto. Use the returned asset, recipient andaccepts[].maxAmountRequired; never hardcode an amount or invent payment parameters. - Confirm the wallet supports the target chain and has USDC on that chain. The same EVM address can have different balances on different networks; a Solana address is not an EVM USDC address. Obtain confirmation before signing or paying; network support does not authorize a charge.
X402 failure recovery
- The initial HTTP 402 without
PAYMENT-SIGNATUREis a payment challenge, not a failed payment. For signed-payment failures, readextensions.acedatacloud.paymentError; do not construct wallet signatures or recipient/asset addresses from guesses. - Reread the authenticated order list (
orders --json) orGET /api/v1/orders/{order_id}/.metadata.x402.last_errorandmetadata.x402.errorsretain the publiccode,stage,retryable,chargedandparams, with explanation inreasonand time intimestamp. Preserve the specific code:insufficient_token_balancemeans insufficient USDC on the selected network;invalid_signaturemeans an invalid payment signature. Do not replace either with genericpayment_failedafter rereading. - Only explicit
charged=falsemeans a pre-settlement refusal; a missingchargedmeans unknown, not uncharged. Check payment evidence before spending again. Even ifretryable=true, aFailedorder cannot be retried in place: resolve the wallet problem and obtain confirmation before creating and paying a new order. - Share only the order ID and public error code with support. Never log or share full order metadata,
PAYMENT-SIGNATURE, wallet signatures, authorization payloads or raw payment/RPC diagnostics.
X402 ACE buyback and burn proof
When the buyback policy is enabled, eligible Base mainnet Circle USDC and Solana mainnet USDC payments allocate 3% of the actual received amount to buy and burn ACE, with no extra wallet charge. Recharge is counted once; spending those Credits on APIs does not count again. Historical discounted orders and old-policy orders are not automatically backfilled. There is no fixed per-operation or daily USDC buyback cap; available funds, market quotes and chain confirmations can still delay completion.
- Read authenticated
GET /api/v1/orders/{order_id}/on the management host as the order owner and inspectace_burn. The CLI only lists orders, not an order-detail or burn command; the order list and anonymous pay-link response do not includeace_burn. If it is absent or null, do not infer eligibility or fabricate proof. - Distinguish the payment receipt (
settlement_network,settlement_transaction) from the asynchronous ACE execution (execution_network). Base payments retain their Base receipt; buying and burning ACE happens on Solana. A Base receipt is not a Solana burn transaction. Do not expose operator configuration or internal funding details. - Report verified burn completion only for
ace_burn.state=Completed, using theswap_signatureandburn_signatureactually returned.Pending,ProcessingorReviewis not proof of a burn, and the order'sFinishedstate does not prove the burn completed. Base source finality can delay the burn without affecting already credited Credits. Reread the same order to check progress; do not repay or create another order to trigger a burn.
Platform tokens — GET /platform-tokens/
{ "count": 2, "items": [ { "id": "efdccba4-…", "token": "<secret>",
"expiration": null, "used_at": null, "created_at": "2026-06-28T06:29:02Z" } ] }
- Create —
POST /platform-tokens/→ returns the new token (starts withplatform-) - Delete —
DELETE /platform-tokens/{id}/→204 No Content
Models — GET /models/
OpenAI-style (no pagination): { "object": "list", "data": [ { "id": "gpt-4.1", "label": "GPT-4.1", "owned_by": "openai", "type": "chat", "capabilities": ["vision"] } ] }
Catalog & docs (PUBLIC — no token required)
These work without auth (send the token if you have one; not required):
- Service detail / pricing —
GET /services/?id=<uuid>→items[0](full:cost,unit,free_amount,title). Theservices/?alias=filter is ignored server-side, so resolve an alias by pagingservices/and matchingaliasclient-side. Theservices/{id}/detail route is broken (500). - APIs —
GET /apis/?path=<path>→ exactly one item with its OpenAPIdefinition+cost.apis/?service_id=is ignored server-side; filter byservice_idclient-side.apis/{id}/is broken (500). - Datasets / integrations —
GET /datasets/,GET /integrations/. - Doc search —
GET /search/?query=<text>&lang=<code>→{ results: [ { id, alias, title, type, snippet, url } ] }. The param isquery(notkeyword). - Doc content —
GET /documents/?id=<uuid>→items[0].content.documents/{id}/(id or slug) is broken (404). - Model catalog —
GET /models/catalog/→{ rates, modalities, count, items:[{ id, name, provider, modality, unit, capabilities, pricing:{ input_credits, output_credits, official_* } }] }. Filter client-side.
Regional catalog discovery
- Read the current public catalog from the caller's environment. Use
get-service --service <alias-or-uuid> --jsonto inspectavailable_regions,unavailable_regions, the service's chat-onlychat_unavailable_regionsand image-onlyimage_unavailable_regions. Useapis --service <alias-or-uuid> --json,docs-list --jsonordoc --id <document-uuid> --jsonfor API/document region fields. Thepricingandspecsummaries omit these fields. Empty region lists mean no explicit regional rule, not an access grant. - Visibility follows the requesting visitor's IP, not the display language or a
?country=override. Mainland-China catalog views hide marked Claude services and chat APIs/documents. Mixed OpenAI, Gemini and Grok services hide their chat entries, but media visibility must be checked separately: OpenAI image generation/editing and companion guides are also hidden in mainland China. Do not hide the entire service based onchat_unavailable_regionsorimage_unavailable_regions; unrelated media entries retain their own rules. Raw REST/CLI results may still include marked rows; apply their region metadata when presenting choices rather than treating presence as visibility. - Respect the current public results when presenting catalog choices. Do not use a cached global list to advertise region-hidden chat or images, or treat a missing entry/404 as proof of retirement or revoked API access. This presentation filtering does not change API routing or billing; check actual account access, key restrictions and current pricing separately before a service call.
Dataset reference pricing
- Resolve the requested dataset's public service with
get-service --service <alias-or-uuid>and read its current metadata. Contact-only datasets are catalog entries, not automatically downloadable purchases; a missing price does not mean free data. - Run
pricing --service <alias-or-uuid> --json. For a Dataset, the CLI includespricing_modeandreference_quotewhen present. The source fields aremetadata.pricing_modeandmetadata.reference_quoteon the Service, not the Dataset'spriceor an API'scost. - If
pricing_mode="contact_only"and the quote hasstatus="reference", presentmin_amount–max_amountwith the suppliedcurrencyand quoteunit.CNYamounts are yuan, not Credits or USD;full_packagemeans the full package andtbmeans per TB. Equal bounds indicate a single reference amount. Do not apply the Credits-to-USD conversion or invent a quote when none is returned. - Treat a reference quote as indicative only: it does not create a purchase Package, guarantee delivery, or grant download or usage rights. Confirm the required version, scope, format, license and final price with support before proceeding; do not call
create-orderorpay-orderbased on a reference quote.
Announcements — GET /announcements/
Public read: { "count": 20, "items": [ { "id", "title", "content", "translation_key", "published", "publish_at", "rank", "tags", "is_read" } ] }
Admin only (superuser token):
- Publish —
POST /announcements/admin/{ "title": "...", "content": "...", "rank": 5, "tags": ["product"], "published": true }(contentis Markdown; a zh-cn Translation row is created and other locales are auto-translated by a CronJob) - Edit / delete —
PUT/DELETE /announcements/admin/{id} - AI polish —
POST /announcements/admin/polish/{ "title", "content" } - AI translate —
POST /announcements/admin/translate/{ "translation_key" }
Blog drafts, review submission and publication (MCP / REST)
Use mcp-acedatacloud with a platform token or the Studio OAuth connection. The bundled CLI does not implement blog commands; use the MCP tools and inspect their current input schemas rather than inventing CLI flags. Blog permissions can be delegated to non-admin accounts; check the available tools and scopes before writing.
| Permissions | Operations |
|---|---|
blog:read | List drafts and read full editing-source content |
blog:write | Create, edit and delete unpublished drafts; submit or withdraw a pending review |
blog:read + blog:publish | Approve or reject a submitted version, or withdraw approval before publication |
blog:write + blog:publish | Publish or schedule an approved draft, unpublish, or delete a published post |
- Confirm the intended article and source language (
zh-cnoren). Use the current editorial categories:product-updates,tech-sharing,product-recommendations, orindustry-insights(defaulttech-sharing). Read the current category list if a custom category is needed; do not guess a slug. - Save an unpublished draft with
acedatacloud_create_blog_draft, or edit it withacedatacloud_update_blog_post. Read the full source withacedatacloud_get_blog_draft, including title, summary, body, category, cover,content_typeandvideo_url, and retain its currentreview_versionandreview_status. Defaultcontent_type="article"requires a nonblank body and clearsvideo_url. Forcontent_type="video", upload the intended video first and use its actual HTTPSvideo_url; the body may be blank, but title and summary remain required. Never fabricate a media URL. A cover requires HTTPScover_urland nonblankcover_alt. If the discovered MCP schema lacks video fields, usePOST /api/v1/blogs/admin/to create an unpublished draft orPATCH /api/v1/blogs/admin/{id}/to edit one after confirmation; do not invent MCP arguments or CLI flags. - Submit a
draftorrejectedarticle for review before seeking approval. Withblog:write, sendPOST /api/v1/blogs/admin/{id}/submit/on the management host with{"expected_version": CURRENT_REVIEW_VERSION}; verifyreview_status="pending". Legacy drafts without a creator must first be saved by an editor. Pending articles cannot be edited: withdraw the review request withDELETEto the same/submit/route and the currentexpected_version, verifydraft, then edit and resubmit. Withdrawal of a pending review is distinct from withdrawal of approval. - A different authorized account must read the full pending draft (and watch the actual video for a video post) and approve that exact version with
acedatacloud_approve_blog_post. The creator cannot approve their own draft. For REST approval,POST /api/v1/blogs/admin/{id}/approval/requiresexpected_versionequal to thereview_versionjust read. The reviewer may instead sendPOST /api/v1/blogs/admin/{id}/reject/with thatexpected_versionand a nonblankcomment(at most 2,000 characters), or omitcommentif an unresolved root review thread already exists on the current version; verifyreview_status="rejected". Readreview_comment, revise and resubmit, checking the review threads below before another review. A 409 on submission, withdrawal, approval or rejection means the draft changed: refresh and reread the full draft before retrying. Do not impersonate a reviewer or switch credentials to bypass this separation. - Only after
review_status="approved", useacedatacloud_publish_blog_postfrom an authorized account: the publisher must not be the reviewer (the creator may publish after peer approval). For scheduling, provide a timezone-awarepublish_at; omit it to publish now. Verify the returned state and time before reporting the post as publicly live. - Changes to reviewed public content (including title, body, category, tags, slug, cover,
content_typeorvideo_url) invalidate approval and advancereview_version. Unpublish before editing a published post withacedatacloud_unpublish_blog_post, then submit and obtain fresh peer approval before republishing. Useacedatacloud_withdraw_blog_approvalto revoke approval while unpublished; an approved article returns todraft.
Review threads (authenticated REST)
Review comments are private editorial feedback, not public blog comments. Use the management host and platform Bearer token. Listing requires blog:read; adding, replying and resolving require blog:read plus either blog:write or blog:publish.
- Read the saved draft and
GET /api/v1/blogs/admin/{id}/comments/. The response is a root-thread array with nestedreplies, anchors,review_versionand resolution fields. Compare each thread's version to the current draft; historical feedback does not prove a selection still matches today's text. - After preview and confirmation, add a root comment with
POST /api/v1/blogs/admin/{id}/comments/: send the currentexpected_version, a nonblankbodyof at most 2,000 characters, andfield="overall"with no offsets or quote. For a text selection, usefield="title","summary"or"content", withstart_offset,end_offsetand the exact savedquote. Offsets count UTF-16 code units, not Python characters or UTF-8 bytes; start is inclusive, end exclusive, and the selection spans at most 2,000 code units. Never anchor to unsaved text. On 409, refresh the draft and recompute the selection; on a quote mismatch, reread the saved source rather than guessing offsets. - Reply to a root thread with
POST /api/v1/blogs/admin/{id}/comments/{comment_id}/replies/and{"body":"REVIEW_REPLY"}(1–2,000 nonblank characters). Replies keep the root's review version; replying to a resolved thread reopens it. Use the root ID, not a reply ID. - Resolve or reopen a root thread with
PATCH /api/v1/blogs/admin/{id}/comments/{comment_id}/and{"resolved":true}or{"resolved":false}. Replies and resolution do not requireexpected_version; reread the thread and confirm its ID and intended action before sending. Resolving feedback does not approve or publish the draft; retain the peer-review workflow above.
All blog writes require a preview and explicit user confirmation: without confirm=true, MCP tools only return a dry-run preview. The REST review routes above execute immediately and have no dry-run flag: preview the method, article ID and body locally and obtain confirmation before sending them with the platform Bearer token. Prefer a matching MCP tool if its discovered schema supports the operation; do not invent submission/rejection tool names or CLI flags. Confirm the article, version and publication time before executing; a successful preview is not evidence of a saved, submitted, approved or published post. If the required operation or permission is unavailable, stop and request the appropriate authorization.
Write-operation safety
Creating/deleting keys, creating/paying orders, deleting platform tokens, and publishing announcements or blogs are irreversible, money-related or publicly visible. Always:
- Confirm the exact target (
application_id,order_id,credential_id) with the user before executing. - With the CLI, writes are dry-run unless
--yesis passed. - Never print a full
tokenvalue into shared logs — it grants account access.
Gotchas
- Use the platform token (
ACEDATACLOUD_PLATFORM_TOKEN), not theapi.acedata.cloudservice token — the latter returns 401 here. remaining_amount/used_amount/amountare in Credits, not USD. Convert with a service's package:USD = Credits × (package.price / package.amount).- Newly created credential/platform tokens are returned in full only once — store them immediately.
- Credential "rotate" is delete + recreate; there is no in-place rotate endpoint.
- Announcement endpoints under
/admin/require a superuser token; a normal token gets403.
MCP:
pip install mcp-acedatacloud| Hosted:https://mcp.acedata.cloud/mcp| See all MCP servers. The MCP exposes these as tools (acedatacloud_get_balance,acedatacloud_list_usage,acedatacloud_get_pricing,acedatacloud_search_docs,acedatacloud_get_api_spec,acedatacloud_create_credential, …) with the same write-confirmation guard.