Overview
A feed is an on-chain series of values, usually the price of an asset or an exchange rate, that DALP reads for valuation and settlement. It maps a subject address, the asset the value is about, and a topic, the kind of value, to one governed source of observations. An observation is a value with the time it was seen. The API reads the directory, feed configuration, values, and staleness policy, and authorised issuers can submit observations. Price sources publish prices into feeds from a provider, keyed by ISIN, the International Securities Identification Number. Read Market data for the governance model and the V1 or V2 reference for complete schemas.
Use V2 for a new integration. V1 routes use /api/v1/system/feeds/* and have no staleness-window or price-source routes. V2 routes use /api/v2/system/feeds/*. Both accept a session cookie or X-Api-Key, including on submission routes.
Call GET /api/ before using a feed capability. The directory, issuer-signed factory, or adapter factory can be absent from an organisation. An unavailable capability returns an error, not an empty feed list.
Two ways a feed gets registered
An issuer-signed feed is created through the platform's factory. The create request sets its decimals, subject, topic, and description. The platform fixes its retention to the latest round on-chain, requires every submitted value to be greater than zero, and allows no future drift from chain time in the feed contract. The create request does not expose those three settings. A rate or exchange-rate movement that can be zero or negative is unsupported by native feed creation. The indexed API can still serve past accepted observations. Read the feed's configuration with GET /api/ before using it for valuation or settlement.
An external feed, such as a Chainlink-compatible aggregator already deployed on chain, is registered rather than created: POST /api/ maps an existing contract into the directory at a subject and topic. Replacing the feed at that key with PUT /api/ retires the previous address once the queued write settles. Removing it with DELETE /api/ only deletes the directory mapping. The feed contract itself stays deployed and readable at its own address. Neither the old feed's history nor its own contract state changes.
A stable adapter gives consumers one address that survives a feed replacement underneath it. POST /api/ deploys one for a subject and topic. Hold onto adapterAddress from the response, not the feed address behind it, especially from a smart contract or any long-lived integration.
An adapter on an issuer-signed feed changes its kind but not its origin. kind reads aggregator-adapter as soon as an adapter exists for the feed, so it no longer says who feeds it. origin says whether the platform's factory created the feed and, for any other feed, whether it has an adapter:
issuer-signed: the platform's factory created the feed and a trusted issuer signs its values, with or without an adapter.aggregator-adapter: a feed that the factory did not create, such as a Chainlink-compatible aggregator, and that has an adapter.external: a feed that the factory did not create and that has no adapter. This includes a DALP feed registered in the directory without the factory.
Read origin, not kind, to tell whether the platform treats a feed as issuer-signed. The platform does the same: an issuer-signed feed that has an adapter can be mapped to a price source, the source publishes to it, and that feed's item on GET /api/ names the source in provenance. origin is on GET /api/, GET /api/, and GET /api/, and the list filters on it with filter[origin].
Decide who may submit a value
A submission passes two checks before the platform queues it. The role check does not depend on the feed's topic.
The caller must hold the feedsManager role on the system, or the governance role on the feed's subject. The check runs against the wallet that executes the request, so a caller who holds neither is refused with 403 and FEED_.
Registering an external feed with POST /api/, replacing a feed with PUT /api/ and removing one with DELETE /api/ need the feedsManager role on the system. The feeds directory contract accepts these writes only from that role, so a holder of the governance role on the subject is refused with the same 403 before any gas is spent. The Console hides Replace feed and Remove feed from that holder.
The platform then signs the update itself, for the organisation's own operations account, and that account carries the organisation's issuer identity. The identity must be a trusted issuer for the feed's topic in the feed's trustedIssuersRegistry, or the submission is refused with 403 and FEED_ISSUER_NOT_TRUSTED. Read the registry address from the feed's configuration response, and add the organisation's issuer identity to it for the topic before you submit.
Both checks run before the platform queues the write, so a caller refused by either one spends no gas. The trusted-issuer check reads the indexed registry, so while the index trails a recent change to the registry, the feed contract can still refuse the update on chain with the same FEED_ISSUER_NOT_TRUSTED. A custody policy that refuses to sign the update answers with 403 and FEED_ instead: review the policy for the issuer key, not the registry.
Read the latest value and staleness
Each feed has one source for its latest round, and the platform picks it per feed, not per request. A feed that the indexer follows as a DALP scalar feed, such as an issuer-signed feed, is read from the index, whether or not an adapter sits in front of it. Every other feed, such as an externally registered aggregator, is read from the feed contract when you call. The feed kind and an adapter address do not decide the source, so read latestRound rather than inferring it. latestRound on GET /api/ and GET /api/ names the source for each feed: indexed, chain, or none when the index follows the feed but holds no round yet.
curl "https://your-instance.example.com/api/v2/system/feeds/0x71C7656EC7ab88b098defB751B7401B5f6d8976F/latest" \
-H "X-Api-Key: sm_dalp_..."An indexed feed answers:
{
"data": {
"feedAddress": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
"roundId": "42",
"answer": "108420000",
"decimals": 8,
"formattedAnswer": "1.0842",
"startedAt": "2026-03-11T16:02:08.000Z",
"updatedAt": "2026-03-11T16:02:08.000Z",
"recordedAt": "2026-03-11T16:02:20.000Z",
"answeredInRound": "42",
"description": "EUR / USD",
"version": "1",
"asOfBlockNumber": "18445240"
},
"links": {
"self": "/api/v2/system/feeds/0x71C7656EC7ab88b098defB751B7401B5f6d8976F/latest"
}
}For an indexed feed, updatedAt is the time the value was observed and recordedAt is when the chain recorded it. Apply a freshness rule to updatedAt for either source, and check asOfBlockNumber when you need to know how far the index has read. For a valuation, compare the age of updatedAt with the effective staleness window.
Staleness is a governed policy, and only V2 exposes it. GET /api/ resolves the same window the platform's own price-age checks enforce elsewhere, such as before a mint. Two governed inputs feed that resolution, and exactly one effective value comes out:
Two governed inputs resolve to one effective window, tagged with its source. The platform's own staleness checks, including the one that gates a mint, use that resolved value.
Read the effective window rather than inferring it, because the default depends on how the resolver was set up. A resolver that has no observed global-window event keeps the on-chain default of one day. The platform initialises a new resolver to never-expires only when the account that creates it holds the Feeds Manager role, and the effective window is never-expires once an event setting it has been observed. The response's source field tells you which input won: "feed-override" or "global-default".
A bare max on the setter routes cannot express both “clear this feed's override” and “reject every value as stale.” That ambiguity is why the override is a tagged choice rather than a number. PUT /api/ takes subject, topicId and a policy of {"mode": "seconds", "seconds": N}, {"mode": "never"}, or {"mode": "inherit"} for one feed, with an optional walletVerification. PUT /api/ sets the system-wide default with {"mode": "seconds", "seconds": N} or {"mode": "never"}, and has no inherit option. GET /api/ reads that default back in the same shape, so a client can read it, change it, and write it back.
V1 has no equivalent to any of the three staleness-window routes. A V1-only integration can read a feed's raw age against a threshold it supplies on each call, but it cannot read or set the platform's governed window, and a staleness-window call on V1 returns 404.
Submit a signed value
The submission route resolves the issuer's nonce and signs for you, so an integration never reads or tracks nonces itself.
POST /api/ takes the human value as a decimal string, such as "1.08". The platform converts it to on-chain units using that feed's decimals. A value with more decimal places than the feed stores is refused with 400, FEED_ (DALP-12245), before anything is signed. Round the value to the feed's decimals and submit it again. It also takes observedAt as a Unix-seconds timestamp. The platform clamps observedAt into the window from the feed's latest observation to the current chain time, so a future timestamp is published at chain time. It refuses the submission with 400, FEEDS_ (DALP-0639), only when the feed already holds an answer newer than chain time. The submit route resolves a signer rather than an executing wallet: the platform signs with the organisation's operations account, while the wallet that executes the request carries the role check. It answers like other queue-backed writes: 200 when the write lands within the wait that Prefer asks for, otherwise 202 with Location set to the transaction's status URL. This route also declares an X-Transaction-Hash header on both answers, and Preference-Applied echoes only the Prefer directives the platform honoured. See Idempotency and async settlement for the retry and polling discipline. This route follows it exactly, with no feed-specific exception.
Feed prices from a provider
A price source maps the ISINs a provider delivers to the price feeds of your tokens. The platform checks each delivered price and publishes the ones that pass through the organisation's operations signer. When the prices family of the organisation's approval policy is on, a price the checks flag waits for a second person, as Review a held price describes. Use a price source when a custodian or market-data vendor, not a person, is the origin of a token's price. The V2 routes are under /api/v2/price-sources, which needs the Feeds Manager role, and /api/v2/held-prices, which accepts the Feeds Manager role or the governance role of the held price's token.
Price sources support five protocols: an SFTP CSV file, an SFTP ISO 20022 reda.001 file, FIX market data over FIXT.1.1 or FIX.4.4, Bloomberg Data License, and LSEG DataScope Select. The Bloomberg and LSEG requests are built from each vendor's public documentation and have not run against the live service. The routes store a source's settings and credential, map instruments, and run the checks, review, and stale alert described here. The platform fetches each delivery itself, because no route accepts a pushed one. It polls an SFTP source for files, keeps one session open to a FIX source and reads its market data messages, and sends a Bloomberg or LSEG source's request at the clock times the source names. A vendor request names only the source's mapped ISINs and the source holds no instrument list of its own, so mapping an ISIN is what puts it on the request.
Set a source up in this order:
POST /api/creates a draft source with its connection settings, publish threshold, hold tolerance, and timing. The platform rejects a host on a loopback, link-local, metadata, or private address unless the deployment's allow list covers its range, and no allow list lifts a cloud metadata address (DALP-14704). An operator sets the allow list, the caps on each delivery, and the network paths described in Price source prerequisites.v2/ price- sources PUT /api/stores the credential. The credential is write-only: no response returns it, onlyv2/ price- sources/ {source Id}/ credential credentialSetand acredentialHint. The hint holds the last characters of the secret so an operator can tell which credential is stored: up to three, fewer for a secret under nine characters, and an empty string for a secret of one or two characters. For a private key it holds the last characters of the hex SHA-256 digest of the key text, never of the key. Send theexpectedyou read, or a concurrent change answers 409 (DALP-14738). A retired source takes no new credential and answers 409 (DALP-14741): create a new source instead.Credential Version - For an SFTP source,
PUT /api/accepts the server's host key fingerprint after you check it with the provider.v2/ price- sources/ {source Id}/ host- key POST /api/maps up to 50 ISINs to tokens in one request. A token with no price feed gets an issuer-signed feed in the same operation. The response is a stored operation with av2/ price- sources/ {source Id}/ instruments links.selflink. PollGET /api/until itsv2/ price- sources/ {source Id}/ instrument- operations/ {operation Id} statusis no longerin_progress. Each item readspending,mapped,completed, orfailed, and a failed item carries thecodeandmessageof its error. The operation isfailedwhen it has finished and any item failed.POST /api/withv2/ price- sources/ {source Id}/ transitions activatestarts the source. The same route takespauseandretire.
After step 2 you can check the connection before you activate. POST /api/ queues a test for any protocol, and GET /api/ returns its result. The test runs from the scheduler host and reads no price. It lists the remote path for SFTP, opens the connection without logging on and, for TLS, checks the provider's certificate for FIX, lists the account's catalogues for Bloomberg, and asks for a fresh token for LSEG. A test that does not finish within 30 seconds fails.
An active SFTP, Bloomberg, or LSEG source can fetch before its next scheduled run. POST /api/ queues the fetch and answers with an operation, and GET /api/ returns it until its status is succeeded or failed. The operation records the user who asked, and links.self points at it. A Feeds Manager can call both routes, with a signed-in session or an API key and no wallet verification. The POST declares no idempotency and reads no Idempotency-Key. A retry while the first request still waits answers 409 with DALP-14743, but a retry after the scheduler claimed the request queues another fetch, and for Bloomberg or LSEG that is another vendor request. Read the operation, or the source, before you repeat the request. The scheduler host that holds the source's lease picks the request up within 10 seconds. An SFTP source then polls its remote path at once. A Bloomberg or LSEG source starts one request and collects its result as it does on schedule. A successful operation reports how many deliveries the fetch produced. A failed one carries the reason.
- A FIX source streams its prices and answers 422 with DALP-14742.
- A source that is not active answers 409 with DALP-14736. Activate it first.
- A source with a fetch still waiting for the scheduler answers 409 with DALP-14743 and names the waiting operation. Read that operation instead of asking again.
Every delivery from the fetch names the user who asked. If one of its prices is held, that user cannot approve it, and another Feeds Manager or the token's governor can. A delivery from the schedule names no one.
To remove a credential, send POST /api/ with {"transition": "remove", "expected. The platform deletes the secret, clears the hint, advances credentialVersion and answers the source. An active source answers 409 (DALP-14740), so pause it first. A credentialVersion that moved since you read it answers 409 with DALP-14738 and removes nothing. A source that already holds no credential answers 200 whatever version you send. Removal also clears the credential of a retired source, whose secret retirement already deleted. Activation answers 409 (DALP-14737) for a source without a credential, including one whose credential a concurrent removal took.
Activation, editing a source with PATCH, setting or removing the credential, the host key, and instrument mapping each need a signed-in session and a wallet verification. An API key answers 403 (DALP-14734). Pause, retire, create, and the reads work with an API key. Because these writes carry a wallet verification or a secret, they declare no idempotency, so read the source again before you repeat one. When the organisation lists the platform-settings protection group, creating or editing a source, a source transition, setting or removing the credential, the host key, and instrument mapping answer 202 and wait as a pending approval until a second person decides them, as Actions describes.
The platform rejects a heartbeat that cannot fit the staleness bound. The heartbeat plus the source's expected delivery interval plus its expected lag must be shorter than the bound, which is the feed's effective staleness window when it is finite and otherwise the source's staleAfterSeconds. A request that breaks the rule answers 422 with DALP-14703.
What happens to a delivered price
Each price becomes an observation with its ISIN, price, currency, price type, and observed time. The platform applies these gates in order and records the outcome on the observation:
The platform judges time on the chain head timestamp, not the server clock. Read an observation with GET /api/, filtered by filter[status] and filter[isin]. The list is newest first, so with filter[isin] its first row is the latest observation of that ISIN. Read the source's files and messages with GET /api/. Each delivery carries outcomes, the counts of how its observations settled: accepted, rejected, dropped, held and published. accepted overlaps held and published, and an unsettled observation is in no group.
GET /api/ and GET /api/ take include=metrics, which adds a metrics block to each source: the live instruments, the observations publishedToday since 00:00:00 UTC of the current UTC day, the open held prices, and lastDeliveryAt, which is null before the first delivery. include=latestDelivery adds the most recent delivery, and the two includes combine in one request. GET /api/ lists the ISINs a source delivered that no mapping covers, and takes page[limit] and page[cursor] to page through them.
Review a held price
The platform holds a flagged price only when the prices family of the organisation's approval policy (PUT /api/) is on at that moment. A price already held keeps waiting for a decision if the family is turned off later. Every family starts off. With it off, a price the checks flag publishes without review, the platform records a waived approval for that observation, and it creates no held price.
With the family on, a held price waits for a decision by a Feeds Manager or by the governor of that token. GET /api/ lists only the held prices the caller may decide, and takes filter[status] and filter[sourceId]. With both, meta.total is the open-hold count of that source. Each held price carries source, which holds the name of the source that delivered it and the hold tolerance in force at the time of the read, the one a decision checks against. A later edit of the source changes it. A user who created the source, edited its settings or mappings, accepted its host key, or queued the delivery that produced the hold cannot decide it. The platform checks that group again at every decision, so a user who edits the source after the hold joins it.
Read the evidence before you decide. GET /api/ returns the full delivery to a Feeds Manager and only the record that produced the observation to a governor.
POST /api/ approves or rejects. The request names reviewedObservationId, the evidenceDigest from the held price, and a wallet verification, and it needs a signed-in session. The platform checks the caller's authority again at decision time.
Retiring the source cancels each of its held prices that still waits for a decision and withdraws its approval, so the held price reads cancelled. A retired source holds no new price.
An approved price goes to the feed through the feed's publish lane. Manual submissions, batch submissions, source publications, and token price writes to one feed take turns on that lane, one write at a time, because a feed accepts an issuer's updates only with strictly sequential nonces. If that publish fails, POST /api/ with retry_publish and a wallet verification tries again.
Restart a stopped source
A source stops publishing after a credential or configuration failure. GET /api/ returns the source's failureClass, which reads credential or config while the source is stopped. Its observations settle as publisher_not_authorized with the reason source_stopped. A transient failure retries on its own and does not stop the source.
Fix the cause first. A config failure means the organisation's identity is not deployed or is not an identity contract, or the organisation has no operations signer. A credential failure means the operations signer holds no claim key, the key an identity contract accepts for signing claims, or the organisation is not a trusted issuer for the price topic, as Decide who may submit a value describes. No price-source route changes the identity, the signer, or the trust.
The platform also stops a source when its fetch from the provider is refused three times in a row for a credential or configuration cause. A credential failure is a refused login or logon, a vendor answer of 401 or 403 other than a Bloomberg 403 that reports clock skew, a stored credential that is missing or of the wrong kind, or a Bloomberg credential past its expiry date. A configuration failure is a refused endpoint, a source setting the provider cannot serve, an SFTP host key problem, a FIX certificate or sequence-number problem, or a Bloomberg refusal of the scheduler's egress address. An unreachable provider, a timeout, and a vendor answer of 408, 429, or 5xx are transient, so the platform retries them. Fix a provider cause with the provider, in the source's settings, or by replacing the credential.
Then clear the stop. Send pause and then activate to POST /api/. Activation clears the failure class and rechecks the endpoint and heartbeat rules against the current settings. For a credential failure, PUT /api/ with a new credential also clears the stop, and a config failure stays until you activate. The new credential does not repair the signer or the trust, so the next publication stops the source again while those causes stand. Pause accepts an API key. Activation and the credential change need a signed-in session and a wallet verification.
Clearing a stop does not prove the cause is gone. The next publication or provider fetch checks again and stops the source again if the cause remains. A paused source publishes nothing, so a feed it maps can go stale until you activate it.
Stale feeds and provenance
A mapped feed that is older than its staleness bound at the chain head raises one operations case of kind stale-feed for each stale episode. The case names the source and the likely cause, such as a paused source or a held price, and resolves when a fresh price lands, the feed is unmapped, or the source is retired. Platform operators also get the metric dalp_ and an alert rule on it. While the indexer trails the chain head by more than the configured bound, the platform makes no stale judgement and reports the lag.
Each item of GET /api/ includes provenance. It names the source with its protocol, and its vendor for a Bloomberg or LSEG source, which is null for an SFTP or FIX source. It also gives the price field and type, the observationId that produced the latest published price, and that price's observed, recorded and published times. signedBy is the operations signer that signed the price, read from the on-chain event, so it stays null until the indexer has seen the transaction. approvedBy is the approver when a held price carried the price. A feed maintained by hand returns null.
Feed errors and retries
Most feed errors are caller mistakes: a bad address, a topic name and topic id that disagree, or a malformed request body. Check the error's retryable flag and the current instruction state before deciding whether another write is needed. Apart from a feed create, which returns its first result for a repeated idempotency key, the feed routes do not document request-level replay safety. Retaining the original key alone does not make an API retry safe:
- The directory has no mapping yet for a subject and topic you expect to be registered. This happens when a registration transaction has confirmed but Ledger Data has not caught up. Confirm the registration, then retry once the index catches up.
- The feed provider, or the organisation's price-feed signer, does not answer. Once the dependency recovers, inspect the original operation before deciding whether to submit again.
- A queued write returns a transaction reference the platform cannot confirm. Check the original request status before submitting again.
Five failures are never worth retrying as sent: a topic name and topic id that resolve to different topics, a submission to a feed that already holds an answer newer than chain time (400, FEEDS_, DALP-0639), a value with more decimal places than the feed stores (400, FEED_, DALP-12245), a submitted value of zero or below (400, FEED_VALUE_NOT_POSITIVE, DALP-14446), and an issuer-signed create for a subject and topic that already has a feed (409, FEED_ALREADY_EXISTS, DALP-14445). Each needs a corrected request, not a repeated one. The platform refuses the last three before it queues a transaction, so they cost no gas. A create repeated with its original idempotency key returns its first result, not the 409. A write that an operator cancels in the transaction queue before it settles answers 409, market- (DALP-20525). It is not retryable as sent: nothing was published and the platform starts no new attempt, so send the write again with a new idempotency key if it is still wanted. A strict submission from a price source ends the same way and does not retry the cancelled write.
A write the queue has accepted and has not settled is checked every five seconds for its first minute, then at growing intervals: 90 seconds, doubling to a 15-minute cap. Its answer can therefore arrive up to 15 minutes after the write settles on a stalled sender.
Next steps
- V1 API reference and V2 API reference for every feed route's full parameters, filters, and response schema.
- Idempotency and async settlement for the sync-versus-queued response shape every feed mutation and submission uses.
- Errors for the
retryableflag and category on every feed error code. - Authentication and API keys for the session-or-API-key credential every feed route accepts.