Assets, Oracles & Ownership
An asset represents an existing on-chain token inside Filedgr, with an optional price oracle and a verifiable link to the wallet that controls it.
Registering an asset does not mint a token
POST /assets takes a token_address — the ERC-20 must already exist. Registration deploys
Filedgr's own wrappers around it: a registry, and (when create_oracle is true, the default) a
price oracle.
Deployment is asynchronous. In practice an asset reports two statuses: FILEDGR_RECEIVED on
registration, then DLT_DEPLOYED once the on-chain deployment lands — or ERROR. The
registry_address, oracle_address and factory_address fields are filled in at that point.
FILEDGR_REVIEWED, DLT_DEPLOYING and ACTIVE exist in the AssetStatus enum but are not emitted
by the current pipeline. Treat DLT_DEPLOYED as the terminal success state — waiting for ACTIVE
will never return.
Price and ownership calls fail until the asset reaches DLT_DEPLOYED. Wait for deployment — via the
asset.deployed webhook event — before doing anything else.
Prices come from data attachments, not from your request
This is the least obvious thing about the asset API.
POST /assets/{asset_id}/prices body: {"stream_id": "...", "attachment_id": "..."}
There is no endpoint that accepts a price value. You upload a file containing prices as a data attachment to a stream, then point this endpoint at it. The platform parses the attachment and pushes the values to the oracle.
Three constraints are easy to miss:
- Ownership must be verified first. The request is rejected unless
ownership_verifiedistrueon the asset. Complete the ownership challenge before submitting any prices. - The attachment must contain a CSV. The platform looks for a
.csvobject inside the attachment and ignores everything else. - Every
price_typein the file must already be registered on the asset; unknown names are rejected.
price_type,price,timestamp,currency
NAV,101.25,2026-08-26T12:00:00,USD
BID,101.10,2026-08-26T12:00:00,USD
currency is optional and defaults to USD. timestamp accepts ISO-8601 or
YYYY-MM-DD HH:MM:SS. Rows that fail to parse are skipped.
Price types
A price type is a named series. Any name works — NAV, BID, ASK, MARKET and CUSTOM are the
conventional ones. One is the primary.
POST /assets/{asset_id}/price-types add
GET /assets/{asset_id}/price-types list
DELETE /assets/{asset_id}/price-types/{price_type} remove
PUT /assets/{asset_id}/price-types/primary set the primary
On-chain, a price type is identified by the hash of its name. Casing and whitespace are
therefore permanent and load-bearing: NAV and nav are two different, non-interchangeable price
types. Pick a convention before you create the first one.
Values are scaled by 10 ** decimals (default 8) into integers on-chain.
Reading prices
GET /assets/{asset_id}/prices current
GET /assets/{asset_id}/prices/{price_type} history for one type
GET /assets/{asset_id}/prices/chart chart series (price_type, from_date, to_date)
Everything that writes returns 202
Adding, removing and re-prioritising price types, and submitting prices, all return 202 Accepted
with a short {"message": "..."} acknowledgement. They are queued, not applied. Failures arrive as
an asset.error webhook event; there is no per-price success event, so read the result back from
the typed price endpoints.
asset.updated is accepted as a webhook subscription but is never emitted. The asset events the
platform produces are asset.created, asset.deployed and asset.error.
The response bodies of the price and price-type routes are not contractually stable — several return untyped objects. Treat them as opaque, and drive your logic from the webhook events and the typed read endpoints instead.
Proving ownership
A three-step challenge/response that proves a wallet controls the asset's token contract.
POST /assets/{asset_id}/ownership/challenge {"claimer_address": "0x..."} -> {claim_id, challenge_message}
POST /assets/{asset_id}/ownership/verify {"claim_id": "...", "signature": "0x..."} -> 202
GET /assets/{asset_id}/ownership/status -> poll
Sign the challenge_message returned by step 1, then submit the signature in step 2. The claim
advances CHALLENGE_CREATED → VERIFICATION_SUBMITTED → VERIFIED or REJECTED, and a successful
verification flips ownership_verified on the asset.
The signing convention here is not the one used for Signatures. Ownership challenges hash the challenge string, so a standard message-signing call is what you want. The signature statement flow signs a raw digest with no prefix. Do not copy code between the two.
Failures do not surface as an HTTP error — step 2 returns 202 regardless. A rejected claim shows up
as status: REJECTED with an error_message on the status endpoint, so you must poll or watch for
asset.error.
Permissions
POST, DELETE and GET on /assets/{asset_id}/permissions, with levels OWNER, ADMIN,
EDITOR, VIEWER — the same asynchronous, on-chain-settled model as
vault permissions.