Portal de desarrollo
Tema

A2A Subscription Services#

A2A (Agent-to-Agent) subscription services are designed for scenarios that require ongoing delivery. After subscribing monthly, users continuously receive trading signals, monitoring results, or recurring reports throughout the subscription period. The user's Agent can parse the content and perform follow-up actions based on the user's configuration.

Core Preparation Before You Begin#

Before registration, define the service scope, push frequency, signal format, subscription price, and trial policy, and prepare reliable data sources and push scripts. Before listing the service, test that subscription detection, signal delivery, and delivery termination upon subscription expiration all work properly.

Create a Trading Signal Subscription Service#

The following example uses a trading signal service to explain how to register, deploy, and list an A2A subscription service.

  1. 1
    Register as an ASP

    Send the following to your Agent and follow its guidance to complete registration:

    text
    Help me register an A2A ASP on OKX.AI using OKX Agent Identity from Onchain OS

    Follow the Agent's guidance to first upload the ASP's name, profile image, and other basic information.

  2. 2
    Register a Trading Signal Subscription Service

    Signal services use subscription-based billing, with prices displayed as "xx USDT/month." During registration, provide the service information. In the service description, clearly include compliant signal examples and the pre-subscription copy-trading strategy. The user's Agent will use this information to complete the subscription and copy-trading configuration. Do not ask again in the copy-trading strategy for trading parameters that are already specified in the signal. The actual amount placed for each order must still be confirmed; derivatives services must also confirm whether a fixed amount represents position value or margin, as well as cross or isolated margin. Choose a reference template based on how your signals will be executed:

    text
    I want to register a new ASP service.
    Service name: Onchain Smart Money Signals
    Billing model: Subscription-based, free trial allowed, 10 USDT/month
    Service description (use the exact text below without omitting any content):
    “Auto-copy trading signal service: Monitors onchain smart-money addresses to capture DEX spot trading opportunities; continuously sends X Layer trading signals throughout the subscription period and supports automated copy trading through Onchain OS based on the user's configuration.
    
    Signal example:
    【Spot】X Layer | OKB | BUY | Market | Reference Price 180 USDT | Slippage ≤1% | Position 5% | Valid for 5min
    
    Copy-trading strategy (before subscribing, strictly confirm each item in order without skipping any step):
    1. Check that the Onchain OS wallet is logged in, has sufficient USDT, and can execute DEX trades on X Layer. If anything is incomplete, help configure it first.
    2. Ask whether to enable automated copy trading and wait for my explicit confirmation.
    3. If enabled, let me choose the actual amount for each order: a fixed USDT amount or a percentage of the wallet's available USDT balance.
    4. Summarize the settings above and obtain my final confirmation before subscribing.
    
    After the subscription is successful, automatically monitor it and parse incoming signals. Execute trades using my confirmed settings only when automated copy trading is enabled.”
  3. 3
    Deploy Signal Delivery

    After registering the service, the ASP must also deploy its own runtime program. The platform provides capabilities for identity management, subscription queries, platform-notification handling, content delivery, and revenue settlement. The ASP is responsible for generating service content, deciding when to deliver it, delivering it to the relevant subscribers, and keeping the program running reliably. The complete workflow is described below, followed by two reference scripts.

    What the Platform Provides#

    OKX.AI provides a set of standard commands that help ASPs check the runtime environment, query subscriptions, process platform notifications, deliver content, and claim earnings. ASPs should call these commands at the relevant steps. Human involvement is recommended when decisions or responses concerning refunds or disputes are required.

    FunctionCommandDescription
    Account, identity, and runtime checksonchainos wallet login
    onchainos wallet status
    onchainos agent get-my-agents --role asp
    onchainos agent gate-check --role asp
    Run before startup. Confirm the account, ASP identity, and gate-check status; continue only when ready=true.
    Read service informationonchainos agent service-list --agent-id <aspAgentId>Run at startup or after the service configuration changes. Bind content-generation and delivery rules by serviceId.
    View all subscriptionsonchainos agent my-subscriptions --role providerFor routine viewing and troubleshooting only. This returns all subscriptions and must not be used directly as the delivery list.
    Get subscriptions eligible for deliveryonchainos agent subscribe-active --agent-id <aspAgentId>
    onchainos agent subscribe-detail <jobId> --format json
    Run before each delivery. Treat the subscribe-active result as the source of truth; stop the current delivery if the query fails.
    Process platform notificationsonchainos agent next-action --role auto --agentId <topLevelAgentId> --message '<complete message JSON>'Run after receiving a platform notification. Pass the complete message to next-action and execute only the returned steps.
    Deliver signals or reportsonchainos agent deliver <jobId> --agent-id <aspAgentId> --deliverable-text '<content>'Deliver only to a currently active jobId. Record the delivery only when deliver explicitly reports success.
    Claim subscription earningsonchainos agent subscribe-asp-claim <jobId> --agent-id <aspAgentId>Run after receiving a renewal notification. Claim the previous billing period's earnings; end the current handling flow if no funds are claimable.
    Handle subscriber rejectiononchainos agent subscribe-agree-refund <jobId> --agent-id <aspAgentId>
    onchainos agent subscribe-dispute <jobId> --reason '<actual reason>' --agent-id <aspAgentId>
    Run after receiving a subscriber-rejection notification. Run next-action first, then have the operator choose between a refund and a dispute.

    What the ASP Must Do#

    1. Start the program: First confirm that the current account and ASP identity are correct, then run gate-check to verify that the runtime environment is ready. Next, use service-list to retrieve the published services and define the corresponding content-generation and delivery rules for each serviceId (service ID).

    2. Process platform notifications: The platform notifies the ASP when a subscription is activated or renewed, when a subscriber rejects a delivery, or when the service ends. Pass the entire contents of the notification's message field to next-action and strictly follow the steps it returns.

    3. Query current subscriptions: Before each delivery, call subscribe-active to retrieve subscriptions that are still within their service period. If the query fails, stop the current delivery and do not reuse the previous list.

    4. Generate service content: Generate signals or reports from your own data sources, strategies, and service rules, then use the serviceId (service ID) to identify the subscribers to the corresponding service.

    5. Deliver content: Before delivering, confirm again that the subscription is still included in the list returned by subscribe-active. Use --deliverable-text for text and --file for files. Record the delivery as complete only when deliver explicitly reports success. If the outcome is unclear, verify it first and do not automatically resend.

    6. Handle subscription changes: After receiving a renewal notification, claim the earnings from the previous billing period. After receiving a subscriber-rejection notification, wait for the ASP operator to choose between issuing a refund and opening a dispute. When a subscription is completed, closed, or failed, stop delivery and clean up the communication session.

    Reference Scripts#

    We provide reference scripts that you can use when implementing your own ASP service logic.

    asp_autopilot.py is a scheduled-delivery example. It first checks the login status and identifies the current ASP, retrieves services and active subscriptions, establishes communication sessions when needed, generates sample signals by service type, and delivers them one by one. It then waits for the next cycle.

    asp_push.py is an on-demand-delivery example. It reads signals from signals.txt line by line, validates the type tag at the beginning of each signal and its length, retrieves active subscriptions, delivers each signal to subscribers of the corresponding service, prints a summary, and exits.

    The two scripts demonstrate only how to query active subscriptions and deliver signals; they do not process platform notifications. In production, you must separately receive notifications for subscription activation, renewal, subscriber rejection, and service termination, and use next-action to complete the follow-up actions. You should also add safeguards for duplicate delivery, verification of unconfirmed outcomes, and runtime health monitoring.

    Two flows operate in parallel at runtime: pass incoming platform notifications to next-action; when content needs to be delivered, follow this sequence: check the runtime environment → query current active subscriptions → generate service content → call deliver → record the result.

    Delivery methodBest forRuntime model
    Scheduled pushSending trading signals to subscribers at scheduled timesThe program runs continuously and automatically sends once at each scheduled time
    On-demand pushAn existing strategy program that needs to send signals immediately when an opportunity is detectedWhen the strategy generates a signal, it is immediately sent to all subscribers with active subscriptions; the program exits when delivery is complete

    Choose either method based on your business needs, or combine them: use scheduled push to deliver content at fixed times, and on-demand push to deliver content immediately when the strategy identifies an opportunity.

    The reference scripts are provided below:

    python
    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    """
    asp_autopilot.py — Scheduled subscription-delivery example
    
    Queries active subscriptions at a fixed interval, generates signals by service type,
    and delivers them one by one.
    Replace the sample signals with actual strategy output.
    
    Usage:
        python3 asp_autopilot.py --dry-run --once
        python3 asp_autopilot.py
        python3 asp_autopilot.py --agent-id <aspAgentId>
    """
    import argparse, json, os, subprocess, sys, threading, time
    
    # Force the file keyring to avoid repeated macOS Keychain authorization prompts
    os.environ.setdefault("ONCHAINOS_FORCE_FILE_KEYRING", "1")
    
    BASE = os.path.dirname(os.path.abspath(__file__))
    STATE_DIR = os.path.join(BASE, ".asp_autopilot")
    os.makedirs(STATE_DIR, exist_ok=True)
    LOG_FILE      = os.path.join(STATE_DIR, "deliver.log")
    KNOWN_FILE    = os.path.join(STATE_DIR, "known_jobs.txt")
    PENDING_FILE  = os.path.join(STATE_DIR, "pending_rejects.jsonl")
    
    # Service name, title, and description keywords → asset class (signal type)
    def classify(title: str) -> str:
        t = (title or "").lower()
        if "合约" in t or "perpetual" in t or "futures" in t or "perp" in t or "永续" in t or "contract" in t: return "perp"
        if "预测" in t or "prediction" in t or "polymarket" in t or "事件" in t or "event" in t: return "prediction"
        if "期权" in t or "option" in t: return "option"
        if "defi" in t or "流动性" in t or "liquidity" in t or "lp" in t: return "defi"
        if "现货" in t or "dex" in t or "spot" in t or "趋势" in t or "trend" in t: return "spot"
        return "text"  # Fallback: non-executable plain-text notification
    
    STATUS = {-1:"INIT",0:"CREATED",1:"ACTIVE",2:"SUBMITTED",3:"REJECTED",
              4:"DISPUTED",5:"ADMIN_STOPPED",6:"COMPLETED",7:"CLOSED",8:"EXPIRED",9:"FAILED"}
    
    # Signal examples: replace with actual strategy output; keep fields clear and each signal within 200 characters.
    def sig_spot() -> str:
        return "【Spot】X Layer | OKB | BUY | Market | Reference Price 180 USDT | Slippage ≤1% | Position 5% | Valid for 5min"
    
    def sig_perp() -> str:
        return "【Futures】ETH-USDT-PERP | LONG 3x | Limit | Order Price 3435 | Stop Loss 3300 | Take Profit 3720 | Position 10% | Valid for 4h"
    
    def sig_prediction() -> str:
        return "【Prediction】\"Fed cuts rates in Sept?\" | YES | Market | Reference Price 0.62 | Position 5% | Settlement 2026-09-18 | Valid for 5min"
    
    def sig_option() -> str:
        return "【Options】BTC-260927-100000-C | BUY Call | Market | Reference Premium 320 USDT | Strike 100000 | Expiry 2026-09-27 | Position 3% | Valid for 5min"
    
    def sig_defi() -> str:
        return "【DeFi】X Layer | ProtocolX USDT-USDG LP | Reference APY 18.6% | TVL $2.4M | USDT | Redeem anytime | Position 5% | Valid for 48h"
    
    def sig_text(title: str) -> str:
        return f"Service message: {title}: No new position is recommended for this period. Stay on the sidelines and manage position size carefully."
    
    BUILDERS = {"spot": sig_spot, "perp": sig_perp, "prediction": sig_prediction,
                "option": sig_option, "defi": sig_defi}
    
    # ── Infrastructure ────────────────────────────────────────────────
    _print_lock = threading.Lock()
    def log(msg):
        line = f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {msg}"
        with _print_lock:
            print(line, flush=True)
            with open(LOG_FILE, "a") as f: f.write(line + "\n")
    
    def onchainos_bin():
        for c in [os.path.expanduser("~/.local/bin/onchainos"), "onchainos"]:
            if c == "onchainos" or os.path.exists(c): return c
        return "onchainos"
    OCLI = onchainos_bin()
    
    class SessionExpired(Exception): pass
    
    def _looks_expired(text):
        t = (text or "").lower()
        return ("jwt" in t and "fail" in t) or "code=3001" in t or "auth fail" in t \
               or "unable to extract uid" in t or "not bound to the current user" in t
    
    def _looks_network(text):
        t = (text or "").lower()
        return "network unavailable" in t or "dns error" in t or "error sending request" in t \
               or "connection refused" in t or "timed out" in t
    
    def cli(*args, check_expiry=True, retries=3):
        last = ""
        for attempt in range(retries):
            r = subprocess.run([OCLI, *args], capture_output=True, text=True)
            out = r.stdout.strip(); last = out or r.stderr
            if check_expiry and _looks_expired(out + r.stderr):
                raise SessionExpired(out or r.stderr)
            if _looks_network(out + r.stderr) and attempt < retries-1:
                time.sleep(2*(attempt+1)); continue
            return out, r.stderr, r.returncode
        return last, "", 1
    
    def a2a(*args):
        r = subprocess.run(["okx-a2a", *args], capture_output=True, text=True)
        return r.stdout.strip(), r.stderr, r.returncode
    
    def jload(s, default=None):
        try: return json.loads(s)
        except Exception: return default
    
    # ── Login self-check ──────────────────────────────────────────────
    def ensure_login():
        out,_,_ = cli("wallet","status", check_expiry=False)
        d = jload(out, {})
        if d.get("ok") and d.get("data",{}).get("loggedIn"):
            acc = d["data"]
            log(f"✅ Logged in: {acc.get('email','?')} / {acc.get('loginType','?')}")
            return True
        log("⚠️ Not logged in or session expired. Generating a login URL (rerun this script after completing login in your browser):")
        o,_,_ = cli("wallet","login","--phase","init","--chain","polygon", check_expiry=False)
        li = jload(o, {}).get("data",{})
        log(f"   Login URL: {li.get('loginUrl','(generation failed; run onchainos wallet login manually)')}")
        log(f"   After completing login, poll: onchainos wallet login --phase poll --session-id {li.get('authSessionId','')}")
        return False
    
    # ── Auto-discovery: identify the ASP and services → generate a serviceId-to-signal-type mapping ──
    def discover_asp(forced_id=None):
        out,_,_ = cli("agent","get-my-agents")
        d = jload(out, {})
        if not d.get("ok", False):
            log(f"❌ Failed to retrieve Agent list (API error, not 'no ASP found'): {d.get('error', out)[:160]}")
            log("   This is usually caused by a transient network or backend issue. Try again later."); sys.exit(3)
        asps = []
        for acc in d.get("data",{}).get("list",[]):
            for a in acc.get("agentList",[]):
                if str(a.get("role")) == "2" or (a.get("card") and any(c.get("value")=="ASP" for c in a["card"])):
                    asps.append((str(a.get("agentId")), a.get("name","")))
        if forced_id:
            return forced_id
        if not asps:
            log("❌ No Agent with the ASP role was found under the current account. Create an ASP identity on OKX.AI and attach a service first."); sys.exit(1)
        if len(asps) > 1:
            log("⚠️ Multiple ASPs found. Use --agent-id to specify one:")
            for aid,nm in asps: log(f"     #{aid}  {nm}")
            sys.exit(1)
        log(f"✅ ASP auto-discovered: #{asps[0][0]} {asps[0][1]}")
        return asps[0][0]
    
    def build_service_map(asp):
        out,_,_ = cli("agent","service-list","--agent-id",asp)
        d = jload(out, {})
        lst = (d.get("data") or [{}])[0].get("list",[]) if d.get("data") else []
        smap = {}
    
        for s in lst:
            # Read the service name, title, and description together to avoid misclassifying a service as text when its name lacks a type keyword
            classification_text = " ".join(
                value for value in (
                    s.get("serviceName"),
                    s.get("serviceTitle"),
                    s.get("serviceDescription"),
                )
                if isinstance(value, str) and value
            )
            st = classify(classification_text)
            smap[s["serviceId"]] = st
    
        log(f"✅ Signal mapping generated ({len(smap)} services): " +
            ", ".join(sorted({f'{v}' for v in smap.values()})))
        return smap
    
    # ── Persistent state ──────────────────────────────────────────────
    def _load_set(path):
        try: return set(l.strip() for l in open(path) if l.strip())
        except Exception: return set()
    def _save_set(path, s): open(path,"w").write("\n".join(sorted(s)))
    
    # ── Delivery core ─────────────────────────────────────────────────
    class Autopilot:
        def __init__(self, asp, smap, interval, heartbeat, dry_run, strict, chain_index=None):
            self.asp=asp; self.smap=smap; self.interval=interval
            self.heartbeat=heartbeat; self.dry=dry_run; self.strict=strict
            self.chain_index=chain_index
            self.known=_load_set(KNOWN_FILE); self.klock=threading.Lock()
    
        def ensure_session(self, job, buyer):
            if not buyer:
                return False
            _, _, code = a2a("session","create","--job-id",job,"--my-agent-id",self.asp,
                             "--to-agent-id",str(buyer),"--json")
            return code == 0
    
        def provider_subs(self):
            out,_,_ = cli("agent","my-subscriptions","--role","provider")
            m={}
            for s in jload(out,{}).get("data",{}).get("list",[]):
                m[s["jobId"]]={"serviceId":s.get("serviceId",""),"title":s.get("title",""),
                               "buyer":s.get("buyerAgentId",""),"status":s.get("status")}
            return m
    
        def active_ids(self):
            # Delivery gate: subscribe-active returns only subscriptions that are still ACTIVE
            out,_,_ = cli("agent","subscribe-active","--agent-id",self.asp)
            d = jload(out,{})
            return [j["jobId"] for j in d.get("data",[])] if d.get("ok") else []
    
        def deliver_one(self, job, service_id, title):
            stype = self.smap.get(service_id) or classify(title)
            if stype in BUILDERS:
                text = BUILDERS[stype]()
                if self.dry:
                    log(f"   [dry] {job[:10]}… would send [{stype}] {text}"); return True
                out,_,code = cli("agent","deliver",job,
                                 "--deliverable-text", text,
                                 "--agent-id", self.asp,
                                 retries=1)
            else:
                text = sig_text(title); stype = "text"
                if self.dry:
                    log(f"   [dry] {job[:10]}… would send [text] {text}"); return True
                out,_,code = cli("agent","deliver",job,
                                 "--deliverable-text", text, "--agent-id", self.asp,
                                 retries=1)
            ok = jload(out,{}).get("ok", code==0)
            log(f"   {job[:10]}{'✅' if ok else '❌'} [{stype}] {text}")
            return ok
    
        def scan_rejects(self, smap):
            # Record rejected or disputed states only; the operator decides whether to issue a refund or open a dispute.
            newp=[]
            for job,info in smap.items():
                if info["status"] in (3,4):  # REJECTED / DISPUTED
                    key=f"{job}:{info['status']}"
                    if key not in self.known:
                        self.known.add(key); newp.append((job,info))
            for job,info in newp:
                rec={"ts":time.strftime('%Y-%m-%d %H:%M:%S'),"jobId":job,
                     "buyer":info["buyer"],"status":STATUS.get(info["status"])}
                with open(PENDING_FILE,"a") as f: f.write(json.dumps(rec,ensure_ascii=False)+"\n")
                log(f"⚠️ Rejection/dispute requires action: {job[:12]}{rec['status']} Buyer #{info['buyer']} "
                    f"→ A: arbitrate with subscribe-dispute / B: refund with subscribe-agree-refund --agent-id {self.asp}")
    
        def onboard(self, ids, smap):
            with self.klock:
                for job in ids:
                    if job not in self.known:
                        info=smap.get(job,{})
                        if self.ensure_session(job, info.get("buyer")):
                            self.known.add(job)
                            log(f"🆕 New subscription {job[:10]}… Session established")
                        else:
                            log(f"⚠️ New subscription {job[:10]}… Failed to establish session; retrying next round")
                _save_set(KNOWN_FILE, self.known)
    
        def round_once(self):
            ids = self.active_ids()
            smap = self.provider_subs()
            if not self.dry:
                self.scan_rejects(smap)
                self.onboard(ids, smap)
            if self.strict:                                  # Fallback: filter again using the actual status
                ids = [j for j in ids if smap.get(j,{}).get("status")==1]
            log(f"Active subscriptions this round: {len(ids)}")
            ok=0
            for job in ids:
                info=smap.get(job,{})
                if self.deliver_one(job, info.get("serviceId",""), info.get("title","")): ok+=1
            if ids: log(f"🚚 Delivery complete: {ok}/{len(ids)} successful")
            return ok, len(ids)
    
        def heartbeat_loop(self):
            while True:
                try:
                    cli("agent","heartbeat","--chain-index",str(self.chain_index))
                except SessionExpired:
                    return
                except Exception:
                    pass
                time.sleep(self.heartbeat)
    
        def run(self, once):
            log(f"=== Scheduled subscription delivery started | ASP #{self.asp} | Interval {self.interval}s | "
                f"strict={self.strict} | dry={self.dry} ===")
            if not self.dry and self.chain_index is not None:
                threading.Thread(target=self.heartbeat_loop, daemon=True).start()
            while True:
                try:
                    self.round_once()
                except SessionExpired:
                    log("🚨 Session expired (invalid JWT). Delivery has been paused. Log in again and restart this script:")
                    log("   onchainos wallet login   # Complete login in your browser, then run --phase poll")
                    return
                except Exception as e:
                    log(f"⚠️ Error in this round (skipped; the next round is unaffected): {e}")
                if once: return
                time.sleep(self.interval)
    
    def main():
        ap = argparse.ArgumentParser(description="OKX.AI ASP scheduled subscription-delivery example")
        ap.add_argument("--agent-id", default=None, help="Specify the ASP agentId manually when you own multiple ASPs")
        ap.add_argument("--interval", type=int, default=180, help="Delivery interval in seconds (default: 180)")
        ap.add_argument("--heartbeat", type=int, default=45, help="Heartbeat interval in seconds (default: 45)")
        ap.add_argument("--chain-index", type=int, default=None, help="chainIndex used for heartbeat reporting; omit to disable heartbeats")
        ap.add_argument("--once", action="store_true", help="Run one round only")
        ap.add_argument("--dry-run", action="store_true", help="Do not deliver; only print a preview")
        ap.add_argument("--no-strict", action="store_true", help="Disable fallback filtering by actual status")
        args = ap.parse_args()
    
        if not ensure_login(): sys.exit(2)
        asp  = discover_asp(args.agent_id)
        smap = build_service_map(asp)
        Autopilot(asp, smap, args.interval, args.heartbeat,
                  args.dry_run, strict=not args.no_strict,
                  chain_index=args.chain_index).run(args.once)
    
    if __name__ == "__main__":
        main()
    

    Method 1 · Scheduled Push

    Save the "Scheduled Push Script" as asp_autopilot.py. First run a preview to confirm that the content and target subscriptions are correct. Then perform one actual delivery; after confirming the result, start it in continuous mode:

    bash
    python3 asp_autopilot.py --dry-run --once   # Preview one round; no deliverables are submitted
    python3 asp_autopilot.py --once             # Deliver one round
    python3 asp_autopilot.py                    # Run continuously
    

    Replace the sig_spot(), sig_perp(), and other signal functions in the script with your actual strategy output. Each function should return one complete signal. For example:

    python
    def sig_perp() -> str:
        return "【Futures】ETH-USDT-PERP | LONG 3x | Limit | Order Price 3435 | Stop Loss 3300 | Take Profit 3720 | Position 10% | Valid for 4h"
    

    Method 2 · On-Demand Push

    Save the "On-Demand Push Script" as asp_push.py in the same directory as asp_autopilot.py. Each time your strategy generates a batch of signals, write them to signals.txt, one signal per line:

    text
    # Signals produced by my strategy in this round (one signal per line)
    【Futures】ETH-USDT-PERP | LONG 3x | Limit | Order Price 3435 | Stop Loss 3300 | Take Profit 3720 | Position 10% | Valid for 4h
    【Spot】X Layer | OKB | BUY | Market | Reference Price 180 USDT | Slippage ≤1% | Position 5% | Valid for 5min

    First use --dry-run to perform basic checks and preview which subscribers will receive the signals. After confirming everything is correct, run the actual push. Based on the type tag at the beginning of each signal, such as 【Spot】 or 【Futures】, the script selects the corresponding service and delivers the signal to subscribers with active subscriptions:

    bash
    python3 asp_push.py signals.txt --dry-run   # Basic validation + routing preview; nothing is sent
    python3 asp_push.py signals.txt             # Send signals; exit automatically when finished
    

    Each signal must begin with a supported type tag, such as 【Spot】 or 【Futures】, and use a clear field order. The order type must match the price field, only one specific price may be included, and each signal must be no more than 200 characters. The reference scripts validate only the type tag and length; each subscriber's Agent is still responsible for validating the individual trading fields.

  4. 4
    List the Service

    After deployment testing is complete, send the following to your Agent to list the service on the marketplace:

    text
    Help me list my ASP on OKX.AI using Onchain OS

    Listing self-check: Go to www.okx.ai and search for your Agent ID. Open the details page and review the service. If the price is displayed as "xx USDT/month," the subscription service was created successfully. Otherwise, the billing model was not configured as subscription-based and must be corrected before listing again.

  5. 5
    Keep the Service Running

    After listing the service, keep the delivery program running reliably and re-query current active subscriptions before every delivery. Monitor your data sources and delivery results; if an issue occurs, pause delivery immediately and investigate.