All posts

10 min read

Build on the CardanoWall API

How to build your own product on CardanoWall: quote, upload, publish, read, verify, and monitor Label 309 records through the same gateway API CardanoWall itself runs on.

You can build a Proof of Existence feature into your own product without sending users through the CardanoWall website. CardanoWall is a user interface on top of a hosted gateway, and that gateway exposes a plain HTTP API: quote a proof, upload content when storage is needed, publish a Label 309 record, track its status, read public records, check balances, and receive webhooks. The proof that lands on Cardano is standard Label 309 metadata, not a private CardanoWall-only format — so anyone, with any tool, can verify it later.

That is the important shape. The product experience can be entirely yours. The proof stays independently verifiable. This is the same API CardanoWall's own web app and worker run on — there is no private door and no faster internal path.

What can you build on it?

Anywhere Proof of Existence belongs in a workflow, the API fits:

  • a SaaS product that timestamps customer documents;
  • a CI/CD pipeline that anchors release manifests (see proofs in CI/CD);
  • an AI platform that publishes provenance records at scale;
  • a compliance archive that commits daily evidence batches;
  • a legal tool that seals evidence for specific recipients;
  • an internal audit system that signs control snapshots;
  • a public explorer or profile page that lists a publisher's records.

The API is not just a convenience wrapper around the website. It is the automation surface — the thing you reach for when a human is not in the loop.

What is the main publish flow?

A publish has three stages: quote, upload (only when bytes need storage), then publish. On the gateway's data plane (/api/v1/*), that maps to:

  • POST /api/v1/poe/quote — lock the price for a short window.
  • POST /api/v1/poe/uploads — store content, returning content-addressed URIs.
  • POST /api/v1/poe/uploads/sessions and its chunk routes — the resumable path for large files.
  • POST /api/v1/poe/publish — submit the finalized record.
  • POST /api/v1/poe/publish-batch — submit many records in one call.
  • GET /api/v1/poe/events/{poe_id} — stream status updates over Server-Sent Events.

Exact request and response bodies live in the gateway's OpenAPI document, served by every deployment — bind to that, not to a blog snippet. The mental model is what stays stable: lock the price first, store any content, then submit the finalized canonical-CBOR record bytes together with the quote id.

Why does the quote come first?

Because publishing is a paid operation, and the price is not knowable in advance.

A gateway pays real costs on your behalf: Cardano transaction fees, Arweave storage when content is attached, foreign-exchange exposure between USD and those networks, and the operator's margin. A quote makes that cost explicit before the publish call spends anything. (For the reasoning behind paid publishing, see why publishing has a price.)

A quote response carries a quote_id, a priced breakdown (network, storage, and service components), the applied margin, the freshness of the exchange-rate snapshot, and an expiry timestamp. The price is locked only for a limited window — roughly fifteen minutes — after which you request a fresh quote.

That lets your application decide:

  • whether the account can afford the publish;
  • whether the exchange-rate snapshot is fresh enough for your policy;
  • whether the operation needs user confirmation;
  • whether to split a large batch;
  • whether to retry with a new quote when the old one expires.

Do not show a "free" label until the gateway confirms the operation costs nothing. A hash-only record with no storage can be cheap, but the gateway is the authority on price, not your UI.

What does the upload step do?

Upload handles bytes that need somewhere to live.

A hash-only proof uploads nothing — you already hold the digest. A record with attached content, a sealed ciphertext, or recovery material needs content-addressed storage, and the upload endpoints store those bytes and return a URI such as ar://.... The API supports multipart uploads, per-file outcomes, deduplication by content hash within an account, and resumable sessions for files too large to send in one request. CardanoWall imposes no fixed maximum upload size — content is billed per byte, and multi-gigabyte uploads are expected.

Treat upload as its own small lifecycle:

  1. Upload the bytes.
  2. Receive the content-addressed URI.
  3. Build or finalize the Label 309 record using that URI.
  4. Publish the record.

If an upload returns an in-flight attempt id, poll that attempt's status endpoint rather than re-uploading blindly — re-sending a multi-gigabyte file because you missed a response is the kind of mistake the attempt id exists to prevent.

What does publish do, and why is it asynchronous?

Publish submits the record and starts the chain pipeline.

The publish call accepts the finalized canonical-CBOR record bytes plus the quote_id. The gateway anchors the record on Cardano under metadata label 309, debits the quoted amount, and returns a gateway record id (a poe_... value) while the transaction is still moving through submission and confirmation.

That asynchrony matters for your design. When the call returns, the on-chain transaction hash may not exist yet. Show a pending state and listen for updates. The status the API reports moves through these values:

  • submitting — the transaction is being built and broadcast;
  • confirming — it is on chain but below the confirmation threshold;
  • confirmed — it has crossed that threshold and is settled;
  • failed — the publish failed terminally and will not land.

The status stream at GET /api/v1/poe/events/{poe_id} exists for exactly this. On a terminal failure, the gateway reverses the debit itself — so your product can honestly tell users "you are only charged for what lands on-chain" without running your own reconciliation. Do not build a vendor-side refund path for publish failures; it would double-refund.

