A provider is an HTTPS service on your own server implementing cdnca.provider/v1 to give the control plane a DNS, SMS, mail, ICP lookup or payment channel. The control plane calls you with signed requests; your code never runs inside it.
Generate an Ed25519 response key pair; put the public key (unpadded base64url, 43 characters) in provider_response_key and keep the private key in your service.
Implement /cdnca-provider/v1/{kind}/{action} under base_url: verify first, then the kind’s actions.
In sandbox mode (sandbox=true in the body) answer from test data only and never touch a customer account.
Sign the manifest and upload the version (with at least one platform package). After a customer installs it, the control plane runs the installation contract against your sandbox; the administrator can enable it only when every case passes.
Open now: dns, sms, mail, icp, payment. Notification channels (notify) are coming soon: the manifest may declare it and market review accepts it, but the control plane does not enable notify yet and ignores the declaration; third-party CDN (cdn) is official only.
https, a DNS host name (no IP literal or localhost), default port, optional path, no query, fragment or trailing /
spec.entrypoint.health_path
Starts with /, at most 512 bytes
spec.entrypoint.provider_response_key
Response public key, 43 characters of unpadded base64url; required when providing dns / sms / icp / mail / payment
spec.provides[]
1–8 entries with distinct kinds; protocol_version is "1"; a provider plugin declares at least one, other kinds declare none
spec.permissions
Each kind needs providers.<kind>.provide (e.g. providers.dns.provide); without it the manifest is rejected
spec.packages
Lists every uploaded platform package and its SHA-256 (currently an evaluation rule)
Protocol and interfaces
Every call is POST {base_url}/cdnca-provider/v1/{kind}/{action} with a JSON body carrying "protocol": "cdnca.provider/v1" and "sandbox"; writes carry idempotency_key (a repeat must return the first result and not apply twice). Requests are at most 256 KiB, answers 1 MiB, timeout 15 s. The control plane connects to public addresses only, without proxies or redirects.
Header
Content
X-Cdnca-Provider-Installation
Control plane installation ID
X-Cdnca-Provider-Timestamp
Unix seconds; reject a skew over 300 s
X-Cdnca-Provider-Nonce
32 hex characters; reject any nonce seen before
X-Cdnca-Provider-Signature
Ed25519 signature of the request message by the installation key, unpadded base64url
X-Cdnca-Provider-Response-Signature
(response header) your response key’s signature of the response message
Return HTTP 401 for a bad signature, timestamp or nonce. Otherwise always HTTP 200 with the envelope {"protocol":"cdnca.provider/v1","ok":true,"data":{…}}, or {"ok":false,"error":{"code":"…"}} on failure; code is one of invalid_request, unauthorized, not_found, conflict, zone_not_found, rate_limited, quota_exceeded, unavailable, unsupported, provider_error, invalid_signature, anything else counts as provider_error. The envelope allows no extra fields. The response signature binds the request nonce, so a captured answer cannot be replayed. The customer’s administrator copies the installation public key to you from “Installation signing key” on the plugin detail page.
Record {id, name, type, value, ttl, line}: name is the FQDN without trailing dot, line a unified line name (默认/电信/联通/移动/境外); list by zone (optional name) returns records; create returns id; an unknown zone answers zone_not_found, deleting a missing record not_found
sms
verify, send
{to (E.164), template_id, params, body, idempotency_key} → {delivery_id}; a non-E.164 number answers invalid_request
mail
verify, health, send
{to (one address, no display name), subject, text, html?, reply_to?, idempotency_key} → {message_id}; the sender is your own configuration; health means your upstream account can send now
The plugin only reports payment facts; the control plane binds each fact to its own order number, merchant, integer amount (amount_minor) and currency, and confirms a notification by calling query_payment before crediting. create_checkout returns action (redirect or qr_code) with url / qr_content; verify_callback gets the raw notification bytes the control plane received; reconcile returns one UTC day’s statement; sandbox_settle is sandbox-only and simulates the gateway callback
The signed manifest, 1 byte to 256 KiB; it must name this plugin and namespace, and integrity.key_id must be an active key of the namespace whose signature verifies
configuration_schema
Yes
The configuration JSON Schema file, 1 byte to 64 KiB; its SHA-256 must equal spec.configuration_schema.sha256
changelog_md
No
Release notes in Markdown, at most 65536 bytes
channel
No
stable (default) or beta
auto_publish
No
true / false: publish automatically once approved
package:<platform>
Yes, 1–16
Field names like package:linux-amd64; the platform matches ^[a-z0-9]+-[a-z0-9_]+$ and is unique; each package defaults to at most 64 MiB, as zip, tar.gz or tar
content
Must be absent
An external_https version has no data file
Endpoint: POST /seller/v1/plugins/{id}/versions, Content-Type: multipart/form-data, with exactly one Idempotency-Key header of 1–128 characters; a retry with the same key returns the same result. The body limit defaults to 200 MiB. Any field name other than those in the table and the kind-specific ones returns 400 invalid_upload.
A plugin version number cannot repeat and must be higher than every reviewed version (approved, published, hidden or delisted). After upload the version is “scanning”; the seller site’s version detail shows the scan report.
Review rules
After upload the review worker runs the validating phase (manifest, entrypoint, permissions, the kind’s own checks); any reject stops it before scanning, then the scanning phase runs. Outcome: a reject → changes requested (fix it and press “Resubmit”, at most 10 scans per version); a quarantine → human review; neither → approved directly or sent to human review under the conditions below.
The first version (the plugin has no reviewed version yet)
Permissions differ from the last reviewed version (added or removed)
Privileged kinds: module, engine_module, provider
Sellers who joined less than 90 days ago (market default, adjustable by the operator)
Three scan attempts in a row errored
provider is a privileged kind: every version gets human review
Any one of them sends the version to human review. The full rule table (rule ID, severity, reject or quarantine, evaluation mode) is on the review rules page.
entrypoint.provides_invalid: provides is malformed, repeated or has a bad protocol version (reject); entrypoint.provides_official_only: cdn declared (reject).
package.*: corrupt archive, zip bomb, path traversal, links, over the entry (10000) or unpacked size limits (32 MiB per file, 128 MiB total) and so on (reject; setuid bits quarantine).
secrets.*: private keys or cloud / code-hosting tokens in the package (reject); JWTs and generic credential assignments are in evaluation mode.
malware.detected, dependencies.malicious: malware or a known malicious dependency (reject).
Review accepts notify, but the control plane does not enable it yet (see the note in Quick start).
metadata.version is canonical SemVer (no leading v); every upload must be higher than the reviewed versions, and a version number cannot be uploaded twice.
spec.compatibility.control_api is 1–8 space-separated comparators (>=, <=, =, >, < followed by canonical SemVer), at most 128 characters; all must hold. A control plane that does not satisfy it cannot install or upgrade to the version; an installed plugin keeps its last compatible version.
When a new version adds permissions, the customer’s administrator must confirm them again in the control plane; any version whose permissions change goes to human review.
provides[].protocol_version is "1" (cdnca.provider/v1). The installation contract is cdnca.provider-contract/v1; when it changes the control plane runs the contract again.
Local debugging
libs/entitlement/pluginprovider/fake.go (FakePlugin) in the repository is the reference server: signature, time window, nonce, idempotency, signed answers and sandbox behaviour are all there. This sketch is adapted from it:
The cases the control plane runs at install time (RunContract, contract.go): every kind has verify, rejects_bad_signature, rejects_tampered_body, rejects_stale_timestamp, rejects_replayed_nonce; dns adds create_a, list_created, create_idempotent, update_a, delete_a, delete_missing_not_found, txt_line, unknown_zone; sms send, send_idempotent, invalid_number; mail health, send, send_idempotent, invalid_recipient; icp filed, unfiled, invalid_domain; payment create_checkout, query_unpaid, sandbox_settle, query_paid, verify_callback, rejects_tampered_callback, refund_idempotent, refund_over_limit, reconcile, unknown_order. Locally, follow newHarness in pluginprovider_test.go: point pluginprovider.NewClient at your service and call pluginprovider.RunContract to get the same result a customer’s control plane will.