Concordex API · v1

Agentic PLT payments & swaps, by API.

JSON in, JSON out. Idempotency keys on every mutation, webhook callbacks on every state transition. Pay in any PLT, receive in any PLT — cross-PLT settlement is a one-call primitive. Built for non-human callers — agents, billing engines, treasury bots, settlement services.

Introduction

The Concordex API lets your software send and swap Protocol-Level Tokens on Concordium through a small set of HTTPS endpoints. Pay in any PLT, receive in any PLT — cross-PLT settlement is a one-call primitive, not a two-leg dance. Your project's wallet keys live inside a hardened vault; your code authenticates with a scoped API key and never touches the chain directly.

Base URL

https://api.concordex.app

Versioning — current major version is v1, included in the URL path. Breaking changes ship as a new major version; previous version supported for ≥12 months past deprecation.

Authentication

Every request must include a project API key as a bearer token:

Authorization: Bearer sk_live_a1b2c3d4e5f6g7h8

Keys are scoped to a single project and to a capability set (payments:write, payments:read, balances:read, keys:admin). Today the same base URL serves Concordex devnet (our P10 Concordium chain) for both sk_test_… and sk_live_… keys; mainnet routing flips on once the PLT-lock primitive lands on chain.

Quickstart

The minimum viable integration is one HTTP call:

curl https://api.concordex.app/v1/payments \
  -H "Authorization: Bearer sk_live_a1b2c3d4e5f6g7h8" \
  -H "Idempotency-Key: pay_2026_05_11_invoice_4831" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "5.00",
    "token": "CCDX",
    "to": "agent_4f2a91…",
    "memo": "Invoice 4831",
    "metadata": { "customer_id": "cust_8821" }
  }'

Response (HTTP 201):

{
  "id": "pay_01HV5K3Q8R7TQ9F2WJZX1Y4M9C",
  "object": "payment",
  "status": "submitted",
  "amount": "5.00",
  "token": "CCDX",
  "to": "agent_4f2a91…",
  "tx_hash": "f8a4c6d2…",
  "created_at": "2026-05-11T14:22:31Z",
  "finalised_at": null
}

Errors

Conventional HTTP status codes. Error bodies are JSON:

{
  "error": {
    "type": "policy_violation",
    "code": "daily_cap_exceeded",
    "message": "This payment would exceed the project daily cap of 10,000 CCD.",
    "param": "amount",
    "request_id": "req_01HV5K3QX9F7T2WJZ"
  }
}
StatusTypeMeaning
400invalid_requestMalformed JSON, missing field, bad parameter shape.
401authentication_errorMissing, invalid, or revoked API key.
403policy_violationValid request blocked by your project policy (cap, allow-list, etc.).
409idempotency_conflictSame idempotency key used for a different request body.
429rate_limitedToo many requests on this key. Retry with backoff.
503chain_unavailableConcordium node temporarily unreachable. Safe to retry.

Create a payment

POST/v1/payments

Parameters

FieldTypeReq?Description
amountstringyesDecimal string, e.g. "5.00".
tokenstringyesPLT symbol (e.g. "CCDX") or PLT identifier. Use "CCD" for the chain's native token (CCD is not itself a PLT — it's the universal counter-asset).
tostringyesConcordium account address of the recipient.
memostringnoOptional on-chain memo, ≤256 bytes UTF-8.
metadataobjectnoOff-chain key/value pairs, returned on retrieve and in webhooks.
require_approvalboolnoIf true, payment enters pending_approval until a second key confirms.

Python example

import os, requests

resp = requests.post(
    "https://api.concordex.app/v1/payments",
    headers={
        "Authorization": f"Bearer {os.environ['CONCORDEX_KEY']}",
        "Idempotency-Key": "pay_2026_05_11_invoice_4831",
    },
    json={
        "amount": "5.00",
        "token": "CCDX",
        "to": "agent_4f2a91…",
        "metadata": {"customer_id": "cust_8821"},
    },
    timeout=10,
)
resp.raise_for_status()
payment = resp.json()
print(payment["id"], payment["status"], payment["tx_hash"])

Cross-token payments (pay_with)

Set pay_with to the token you want to debit when it differs from the token the receiver should land in. Concordex quotes the matching pool, executes the swap, and settles atomically. Today the orchestrator mediates settlement through a Concordex treasury account on devnet; once PLT-locks land on chain, the same call routes through LP locks without Concordex touching principal. Slippage and allowed-pair policy apply.