What should your API client store?

Store enough to reconnect the workflow — and no more. At minimum:

  • your own user or account id;
  • the gateway poe_id;
  • the quote id used for the publish;
  • the record digest, or a hash of the record bytes;
  • the final Cardano transaction hash once it exists;
  • status and timestamps;
  • any upload attempt ids;
  • any content-addressed URIs;
  • any local material you will need to verify later.

Do not treat your database as the proof. Treat it as a product read model. The proof is the on-chain Label 309 record plus the content or keys needed to verify it — and that combination stands on its own, independent of your servers.

How do you read and verify records?

The records surface is built for public, anonymous reads:

  • GET /api/v1/records — the on-chain record feed, with filters and pagination.
  • GET /api/v1/records/count — a count for that feed.
  • GET /api/v1/records/{tx_hash} — a single record by transaction hash.
  • POST /api/v1/records/{tx_hash}/verify — a server-side verification report.

The list and get routes serve anonymous callers — public verify pages and explorers need no credential. A bearer token can add account context where relevant, but public record verification should never depend on a private CardanoWall session.

Use these endpoints for product convenience. For high-assurance checks, also run a standalone verifier that fetches chain data through Cardano infrastructure it chooses and produces an independent report. That is the healthy split: the API makes apps easier to build, but the proof must still stand outside the API. The open-source SDKs and CLI ship exactly that verifier — see verifying a Label 309 record for how it works end to end.

How should authentication work?

Keep credentials narrow, and keep operator credentials on your backend.

The gateway separates two planes. Your users act over the data plane (/api/v1/*) with a short-lived account token or API key that your backend mints per session. Your backend alone holds the operator credential for the control plane (/control/v1/*) — provisioning accounts, applying credits when your billing collects money, setting margins, and minting those account tokens.

Data-plane access is scoped. The scopes you will use:

  • poe:create — quote, upload, publish, and batch publish;
  • poe:read — record reads, the verify route, and the status stream when called with a bearer;
  • account:read — balance and ledger reads;
  • webhooks:read and webhooks:write — account-scoped webhook management;
  • billing:read — reserved for vendor billing surfaces.

A publish page needs poe:create; a balance widget needs account:read; neither needs more. Operator credentials must never reach a browser bundle, a mobile app, a partner's script, or a CI job that only needs to publish proofs — those mint their own scoped account token instead. A leaked account token should be a one-hour problem, not an incident.

How should retries and idempotency work?

Design retries before production, because publish systems fail in boring ways: network blips, expired quotes, insufficient balance, rate limits, an upload still in flight, or a dropped status stream.

A solid integration should:

  • use idempotency where the API supports it, keyed on a stable originating id;
  • treat quote expiration as normal — fetch a new quote and continue;
  • poll the authoritative attempt or record endpoint after an uncertain failure, rather than re-submitting blindly;
  • avoid double-uploading large files;
  • avoid double-crediting balances (key every credit on the payment's own id);
  • log request ids and gateway ids for support;
  • distinguish a failed publish from an unverifiable record in your reports.

Publish deduplicates on the record bytes, and upload batches support idempotent replay. Lean on those semantics instead of a "try again and hope" loop.

What should webhooks do?

Webhooks are how you build read models — not by polling every second, and certainly not by reading the gateway's database tables, which are engine-internal and change without notice.

Register a webhook subscription and the gateway pushes lifecycle events: poe_status_changed, balance_changed, refund intents, upload failures. Operators can subscribe to the full instance-wide firehose; account-scoped webhook routes exist on the data plane too. A "Sent items" view — each user seeing the records they published, with live status — is the canonical use: consume poe_status_changed, project it into your own table, and render from there.

Your receiver should verify each delivery's signature, accept at-least-once delivery, key projections on the event or delivery id, and treat a redelivery as a no-op. The read model belongs to your app. The canonical spend and publish state belongs to the gateway — cache it for rendering if you must, but read the gateway for any decision that gates a spend.

What should never leave the client?

Private identity material. Your application calls the API to publish records; it should never hand the user's Identity Seed, signing private key, or recipient private key to the gateway.

  • For signed records, sign locally or use off-host signing — the SDKs expose a flow where the private key lives in a KMS, an HSM, or an air-gapped machine and only the public key and signature cross the wire.
  • For sealed records, encrypt locally before upload when the use case needs end-to-end confidentiality.
  • For recipient verification, decrypt locally.

The API is for publishing and lifecycle operations. It is not the root of trust, and it is designed so it never needs to be. For the broader principle, see why keys never leave the device.

Where to start

The fastest path is the open-source SDKs and CLI at github.com/cardanowall: a TypeScript SDK (@cardanowall/sdk-ts), a Python SDK (cardanowall-sdk), a Rust SDK (cardanowall), and the cardanowall CLI. All are gateway-agnostic — you supply the base URL and an opaque API key — and all ship the same standalone verifier. If you would rather drive the API by hand first, publish your first proof walks through one record end to end, and using the CLI in automation covers the scripted path. If you want to run the backend yourself rather than use the hosted one, the gateway is open source too — see run your own gateway.

Further reading

apidevelopersgateway