subcent

TypeScript SDK

The @subcent/sdk package provides a typed client for the Subcent API. It handles authentication, request formatting, and error parsing.

Installation#

npm install @subcent/sdk

Configuration#

import Subcent from "@subcent/sdk";

const sc = new Subcent({
  apiKey: "sc_test_your_api_key",
  network: "testnet",           // "mainnet" or "testnet"
  baseUrl: undefined,           // optional override
});

SubcentConfig#

| Field | Type | Required | Description | |---|---|---|---| | apiKey | string | Yes | Your API key (sc_live_, sc_test_, or sc_agent_ prefix) | | network | string | Yes | "mainnet" or "testnet" | | baseUrl | string | No | Override the API base URL. Defaults to https://api.subcent.io/v1 for mainnet, https://testnet-api.subcent.io/v1 for testnet |


Payments#

The payments namespace handles payment requests, approvals, and listing.

payments.request(params)#

Submit a payment request from an agent to a merchant.

const payment = await sc.payments.request({
  agent_id: "agt_550e8400...",
  merchant_id: "mrc_660e8400...",
  amount: "25.00",
  currency: "USDC",       // optional, defaults to "USDC"
  description: "Weekly grocery order",
  category: "groceries",
  metadata: { order_id: "ord_12345" },
});

console.log(payment.payment_id);
console.log(payment.status);   // "completed" | "pending_approval" | "rejected"

Parameters:

| Field | Type | Required | Description | |---|---|---|---| | agent_id | string | Yes | The agent making the payment | | merchant_id | string | Yes | The merchant receiving the payment | | amount | string | Yes | Amount in decimal USDC | | currency | string | No | Defaults to "USDC" | | description | string | No | Human-readable description | | category | string | No | Spending category | | metadata | object | No | Arbitrary key-value data |

payments.get(paymentId)#

Retrieve a specific payment by ID.

const payment = await sc.payments.get("pay_770e8400...");
console.log(payment.status);
console.log(payment.transaction?.tx_hash);

payments.list(params)#

List payments for a vault with optional filters.

const result = await sc.payments.list({
  vault_id: "550e8400...",
  agent_id: "agt_550e8400...",   // optional
  status: "completed",           // optional
  limit: 20,                     // optional, default 50, max 200
  cursor: undefined,             // optional
});

for (const payment of result.payments) {
  console.log(payment.payment_id, payment.amount);
}

if (result.has_more) {
  // Fetch next page
}

payments.approve(paymentId)#

Approve a payment that is pending human approval.

const result = await sc.payments.approve("pay_770e8400...");
console.log(result.status);            // "completed"
console.log(result.transaction.tx_hash);

payments.reject(paymentId, reason?)#

Reject a payment that is pending human approval.

const result = await sc.payments.reject("pay_770e8400...", "Amount too high");
console.log(result.status);  // "rejected"
console.log(result.reason);  // "Amount too high"

Vaults#

The vaults namespace manages on-chain smart-contract wallets.

vaults.create(params)#

Create a new vault on the specified blockchain.

const vault = await sc.vaults.create({
  owner_wallet: "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
  chain: "solana",    // "solana" or "base"
  label: "Production Vault",
});

console.log(vault.vault_id);
console.log(vault.vault_address);
console.log(vault.status);  // "active"

Parameters:

| Field | Type | Required | Description | |---|---|---|---| | owner_wallet | string | Yes | The wallet address that owns this vault | | chain | string | Yes | "solana" or "base" | | label | string | No | Human-readable label |

vaults.get(vaultId)#

Retrieve vault details including balance and status.

const vault = await sc.vaults.get("550e8400...");
console.log(vault.balance);          // "1250.500000"
console.log(vault.status);           // "active" | "frozen"
console.log(vault.authorized_agents);

vaults.freeze(vaultId)#

Freeze a vault to block all transactions immediately.

const result = await sc.vaults.freeze("550e8400...");
console.log(result.status);     // "frozen"
console.log(result.frozen_at);

vaults.unfreeze(vaultId)#

Unfreeze a previously frozen vault.

const result = await sc.vaults.unfreeze("550e8400...");
console.log(result.status);       // "active"
console.log(result.unfrozen_at);

Agents#

The agents namespace handles agent registration and revocation.

agents.create(params)#

Register a new agent authorized to spend from a vault.

const agent = await sc.agents.create({
  vault_id: "550e8400...",
  label: "Shopping Assistant",
  framework: "langchain",
  webhook_url: "https://your-app.com/webhooks/agent",
});

console.log(agent.agent_id);
console.log(agent.api_key);  // "sc_agent_..." -- shown only once!

Parameters:

| Field | Type | Required | Description | |---|---|---|---| | vault_id | string | Yes | The vault to authorize | | label | string | No | Human-readable name | | framework | string | No | AI framework identifier | | webhook_url | string | No | URL for agent-specific events |