POST /v1/payments
{
  "pay_with":      "CCDX",       // debit your wallet in this token
  "to":            "agent_4f2a91…",
  "amount":        "42.00",       // amount the receiver lands with
  "token":         "EURX",       // receiver's token (any live PLT works)
  "max_slippage":  "0.30%",       // optional, defaults to project policy
  "idempotency_key": "pay_2026_05_11_0001"
}

Response includes swap_quote_id and the effective rate alongside the usual payment fields. The payment is rejected (no debit, no settle) if the quote would breach the slippage cap.

Retrieve a payment

GET/v1/payments/{id}

Returns the current payment row. status evolves through:

  • pending_approval — created with require_approval=true.
  • submitted — signed by the vault, sent to Concordium.
  • finalised — finality reached on-chain. finalised_at + fee populated.
  • rejected — chain rejected the transaction. Terminal.
  • failed — policy violation discovered after submission. Terminal.

List payments

GET/v1/payments

Cursor-paginated, newest first. Query: limit (default 20, max 100), starting_after, status, token, created_gte, created_lte.

Balances

GET/v1/balances
{
  "object": "list",
  "data": [
    { "token": "CCD",  "balance": "12450.75", "decimals": 6 },
    { "token": "CCDX", "balance": "8200.00",  "decimals": 6 }
  ]
}

Quote a swap

POST/v1/swaps/quote

Returns an indicative rate + a short-lived quote_id you can pass to POST /v1/swaps. No funds move. Quotes expire after 30 seconds. Direct pairs are PLT↔CCD; cross-PLT trades return a multi-hop route.

curl https://api.concordex.app/v1/swaps/quote \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "from":      "EURX",
    "to":        "CCDX",
    "amount_in": "1000"
  }'
{
  "quote_id":     "quo_01HV5P9F2C…",
  "from":         "EURX",
  "to":           "CCDX",
  "amount_in":    "1000.00",
  "amount_out":   "2714.55",
  "rate":         "2.71455",
  "route":        ["EURX/CCD", "CCD/CCDX"],
  "expires_at":   "2026-05-11T14:23:01Z"
}

Execute a swap

POST/v1/swaps

Atomic swap from the project wallet. Pairs are PLT↔CCD; cross-PLT trades route through CCD in a single atomic call. Either pass a fresh quote_id for guaranteed rate (within slippage), or pass max_slippage and let Concordex quote-then-execute under the cap.

curl https://api.concordex.app/v1/swaps \
  -H "Authorization: Bearer sk_live_…" \
  -H "Idempotency-Key: swap_2026_05_11_treasury_0001" \
  -H "Content-Type: application/json" \
  -d '{
    "from":         "EURX",
    "to":           "CCDX",
    "amount_in":    "1000",
    "max_slippage": "0.30%"
  }'
{
  "id":         "swp_01HV5PA7K3…",
  "status":     "finalised",
  "from":       "EURX",
  "to":         "CCDX",
  "amount_in":  "1000.00",
  "amount_out": "2713.34",
  "rate":       "2.71334",
  "fee":        { "token": "CCD", "amount": "0.0042" },
  "tx_hash":    "f8a4c6d2…",
  "finalised_at": "2026-05-11T14:22:46Z"
}

Swap parameters

FieldTypeReq?Description
fromstringyesSource token — a PLT symbol/identifier, or "CCD" for the native counter-asset.
tostringyesDestination token — a PLT symbol/identifier, or "CCD". Direct pairs are PLT↔CCD; cross-PLT trades route through CCD automatically.
amount_instringone ofDecimal amount to debit. Mutually exclusive with amount_out.
amount_outstringone ofDecimal amount the project should receive. Concordex computes the required amount_in.
max_slippagestringnoCap as a percentage, e.g. "0.30%". Defaults to project policy.
quote_idstringnoPre-fetched quote from POST /v1/swaps/quote. If absent, Concordex quotes inline.
recipientstringnoOptional — land the destination PLT on a different Concordium account (useful for pay-and-swap flows).
Liquidity model

Today swaps run against per-pair pools mediated by a Concordex treasury account on devnet (constant-product AMM, 30 bps LP fee). Reserves and LP positions live in the Concordex ledger; each swap settles as two on-chain PLT transfers. When Concordium ships the PLT-lock primitive in protocol, the same API call settles directly against LP locks: LPs delegate scoped, capped amounts of their PLTs with per-swap caps, per-period caps and expiry, and Concordex only holds release-signing authority — principal stays with the LP. The API surface, schema and pricing engine are unchanged through that migration; only the three settlement helpers get rewired.

CCDX staking

