API Reference
Programmatic interface for any Unova Type-2 node. Generic data primitives — works equally well for supply chain, IoT telemetry, certificates, real estate, carbon credits, or any domain where you need a verifiable on-chain audit trail.
Overview
Every Type-2 node runs the same Hermes API. The launchpad shows you the URL on the node detail page (typically http://<your-droplet-ip>). You authenticate per-facility with a bearer token, then read and write assets and events against your node — which batches them into bundles published on-chain.
Core data model
Asset
A trackable entity with a unique on-chain identity.
Examples: A shipment container · A medical device · A real-estate parcel · An IoT sensor · A carbon credit certificate · A vehicle's VIN
Event
Anything that happened to or with an asset, signed and timestamped.
Examples: Container scanned at customs · Sensor reading · Title transferred · Carbon credit retired · Vehicle service record
Bundle
A batch of assets and events published on-chain. Provides cryptographic proof of integrity for the data inside.
Examples: Auto-generated by your node based on bundle settings (interval + min items)
Facility
A sub-identity of your company — typically a physical site or system that submits data.
Examples: A warehouse · An IoT gateway · A specific factory line · A regional office
Base URL
http://<your-type-2-droplet-ip>
Find the URL on your node's detail page at /nodes/mine. SSL/custom domain support is on the roadmap; today the API is exposed on plain HTTP port 80 of the droplet.
Authentication header
Authorization: UNOVA_TOKEN <your-bearer>
Tokens are per-facility. Generate one in /data/company → Facilities → API token.
Response envelope
All responses are JSON, wrapped in this shape:
{
"meta": {
"code": 200,
"message": "Success"
},
"data": [ /* result(s) */ ],
"resultCount": 42
}Read access
All asset, event, and bundle-listing reads require a valid bearer token. Anonymous requests get 401 Unauthorized.
The accessLevel filter is enforced: events with content.idData.accessLevelhigher than the caller's account accessLevel are hidden. super_account bearers bypass this and see everything.
Every Type-2 node deployed via the launchpad enforces this by default — no configuration needed.
Privacy at the bundle level: data with accessLevel > 0is automatically encrypted with your organization's key before being bundled. Other Atlas nodes that shelter the bundle see ciphertext they can't decrypt — only your organization's accounts can read it back.
The one exception is GET /bundle/:bundleId — deliberately public so other Atlas nodes can pull bundles for sheltering and challenge resolution. Bundle metadata listing endpoints (/bundle/list, /bundle/query) require auth.
Authentication
Each facility you create at /data/company gets its own auto-generated wallet (privkey encrypted at rest in the launchpad). To get an API token, you exchange that privkey at your node's /auth/getApiToken endpoint.
The launchpad does this for you — when you click API token on a facility, it loads the privkey, calls Hermes, and returns the bearer. You never need to handle the privkey manually.
/auth/getApiTokenAuth: None (public)Exchange a facility's signed payload for a bearer token. Done by the launchpad on your behalf.
Request body
{
"publicKey": "0x...",
"validUntil": 1746460800,
"signature": "0x..."
}Response
{
"data": [{
"apiToken": "UNOVA_TOKEN eyJhbGciOi..."
}]
}Token permissions
Tokens carry permissions inherited from the wallet they were issued to. Common ones:
create_asset — submit new assetscreate_event — submit new eventssuper_account — admin endpoints (push bundle, backup/restore)A facility wallet typically has create_asset + create_event. The node operator wallet typically has super_account. Permissions are configured during the organization-onboarding flow on Hermes.
Signing requests
Every createendpoint (asset or event) requires a signed payload so the network can verify the writer's identity and detect tampering. The signing scheme is the same for both:
- Build the payload object (the
idDatafor assets, oridData+datafor events). - Serialize using the deterministic format below (sorted keys, no whitespace).
- Hash via
web3.eth.accounts.hashMessage(serialized)(EIP-191 personal_sign hash). - Sign with the facility's private key —
web3.eth.accounts.sign(serialized, privateKey)handles step 3+4 together. - The resulting hash also serves as the
:assetIdor:eventIdin the URL — Hermes checks they match.
Deterministic serialization
All object keys are sorted alphabetically; arrays preserve order; strings are JSON-quoted; numbers and booleans stringify to their default form. No whitespace.
// Pseudocode for serializeForHashing
function serialize(x) {
if (isObject(x)) {
const keys = Object.keys(x).sort();
return "{" + keys.map(k => `"${k}":${serialize(x[k])}`).join(",") + "}";
}
if (Array.isArray(x)) {
return "[" + x.map(serialize).join(",") + "]";
}
if (typeof x === "string") return `"${x}"`;
return String(x);
}Node.js example — sign + create an asset
import Web3 from "web3";
const web3 = new Web3();
// Your facility's private key (decrypted from the launchpad's API-token modal,
// or stored in your warehouse / IoT system)
const FACILITY_PRIVKEY = "0x...";
const FACILITY_ADDRESS = "0x..."; // derived from the privkey
// 1. Build the idData
const idData = {
createdBy: FACILITY_ADDRESS,
timestamp: Math.floor(Date.now() / 1000),
sequenceNumber: 0,
};
// 2-4. Serialize + sign in one call
const serialize = (x) => { /* see above */ };
const serialized = serialize(idData);
const { signature } = web3.eth.accounts.sign(serialized, FACILITY_PRIVKEY);
// 5. Compute the assetId — it's the hash of the {idData, signature} content
const content = { idData, signature };
const contentSerialized = serialize(content);
const assetId = web3.eth.accounts.hashMessage(contentSerialized);
// 6. POST to the node
const res = await fetch(`${HERMES_URL}/asset2/create/${assetId}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `UNOVA_TOKEN ${BEARER}`,
},
body: JSON.stringify({ content }),
});For events, the content additionally includes a data array, and idData includes assetId + dataHash (the hash of the data array). See the Events section.
Code examples
Five end-to-end examples covering the flows almost every integration needs. Replace placeholders (HERMES_URL, FACILITY_PRIVKEY, FACILITY_ADDRESS) with values from your facility's API token modal in /data/company.
1. Get a bearer token
Exchange your facility's private key for a Hermes bearer. Cache the token client-side; it expires hourly.
curl -X POST "$HERMES_URL/auth/getApiToken" \
-H "Content-Type: application/json" \
-d '{"privateKey":"'"$FACILITY_PRIVKEY"'"}'2. Create an asset
Sign + POST the smallest valid asset payload. The assetId in the URL is the content hash; Hermes rejects mismatches.
# Use the Node.js or Python example to compute the signature + assetId,
# then POST the resulting JSON:
curl -X POST "$HERMES_URL/asset2/create/$ASSET_ID" \
-H "Content-Type: application/json" \
-H "Authorization: UNOVA_TOKEN $BEARER" \
-d '{
"content": {
"idData": {
"createdBy": "0xfacility...",
"timestamp": 1746460800,
"sequenceNumber": 0
},
"signature": "0x..."
}
}'3. Create an event
Same signing flow as assets, but idData also includes assetId, uniqueID (your physical-asset identifier), and dataHash.
# As with assets, compute eventId + signature in code, then POST:
curl -X POST "$HERMES_URL/event2/create/$EVENT_ID" \
-H "Content-Type: application/json" \
-H "Authorization: UNOVA_TOKEN $BEARER" \
-d '{
"content": {
"idData": {
"assetId": "0xabc...",
"uniqueID": "LOT-42",
"createdBy": "0xfacility...",
"timestamp": 1746460800,
"accessLevel": 0,
"dataHash": "0x..."
},
"data": [
{ "type": "unova.event.scan", "value": "delivered" }
],
"signature": "0x..."
},
"groupNumbers": ["1"]
}'4. Look up by uniqueID
The lookup users actually want — find an asset's full event history by YOUR identifier (lot, container, SKU).
curl "$HERMES_URL/assets/LOT-42/events" \ -H "Authorization: UNOVA_TOKEN $BEARER"
5. Walk the genealogy graph
Trace inputs (parents) and outputs (children) of an asset via the Atlas /graphdb worker. Note port 9876 — different from the main Hermes port.
# Forward (children + descendants):
curl -X POST "http://$NODE_IP:9876/graphdb/chain?forward=true" \
-H "Content-Type: application/json" \
-H "Authorization: UNOVA_TOKEN $BEARER" \
-d '{"uniqueID":"LOT-42"}'
# Backward (parents + roots): same call with ?forward=falseAssets
An asset is any entity you want to track. It has a unique ID, an owner address, and a timeline of events. You define the schema — the API doesn't prescribe a shape for asset metadata.
/asset2/create/:assetIdAuth: Bearer (create_asset)Create a new asset. The :assetId in the URL is the content hash of the body — Hermes verifies they match and rejects mismatches. See the Signing section for how to compute it.
Request body
{
"content": {
"idData": {
"createdBy": "0xfacility-address...",
"timestamp": 1746460800,
"sequenceNumber": 0
},
"signature": "0x... (130-char hex)"
}
}Response
{
"data": {
"assetId": "0x...",
"createdBy": "0xfacility-address...",
"createdAt": 1746460800,
"content": { /* echoed */ }
}
}Look up an asset
Two identifiers exist: uniqueID is YOUR identifier (lot, container, SKU, animal tag — assigned by you when you create the asset), assetId is the on-chain content hash (returned by Hermes after creation, deterministic from signed content). Real integrations almost always look up by uniqueIDbecause that's what your ERP / scanners / labels use.
/assets/:uniqueID/eventsAuth: BearerPRIMARY USER LOOKUP — find all events for an asset using YOUR uniqueID (lot, container, SKU). This is what your ERP integration calls when a user types a lot number into a search box.
/asset2/v2/assetSearchAuth: BearerFilter / paginate assets by arbitrary mongo-style query. Use this for list views with filters (date range, supplier address, asset type, customer).
Request body
{
"query": { "content.idData.createdBy": "0xfacility..." },
"paginationField": "content.idData.timestamp",
"supplier": "0xsupplier...",
"customer": "0xcustomer..."
}/asset2/v2/user/uniqueIdsAuth: BearerList all uniqueIDs the calling account has ever created. Useful for picker UIs / autocomplete.
Request body
{
"list": ["0xpartnerA...", "0xpartnerB..."] // optional: include partners' uniqueIDs too
}/asset2/v2/user/assetTypesAuth: BearerList the distinct asset.type values the calling account has used. For populating type-filter dropdowns.
/asset2/v2/user/eventTypesAuth: BearerList the distinct event.type values seen across the calling account's assets. For populating event-filter dropdowns.
/asset2/v2/list/:publicKeyAuth: BearerList assets created by a specific publicKey (typically a facility wallet), paginated.
/asset2/v2/list/all/:publicKeyAuth: BearerSame as /v2/list but includes assets from sub-accounts under that publicKey.
Direct lookups (when you already have the assetId)
After /asset2/create returns an assetId, you can cache it and address the asset directly without a uniqueID search.
/asset2/info/:assetIdAuth: BearerGet a single asset by its on-chain hash.
Response
{
"data": {
"assetId": "0xabc123...",
"createdBy": "0xowner...",
"metadata": { "name": "Container LOT-42", "type": "shipment" },
"createdAt": 1746360800
}
}/asset2/exists/:assetIdAuth: BearerCheap existence check — returns 200 if the asset is known to this node, 404 otherwise.
/asset/:assetId/eventsAuth: BearerFlat list of every event ever logged against a specific asset (by hash) — its full lifecycle history.
/asset2/queryAuth: BearerGeneric query — mongo-style filter + sort + pagination. More flexible than /assetSearch, no business-logic conveniences.
Request body
{
"query": {
"createdBy": "0xowner..."
},
"sort": { "createdAt": -1 },
"page": 0,
"perPage": 20
}/asset2/v2/remove/:uniqueIdAuth: BearerRemove an asset and ALL its events from this node's local cache by uniqueID. Does NOT remove from chain — the bundle stays anchored. For GDPR-style erasure or test cleanup.
/asset3/v3/list/all/:publicKeyAuth: Bearerv3 alternative listing endpoint — returns assets owned by publicKey including child accounts, paginated.
Events
An event is a signed, timestamped record of something that happened to or with an asset. Events are the primary write — you submit them as your business logic executes (a scan, a sensor reading, a state transition).
/event2/create/:eventIdAuth: Bearer (create_event)Submit a new event. The :eventId in the path is the content hash of the body — Hermes verifies they match. uniqueID inside idData is YOUR identifier (the lot number, container ID, SKU, animal tag — whatever links this event to a physical asset); it's what the trace graph uses to walk genealogy.
Request body
{
"content": {
"idData": {
"assetId": "0xabc123...",
"uniqueID": "LOT-42", // ← required: your physical-asset link
"timestamp": 1746360800,
"accessLevel": 0,
"createdBy": "0xfacility...",
"dataHash": "0x..." // = calculateHash(data)
},
"data": [
{
"type": "unova.event.location",
"geoJson": { "type": "Point", "coordinates": [4.40, 51.22] }
},
{
"type": "unova.event.scan",
"value": "delivered"
}
],
"signature": "0x... (130-char hex over serialized idData)"
},
"groupNumbers": ["1"] // partner groups to share with
}Response
{
"data": {
"eventId": "0x...",
"content": { /* echoed */ }
}
}/event2/listAuth: BearerList recently published events across all assets the calling account can see.
/event2/info/:eventIdAuth: BearerGet a single event by its ID.
/event2/queryAuth: BearerQuery events with arbitrary mongo-style filters. Use this to surface events of a specific type, by an asset, in a date range, etc.
Request body
{
"query": {
"content.data.type": "unova.event.scan",
"content.idData.assetId": "0xabc123..."
},
"sort": { "content.idData.timestamp": -1 },
"perPage": 20
}/event2/searchAuth: BearerFree-text search across event payloads. Indexed by Hermes.
/event2/lookup/typesAuth: BearerList all event types your node has ever seen — useful for building filter UIs.
/event2/v2/list/:publicKeyAuth: BearerList events created by a specific publicKey, paginated.
/event2/v2/list/all/:publicKeyAuth: BearerList events from a publicKey including its child accounts, paginated.
/event2/v2/countAuth: BearerCount events matching a query — for dashboards / pagination totals.
Trace endpoints
Three layers of trace, increasing in richness:
/asset/:assetId/eventsAuth: BearerFlat list of every event ever logged against a specific asset. The simplest 'show me what happened' view.
/asset2/v2/traceAuth: BearerReturns a numeric traceability score (% inbound vs outbound events) for a given asset. Useful for dashboards. Query params: ?inbound=true and/or ?outbound=true.
/asset2/v2/trace/allAuth: BearerSame shape as /v2/trace but aggregated across all addresses on the node — total network-level score.
/account/:address/traceAuth: BearerPer-account traceability score across everything that account has touched.
Genealogy graph (Atlas /graphdb)
For the full lineage walk — backward (inputs) + forward (outputs) + events — the Atlas worker exposes Neo4j-backed endpoints on a separate port:
http://<node-ip>:9876/graphdb/chainAuth: Bearer (any facility token)Walks TRANSFORMS_INTO + HAS_EVENT relationships in either direction. Returns backwardReferenceAssets[], forwardReferenceAssets[], events[]. Pass ?forward=true|false to choose direction. Body: { uniqueID }. The launchpad's /data/trace UI uses this.
Request body
{
"uniqueID": "LOT-42" // your physical-asset identifier
}Response
{
"data": [{
"success": true,
"backwardReferenceAssets": [
{ "uniqueID": "LOT-37", "data": "...", "labels": ["ASSET"] }
],
"forwardReferenceAssets": [
{ "uniqueID": "LOT-42-A", "data": "...", "labels": ["ASSET"] }
],
"events": [
{ "uniqueID": "Event_42_scan", "data": "...", "labels": ["EVENT"] }
]
}]
}http://<node-ip>:9876/graphdb/routesAuth: Bearer (any facility token)Returns just the TRAVERSAL ROUTES (paths) without full node data. Cheaper than /chain for 'how is X related to Y' lookups.
Bulk + file uploads
For high-throughput ingestion (warehouse scanners, IoT batches, ERP sync) and for attaching binary files (images, PDFs, certificates) to assets/events, use the unified /api endpoint. It accepts an array of asset+event payloads in a single multipart request and ties uploaded files to the entities by index.
/apiAuth: Bearer (create_asset + create_event)Multipart bulk-create. Send a JSON array of asset/event payloads in the requestbody field; attach up to 10 files via files[]. Each entity in the array gets validated, signed, and either an assetId/eventId is computed or you can provide your own.
Request body
# multipart/form-data
requestbody: '[
{
"type": "asset",
"content": {
"idData": { "createdBy": "0x...", "timestamp": 1746460800, "sequenceNumber": 0 },
"signature": "0x..."
}
},
{
"type": "event",
"content": {
"idData": {
"assetId": "0x...",
"createdBy": "0x...",
"timestamp": 1746460801,
"accessLevel": 0,
"dataHash": "0x..."
},
"data": [
{ "type": "unova.event.scan", "value": "received", "fileIndex": 0 }
],
"signature": "0x..."
}
}
]'
files[]: <binary> # uploaded file referenced by fileIndex aboveResponse
{
"data": [
{ "assetId": "0x..." },
{ "eventId": "0x..." }
]
}Max 10 files per request. The token must hold both create_asset and create_event permissions even if your batch only contains one type.
Bundles
Bundles are the on-chain anchor for assets and events. Each bundle contains a batch of entities, signed by your node operator, and its hash is published to the chain. You typically don't fetch bundles directly — they're for proving data integrity to a third party.
/bundle2/listAuth: BearerList recently published bundles.
/bundle2/info/:bundleIdAuth: BearerGet a bundle's full content — every asset and event inside it, plus the on-chain proof.
/bundle2/queryAuth: BearerQuery bundles by node operator, time range, or content type.
Note: the v1 /bundle/:bundleIdendpoint is intentionally public — Atlas nodes pull bundles from each other without auth as part of the network's sheltering / challenge-resolution protocol. The data inside bundles can still be encrypted (see the accessLevel > 0 section in the user docs).
Organizations
An organization is the top-level tenant on a Hermes node. It owns accounts, assets, events, and bundles. The launchpad creates one organization per Type-2 node automatically; you typically don't need to call these endpoints unless you're running a multi-org node or doing migrations.
/organization2/info/:organizationIdAuth: BearerGet an organization's profile, owner address, and metadata.
/organization2/update/:organizationIdAuth: Bearer (super_account)Update organization metadata (name, contact, logo, etc.).
/organizationAuth: BearerList all organizations on this node.
/organization/:organizationId/accountsAuth: BearerList all accounts (users) belonging to an organization.
Accounts
Accounts are individual identities (wallets) within an organization. Each account has a public address, permissions, and metadata. The facility wallets you create in the launchpad register as accounts under your organization.
/account2/listAuth: BearerList accounts on this node, paginated.
/account2/info/:publicKeyAuth: BearerGet account details by wallet address — permissions, organization, metadata.
/account2/create/:addressAuth: Bearer (manage_accounts)Register a new account on this node. Used to add a new facility-style sub-identity.
Request body
{
"permissions": ["create_asset", "create_event"],
"accessLevel": 1,
"organization": 1
}/account2/modify/:addressAuth: Bearer (manage_accounts)Modify an existing account's permissions or metadata.
/account/:address/traceAuth: BearerGet the full activity trace for an account — every asset created, every event submitted.
Organization requests (KYC)
For nodes that vet new organizations before granting write access, a request / approve / refuse flow is built in. The launchpad doesn't use this today (we manage tenancy in our DB), but it's available if you want pure on-node KYC.
/organization/requestAuth: None (public)Submit a new organization application. Goes into the pending queue.
Request body
{
"address": "0x...",
"title": "Acme Logistics BV",
"email": "ops@acme.example"
}/organization/requestAuth: Bearer (super_account)List pending organization requests.
/organization/request/refusedAuth: Bearer (super_account)List refused organization requests.
/organization/request/:address/approveAuth: Bearer (super_account)Approve a pending organization request and provision the account.
/organization/request/:address/refuseAuth: Bearer (super_account)Refuse a pending organization request.
Node info
/nodeinfoAuth: None (public)Get this Hermes node's identity, version, and configuration. Use it as a health check.
Response
{
"data": {
"nodeAddress": "0x...",
"version": "2.x.x",
"headContract": "0x000...0F10",
"network": "test"
}
}Analytics
Aggregate counts of assets/events by organization and time range. Useful for building dashboards on top of your node's data without paginating through every record.
/analytics/:organizationId/:collection/countAuth: BearerTotal count of assets or events for a given organization. :collection is 'asset' or 'event'.
/analytics/:organizationId/:collection/count/:start/:end/totalAuth: BearerCount over a time range (UNIX seconds for :start and :end).
/analytics/:organizationId/:collection/count/:start/:end/aggregate/:groupAuth: BearerTime-bucketed counts. :group is 'hour' | 'day' | 'week' | 'month'.
/analytics/:collection/countAuth: BearerOrg-agnostic total count for the entire node.
Metrics
Prometheus-compatible metrics for monitoring node health. The root /metric endpoint is open for scraping; sub-paths require super_account auth.
/metricAuth: None (public)Prometheus exposition format — scrape with your monitoring stack.
/metric/uonAuth: Bearer (super_account)Current UON balance of the node operator wallet.
/metric/bundleAuth: Bearer (super_account)Bundle publishing stats — count, last-published timestamp, failures.
/metric/balanceAuth: Bearer (super_account)Atlas worker balance + recent gas usage.
Admin
Admin endpoints are gated by the super_account role — typically the node operator key. Used for backups and forcing immediate bundle publishing.
/admin/pushbundleAuth: Bearer (super_account)Force the worker to publish a bundle right now, ignoring the schedule. Useful for testing and for end-of-day flushes.
/admin/getconfigAuth: Bearer (super_account)Download a backup of all organizations + accounts on this node.
/admin/restoreconfigAuth: Bearer (super_account)Restore from a backup payload. Disaster-recovery use only.
Standard schemas
The Unova network ships with a small catalog of well-known data types that every node validates and that the trace UI knows how to render. You can mix these with your own custom type values — the standard ones just get richer rendering and validation.
Asset metadata items
Submitted as the data array of a regular event against an asset (right after the asset is created).
unova.asset.identifiersAlternate IDs for an asset (SKU, GTIN, serial, internal ref)
{
"type": "unova.asset.identifiers",
"identifiers": {
"sku": ["WHEEL-12-BLK"],
"gtin": ["1234567890123"],
"internalRef": ["LOT-2024-W47-A"]
}
}unova.asset.infoDisplay-friendly metadata: name, description, images
{
"type": "unova.asset.info",
"name": "Container LOT-42",
"description": "Pharmaceutical batch, cold chain",
"images": ["https://example.com/img1.jpg"]
}unova.asset.locationCurrent location of the asset
{
"type": "unova.asset.location",
"geoJson": { "type": "Point", "coordinates": [4.40, 51.22] },
"name": "Warehouse Antwerp",
"country": "BE",
"city": "Antwerp"
}Event data items
unova.event.identifiersExternal IDs reported with the event (scan code, RFID, etc.)
{
"type": "unova.event.identifiers",
"identifiers": {
"scanCode": ["8412345678901"],
"rfid": ["E20A1234..."]
}
}unova.event.locationWhere the event happened — GeoJSON Point + optional human-readable name
{
"type": "unova.event.location",
"geoJson": { "type": "Point", "coordinates": [4.40, 51.22] },
"name": "Customs checkpoint Antwerp",
"country": "BE",
"city": "Antwerp"
}Custom types: any typestring that doesn't start with unova. is passed through as-is (no validation). Use your own namespace, e.g. com.acme.shipment.scan, to avoid collision with future standard types.
Supply chain patterns
Common modeling patterns for supply-chain customers — these are recipes built on the generic asset/event primitives, not separate APIs.
Tracking a shipment end-to-end
- Create the asset once at origin:
POST /asset2/create/:assetId - Immediately tag it with display info + identifiers via an event with
unova.asset.infoandunova.asset.identifiersdata items - At each handoff (customs, warehouse, courier), submit an event with the asset's same
uniqueID(your lot/container ID) and data items likeunova.event.location+unova.event.scan - Use
groupNumbersto share each event with the right partner group (e.g., group 1 = your own facilities, group 2 = customs broker, group 3 = end customer) - At delivery, look up
POST /graphdb/chainby the shipment's uniqueID to render the full timeline with location history + every scan
Batch genealogy (TRANSFORMS_INTO)
For manufacturing or processing, where one batch becomes another:
- Create child assets with a custom data item that references parent uniqueIDs, e.g.
{ "type": "com.acme.transform", "fromAssets": ["LOT-37", "LOT-38"] } - Atlas's graph migration creates
TRANSFORMS_INTOedges automatically based on these references - Trace genealogy walks both directions — backward to find inputs, forward to find downstream uses
Recall workflow
Flag a problem batch + propagate to anyone who received downstream assets:
- Submit a recall event against the affected asset:
{ "type": "com.acme.recall", "reason": "...", "severity": "critical" } - Set
groupNumbersto include EVERY partner group (recall events are typically broadcast) - Partners querying
/graphdb/chainon any descendant asset will see the recall event in the genealogy
Privacy patterns
- Public (transparency):
accessLevel: 0,groupNumbers: ["1"](your own group). Anyone who shelters the bundle reads it in plaintext. - Private to your org:
accessLevel: > 0,groupNumbers: ["1"]. Hermes auto-encrypts with your org key; partners who pull the bundle see ciphertext. - Shared with specific partners:
accessLevel: > 0,groupNumbers: ["1", "5"]where 5 is a partner group. Only that group's nodes get bundle-routed and only their accounts decrypt.
Errors & limits
HTTP status codes
200 Success400 Validation error — check the message field401 Missing or invalid bearer token403 Token lacks the required permission (e.g. create_event)404 Resource not found422 Schema mismatch — payload didn't match the expected structure500 Server error — check your node's Hermes logsPagination
All list and query endpoints accept page (0-indexed) and perPage (max 50 by default, server-configurable up to 500 via the PAGINATION_MAX env var).
Rate limits
None by default — your node, your rules. Add a reverse proxy (nginx, Cloudflare) in front if you're serving public traffic.
Asset / event size limits
Hermes accepts payloads up to ~16 MB (Mongo BSON limit). Bundles are capped by chain block size — typically a few hundred entities per bundle.
If you find an undocumented endpoint that's useful, ping us at tech@unova.io and we'll add it here.