x

The api_key is returned only in this response. Store it in a secrets manager immediately.

agents.revoke(agentId)#

Permanently revoke an agent's access.

const result = await sc.agents.revoke("agt_550e8400...");
console.log(result.status);  // "revoked"

Policies#

The policies namespace manages spending policies for agents.

policies.create(params)#

Create a new spending policy bound to an agent and vault.

const policy = await sc.policies.create({
  agent_id: "agt_550e8400...",
  vault_id: "550e8400...",
  budget: {
    max_per_transaction: 100,
    max_daily: 500,
    max_monthly: 5000,
    currency: "USDC",
  },
  categories: {
    mode: "allowlist",
    list: ["groceries", "household"],
  },
  velocity_controls: {
    max_transactions_per_hour: 10,
    max_transactions_per_day: 50,
  },
  approval_rules: [
    {
      condition: "amount > 50",
      action: "request_approval",
      timeout_seconds: 3600,
      fallback: "reject",
    },
  ],
  escrow_rules: {
    require_escrow_above: 200,
    default_escrow_expiry_hours: 48,
  },
});

console.log(policy.policy_id);
console.log(policy.policy_hash);  // "sha256:..."
console.log(policy.version);     // 1

See the Policies API reference for the full schema of all fields.

policies.get(policyId)#

Retrieve a policy with its full configuration.

const policy = await sc.policies.get("pol_880e8400...");
console.log(policy.budget.max_per_transaction);
console.log(policy.version);

policies.update(policyId, params)#

Update an existing policy. Increments the version number.

const updated = await sc.policies.update("pol_880e8400...", {
  budget: {
    max_per_transaction: 200,
    max_daily: 1000,
    currency: "USDC",
  },
});

console.log(updated.version);      // 2
console.log(updated.policy_hash);  // new hash

Merchants#

The merchants namespace handles merchant registration and catalog access.

merchants.register(params)#

Register a new merchant.

const merchant = await sc.merchants.register({
  wallet_address: "8xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
  chain: "solana",
  business_name: "FreshMart Groceries",
  category: "groceries",
  catalog_url: "https://api.freshmart.com/catalog",
  webhook_url: "https://api.freshmart.com/webhooks",
});

console.log(merchant.merchant_id);
console.log(merchant.status);  // "pending_verification"

merchants.getCatalog(merchantId)#

Retrieve a merchant's product catalog.

const catalog = await sc.merchants.getCatalog("mrc_660e8400...");
console.log(catalog.total_items);
for (const item of catalog.items) {
  console.log(item.name, item.price);
}

Escrows#

The escrows namespace handles conditional payment escrows.

escrows.create(params)#

Create a new escrow with conditional release terms.

const escrow = await sc.escrows.create({
  agent_id: "agt_550e8400...",
  merchant_id: "mrc_660e8400...",
  amount: "500.00",
  currency: "USDC",
  condition: {
    type: "delivery_confirmation",
    data: { tracking_number: "1Z999AA10123456784" },
  },
  expires_in_hours: 72,
});

console.log(escrow.escrow_id);
console.log(escrow.status);  // "funded"

Error Handling#

All SDK methods throw AppError when the API returns an error response. The error includes the error code, HTTP status, and a human-readable message.

import { AppError } from "@subcent/sdk";

try {
  await sc.payments.request({
    agent_id: "agt_invalid",
    merchant_id: "mrc_660e8400...",
    amount: "25.00",
  });
} catch (err) {
  if (err instanceof AppError) {
    console.log(err.errorCode);   // "AGENT_NOT_AUTHORIZED"
    console.log(err.httpStatus);  // 403
    console.log(err.message);     // "Agent not found"
    console.log(err.toJSON());
    // { "error": "agent_not_authorized", "message": "Agent not found" }
  }
}

Error Code Reference#

The SDK re-exports AppError from @subcent/shared. It maps API error responses back to typed error codes:

| Error Code | HTTP Status | Typical Cause | |---|---|---| | VAULT_NOT_FOUND | 404 | Invalid vault ID | | VAULT_FROZEN | 403 | Vault is frozen | | INSUFFICIENT_BALANCE | 400 | Not enough USDC in vault | | AGENT_NOT_AUTHORIZED | 403 | Invalid or unauthorized agent | | AGENT_REVOKED | 403 | Agent has been revoked | | POLICY_VIOLATION | 400 | Payment violates spending policy | | POLICY_NOT_FOUND | 404 | No active policy for agent | | MERCHANT_NOT_FOUND | 404 | Invalid merchant ID | | MERCHANT_SUSPENDED | 403 | Merchant is suspended | | RATE_LIMIT_EXCEEDED | 429 | Too many requests | | CHAIN_ERROR | 502 | Blockchain communication error | | INTERNAL_ERROR | 500 | Unexpected server error |