Developers
API reference
Authentication
Every request carries an API key as a bearer token. Keys are issued in the dashboard and are shown exactly once — AssetForge stores only an HMAC of the secret, so a lost key is rotated, never recovered.
Base URL
https://af.niqcode.com
Request
curl "https://af.niqcode.com/api/v1/assets" \ -H "Authorization: Bearer af_test_…"
Test mode
Keys prefixed af_test_. Never touch mainnet.
Production mode
Keys prefixed af_live_. Required for mainnet.
Scopes
assets:readRead asset records and run searches.assets:writeCreate and modify asset records.validation:runRun the validation engine.tokens:readRead token configurations, transactions and holders.tokens:writeCreate and modify token configurations.tokens:deployPrepare deployments. Does not grant signing — AssetForge holds no keys.oracles:readRead oracle feeds and readings.webhooks:readList webhook endpoints.webhooks:writeCreate and modify webhook endpoints.
Rate limits
Requests are limited per key and per endpoint; the default is 120 per minute, and validation is 30 per minute because each run performs live network calls. Every response carriesx-ratelimit-limit and x-ratelimit-remaining; a 429 carries retry-after.Assets
Create and read asset records. An asset is the subject of everything else.
/api/v1/assetsReturns assets in the authenticated organization, newest first.
assets:readQuery parameters
q | string | Free-text match on name, ticker or AssetForge ID. |
type | string | Asset type, e.g. EQUITY, ETF, PRIVATE_CREDIT. |
status | string | DRAFT, PENDING, VERIFIED, RESTRICTED, UNSUPPORTED. |
jurisdiction | string | ISO 3166-1 alpha-2 code. |
limit | integer | Page size, 1–100. Defaults to 25. |
offset | integer | Rows to skip. Defaults to 0. |
Example
curl "$BASE/api/v1/assets?type=EQUITY&limit=10" \ -H "Authorization: Bearer $ASSETFORGE_KEY"
/api/v1/assetsCreates a DRAFT asset record. Validation is a separate, explicit call.
assets:writeBody
name* | string | Asset name. |
assetType* | string | One of the eleven asset types. |
ticker | string | Not an identifier — never used alone to match an asset. |
issuer | string | Issuing entity name. |
jurisdiction | string | ISO 3166-1 alpha-2 code. |
identifiers | array | Up to 10 { type, value } pairs. Check digits are verified. |
Example
curl -X POST "$BASE/api/v1/assets" \
-H "Authorization: Bearer $ASSETFORGE_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Meridian Industrial REIT",
"assetType": "REAL_ESTATE",
"jurisdiction": "GB",
"identifiers": [{ "type": "ISIN", "value": "GB00BMBL1G81" }]
}'/api/v1/assets/:idAccepts an internal id or a slug. Includes tokens and the oracle feed.
assets:read/api/v1/assets/:id/oracleCurrent reading with its source, timestamp, age and staleness. An unconfigured feed returns status NOT_CONFIGURED rather than a fabricated price.
oracles:readValidation
Run the validation engine. Results are technical signals, never legal or regulatory determinations.
/api/v1/assets/:id/validateRuns all ten categories and returns the full report: per-check status and evidence, per-category scores, the overall score, and the coverage figure.
validation:runBody
network | string | "mainnet" or "testnet". Selects which chain the onchain checks run against. |
contractAddress | string | Overrides the stored address for this run. |
Example
curl -X POST "$BASE/api/v1/assets/ast_5/validate" \
-H "Authorization: Bearer $ASSETFORGE_KEY" \
-H "Content-Type: application/json" \
-d '{ "network": "testnet" }'- Rate limited to 30 requests per minute: each run performs live RPC and upstream API calls.
- The `coverage` field reports how much of the record could actually be evaluated. A high score over low coverage means little was checked — read both.
Tokens
Configure, deploy and monitor tokens.
/api/v1/tokensToken configurations in this organization, deployed or not.
tokens:readQuery parameters
limit | integer | Page size, 1–100. Defaults to 25. |
offset | integer | Rows to skip. Defaults to 0. |
/api/v1/tokensSaves a configuration. No contract is deployed by this call.
tokens:writeBody
assetId* | string | The asset this token represents. |
name* | string | Token name. |
symbol* | string | Uppercase alphanumeric, up to 16 characters. |
decimals | integer | 0–18. Defaults to 18. |
initialSupply | string | Base units as a decimal string — these exceed Int64. |
maxSupply | string | Base units. Omit for uncapped. Immutable after deployment. |
transferRestricted | boolean | Gate transfers on the compliance registry. Defaults to true. |
/api/v1/tokens/:idAccepts an internal id or a symbol.
tokens:read/api/v1/tokens/:id/deploySimulates the deployment against the target network and returns an unsigned transaction. AssetForge holds no keys and never broadcasts on your behalf.
tokens:deployBody
network | string | "testnet" (default) or "mainnet". |
confirmMainnet | boolean | Must be true for mainnet. A deliberate second gate. |
deployerAddress | string | Used to estimate gas and check the balance covers it. |
- Refuses with `compliance_review_required` unless the asset has an internally approved compliance review.
- Refuses with `asset_already_tokenized` if a deployment exists on the target network.
- The response contains the transaction for you to sign. Nothing is broadcast by this endpoint.
/api/v1/tokens/:id/transactionsObserved onchain events for this token, newest first.
tokens:readQuery parameters
limit | integer | Page size, 1–100. Defaults to 25. |
offset | integer | Rows to skip. Defaults to 0. |
/api/v1/tokens/:id/holdersHolders and balances, largest first.
tokens:readQuery parameters
limit | integer | Page size, 1–100. Defaults to 25. |
offset | integer | Rows to skip. Defaults to 0. |
Search
One query across assets, tokens, issuers, contracts and transactions.
/api/v1/searchSubstring matching on names and tickers; exact matching on contract addresses and transaction hashes. A partial address is never treated as a match.
assets:readQuery parameters
q* | string | At least two characters. |
Webhooks
Subscribe an HTTPS endpoint to signed events.
/api/v1/webhooksSigning secrets are never returned, including to the owner.
webhooks:read/api/v1/webhooksThe signing secret is returned exactly once, in this response.
webhooks:writeBody
url* | string | HTTPS only. |
events* | array | One or more event names. |
description | string | Free text. |
Health
Operational readiness. Unauthenticated.
/api/v1/healthReports whether each capability is configured — never what it is configured to. Safe to expose to a monitor.
Errors
Failures return a stable machine code and a message written for a person. A raw exception or stack trace is never returned.
Error shape
{
"error": {
"code": "compliance_review_required",
"title": "Compliance review required",
"message": "This asset has no completed compliance review…",
"details": { "assetId": "ast_5", "current": "IN_PROGRESS" }
},
"meta": { "requestId": "req_9f2a…" }
}| Code | HTTP | Meaning |
|---|---|---|
wrong_network | 409 | Your wallet is connected to a different network. Switch to the Robinhood Chain network this action targets and try again. |
insufficient_gas | 402 | The signing account does not hold enough ETH to cover this transaction. Fund the account and retry. |
rpc_unavailable | 503 | The RPC endpoint did not respond. Public endpoints are rate-limited — configure a dedicated provider for production traffic. |
oracle_unavailable | 503 | The configured price source did not return a value. AssetForge will not substitute a stale or estimated price. |
deployment_failed | 502 | The deployment transaction did not succeed. No token was created. Review the revert reason before retrying. |
verification_failed | 502 | The explorer could not verify this source against the deployed bytecode. Check the compiler version, optimiser settings and constructor arguments match the deployment exactly. |
asset_not_found | 404 | No asset with that identifier exists in your organization. |
token_not_found | 404 | No token with that identifier exists in your organization. |
asset_already_tokenized | 409 | This asset already has a deployed token on the target network. Issue on a different network, or manage the existing token. |
compliance_review_required | 409 | This asset has no completed compliance review. Complete the compliance step before deploying. |
unsupported_jurisdiction | 409 | This asset's jurisdiction is on your organization's restricted list. A member with compliance permissions must override this before proceeding. |
invalid_contract | 422 | No contract code was found at that address on the selected network, or it does not implement the expected interface. |
rate_limited | 429 | Too many requests. Retry after the interval given in the Retry-After header. |
wallet_rejected | 400 | The request was rejected in your wallet. Nothing was broadcast. |
unauthenticated | 401 | Provide a valid API key as a bearer token: Authorization: Bearer af_live_… or af_test_… |
forbidden | 403 | This API key does not carry the scope required for this operation. |
validation_failed | 422 | The request body failed validation. See the details field for the offending fields. |
demo_mode | 409 | This deployment is running in demo mode. Write operations that would touch a chain or a database are disabled. Configure DATABASE_URL and an RPC provider to enable them. |
not_configured | 501 | This capability requires configuration that is not present in this deployment. See the settings page for what is missing. |
conflict | 409 | The resource was modified by another request. Re-read it and retry. |
internal_error | 500 | Something went wrong on our side. The failure has been logged. |
Webhook events
Verifying a delivery
Every payload is signed. The timestamp is inside the signed material, which is what makes a captured request non-replayable — signing the body alone would not.
Header
AssetForge-Signature: t=1757601600,v1=<hex hmac-sha256>
Node.js
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(
header.split(',').map((kv) => kv.split('=').map((s) => s.trim())),
);
const timestamp = Number(parts.t);
// Reject anything outside the tolerance window, in either direction.
if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > 300) return false;
const expected = createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(parts.v1, 'hex');
return a.length === b.length && timingSafeEqual(a, b);
}| Event | Fires when |
|---|---|
asset.created | An asset record was created. |
asset.validated | A validation run completed. The payload carries the score, status and per-category results. |
asset.validation_failed | A validation run could not complete — usually an unreachable data source. |
asset.review_required | An asset was flagged as needing human review, either by a failing critical check or by a compliance rule. |
token.created | A token configuration was saved. |
token.deployment_started | A deployment was simulated and the unsigned transaction was issued. |
token.deployed | A deployment transaction was confirmed onchain. The payload carries the contract address and transaction hash. |
token.verified | Contract source was verified on the block explorer. |
token.minted | A mint occurred. |
token.burned | A burn occurred. |
token.transferred | A transfer was observed by the indexer. |
compliance.updated | A compliance review changed status, risk rating or restrictions. |
oracle.updated | An oracle feed produced a new reading, or its status changed. |
contract.paused | A token contract was paused or unpaused. |
Get a key
Test-mode keys are issued instantly from the dashboard.
Before you go to production
Deploy to Robinhood Chain Testnet first. Contract templates have not been independently audited; commission a professional audit before holding value.