Every swap pays a 10 bps protocol fee on top of the LP fee. The fee accumulator is swept periodically, converted to CCD via the per-token pools, and split 45% to stakers, 45% to a 365-day lock wallet, 10% to the protocol treasury. Stakers earn CCDX pro-rata, distributed via a Synthetix-style accumulator (O(1) per sweep). Live state + APR: /stake.

GET/v1/stake

The caller's CCDX position — staked principal + pending rewards (accrued via reward-per-share since last touch).

POST/v1/stake

Stake N CCDX. The vault signs the transfer caller → staking pool; the API updates the accumulator atomically.

POST /v1/stake
{
  "amount": "100"
}
POST/v1/stake/withdraw

Withdraw N CCDX from your principal. Pending rewards stay accrued — claim separately.

POST/v1/stake/claim

Pay out accrued CCDX rewards to your wallet. Zero on first claim or right after a previous claim.

GET/v1/stake/stats

Global rewards state — total staked, accumulator, 7-day rolling APR estimate.

GET/v1/protocol/fees

Unswept per-token fee accumulator + last completed sweep epoch + lock-wallet totals.

API keys

POST/v1/projects/keys

The full secret is returned exactly once. Store it on your side immediately.

curl https://api.concordex.app/v1/projects/keys \
  -H "Authorization: Bearer sk_live_admin_…" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "billing-worker",
    "capabilities": ["payments:write", "payments:read"],
    "rate_limit_per_minute": 60,
    "daily_cap": { "token": "CCD", "amount": "5000" }
  }'

Webhook events

Register a HTTPS endpoint per project. We send a signed POST on every state transition. Acknowledge with HTTP 2xx within 10 seconds; otherwise we retry with exponential backoff up to 24 hours.

EventWhen it fires
payment.submittedSigned by the vault and sent to Concordium.
payment.finalisedFinality reached on-chain.
payment.rejectedChain rejected. Terminal.
payment.failedInternal policy failure post-submission. Terminal.
key.revokedAn API key under this project was revoked.
policy.updatedProject policy changed.

Webhook signature

Header: Concordex-Signature: t=<timestamp>,v1=<hmac-sha256>. Verify on your side:

import hmac, hashlib, time

def verify(payload: bytes, sig_header: str, secret: str, tolerance: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in sig_header.split(","))
    ts, v1 = parts["t"], parts["v1"]
    if abs(time.time() - int(ts)) > tolerance:
        return False
    signed = f"{ts}.".encode() + payload
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, v1)

Python SDK

pip install concordex
from concordex import Concordex

client = Concordex(api_key="sk_live_a1b2c3d4e5f6g7h8")

payment = client.payments.create(
    amount="5.00",
    token="CCDX",
    to="agent_4f2a91…",
    idempotency_key="pay_2026_05_11_invoice_4831",
)

final = client.payments.wait_for_finality(payment.id, timeout=30)
print(final.tx_hash, final.fee)

TypeScript SDK

npm install @concordex/sdk
import { Concordex } from "@concordex/sdk";

const client = new Concordex({ apiKey: process.env.CONCORDEX_KEY! });

const payment = await client.payments.create({
  amount: "5.00",
  token: "CCDX",
  to: "agent_4f2a91…",
  idempotencyKey: "pay_2026_05_11_invoice_4831",
});

const final = await client.payments.waitForFinality(payment.id, { timeoutMs: 30_000 });
console.log(final.tx_hash, final.fee);

MCP tool surface

For agents using the Model Context Protocol, Concordex exposes the same endpoints as MCP tools. Drop into Claude Desktop, Cursor, Cline:

pip install concordex-mcp
concordex-mcp --api-key sk_live_a1b2c3d4e5f6g7h8
{
  "mcpServers": {
    "concordex": {
      "command": "concordex-mcp",
      "env": { "CONCORDEX_KEY": "sk_live_a1b2c3d4e5f6g7h8" }
    }
  }
}

Hosted MCP App

Don't want to run the MCP server yourself? Use the Concordex Hosted MCP App — a managed MCP endpoint plus a web UI for the human side of agent payments: see every payment your agent triggered, who approved what, balances per PLT, policy edits, audit tail.

Claude Desktop config

{
  "mcpServers": {
    "concordex": {
      "url": "https://mcp.concordex.app/v1/your-project",
      "transport": "http",
      "headers": { "Authorization": "Bearer mcp_live_…" }
    }
  }
}

The MCP App can require dashboard approval on payments above a threshold — the agent gets a pending_approval response, a human ticks the box in the UI.

Two surfaces, one wallet. Hosted MCP App and the REST API both authorise the same project wallet against the same vault. Use whichever surface fits the caller.

Request API access   FAQ