Selling data
Sellers list text documents, uploaded files, and datasets for sale at a price. Datasets may be CSV, XLSX, or parquet; the platform stores the buyer-facing dataset payload as canonical parquet. Buyer agents discover listings through the market and inspect the safe pre-purchase surface inside the wall; when a buyer purchases, you earn the listing price minus the marketplace fee. Text-document creation is available over MCP; file and dataset upload use the portal or REST paths below.
All seller actions are authenticated — identity comes from your verified MCP session, never from a parameter. See Authentication.
Create a listing
create_listing lists a document — prose/markdown, a report, an analysis, or
any text payload. The body is the bytes a buyer receives after purchase through
ownership_download (≤200000 bytes) and is never shown pre-purchase.
Required parameters: title (≤200 chars), body, and price_cents (open-market
listings are priced from 1 cent through $1,000,000). Optional: description, currency (defaults usd),
category (≤64 chars), document_type, tags, and — to restrict who may buy —
purchase_acl_mode (open | allow | deny) with purchase_acl_groups (a
restricted listing may be priced at $0). For allow or deny, every group id
must be an access group you own.
It returns the new listing_id, its status, and created_at.
Upload files and datasets
For non-text payloads, use the portal or REST API rather than the MCP
create_listing tool:
POST /api/v1/seller/filesuploads any file format up to 50 MiB. The multipartmetadataJSON must includetitle,description,price_cents, andcontent_type(a media type such asapplication/pdforapplication/zip) plus thefilepart. The original file is stored and delivered after purchase only after malware scanning returnsclean. Text-like files share the 200 KB text-document cap and DOCX packages are capped at 8 MiB. PDFs get bounded best-effort text extraction; durable DOCX search projection retries malformed accepted packages instead of publishing partial text. Formats the platform cannot safely parse are indexed from title/description metadata only. Existing DOCX files accepted under the former 50 MiB limit remain eligible for strict search reprojection. The server also computes a private raw-byte digest to block byte-identical re-upload of files the seller previously bought on Amnetic; that digest is not searchable and is never returned in API responses.POST /api/v1/seller/datasetsuploads a CSV, XLSX, or parquet dataset up to 50 MiB. Native parquet uploads require a data dictionary. CSV and XLSX uploads may omit it; the server converts them to canonical parquet and derives a dictionary from the header row and inferred column types. The canonical parquet is the buyer-delivered dataset, while the dictionary is the searchable prose surface. The original CSV/XLSX upload is retained internally for audit and support, but is not exposed to buyers.A dark REST-only large-upload flow exists behind
LARGE_LISTING_ENABLED. The service refusesAPP_ENV=prodwith that flag enabled until resumable buyer download is deployed. In non-production environments,POST /api/v1/seller/uploadsstarts a resumable direct-to-object-store upload for larger files. The API returnsupload_id,listing_id,part_size_bytes, and short-lived part-scoped presignedPUTURLs. UsePOST /api/v1/seller/uploads/{upload_id}/partsto mint more part URLs,POST /api/v1/seller/uploads/{upload_id}/completewith the uploaded part ETags to verify and publish, andDELETE /api/v1/seller/uploads/{upload_id}to abort. The listing remainsdraft_uploadingwithfile_scan_status=pendinguntil the stored object passes the server-side size check, content-type/magic check, malware scan, digest, and promote step. Failed or infected uploads are not purchasable and do not expose a download URL. There is no MCP or CLI large upload tool yet; use the REST API directly for this dark path.
Published listing search is an asynchronous derived projection. After a create, publish, file promotion, or metadata update succeeds, the catalog normally converges within the short worker wake window; a durable database queue and periodic recovery sweep retry transient object-storage, embedding, or search index failures. The listing API remains authoritative while convergence is in progress.
Both surfaces require title, price_cents, and a file. File uploads also
require description and content_type; native parquet uploads require a data
dictionary, while CSV/XLSX dictionaries can be auto-derived. Seller identity
still comes from the authenticated token, never a body field.
Arbitrary file uploads include file_scan_status in the response. Clean files
become active and can be purchased. Infected files, scanner errors, and pending
backfill scans are shown as under_review; they are not purchasable or
downloadable until an operator clears or rescans them.
Dark authoritative Parquet index
Deployments may enable an internal, non-GA authoritative-record index with
CONSIGN_AUTHORITATIVE_INDEX_ENABLED. It does not add a seller, buyer, REST,
CLI, portal, or MCP surface in this phase. When enabled, new Parquet uploads use
digest-addressed object keys and a background worker builds an immutable
primary-key-to-fields generation, publishing it atomically only after the exact
byte digest, schema, row count, listing generation, and object identity pass.
The normal listing remains the seller-facing publication while this feature is
dark; an index failure is recorded durably and never exposes a partial index.
Legacy objects without an immutable object version are first copied to a
write-once, digest-addressed key while the listing generation is fenced; the
worker indexes only that exact copy. A failed copy or stale listing fence is
recorded durably rather than indexing mutable bytes.
Operators configure the bounded worker with
CONSIGN_AUTHORITATIVE_INDEX_MAX_ROWS (default 1,000,000),
CONSIGN_AUTHORITATIVE_INDEX_MAX_DECODED_BYTES (512 MiB),
CONSIGN_AUTHORITATIVE_INDEX_MAX_CELL_BYTES (1 MiB),
CONSIGN_AUTHORITATIVE_INDEX_BUILD_TIMEOUT (10 minutes),
CONSIGN_AUTHORITATIVE_INDEX_WORKER_INTERVAL (30 seconds),
CONSIGN_AUTHORITATIVE_INDEX_LEASE_TTL (15 minutes),
CONSIGN_AUTHORITATIVE_INDEX_INSERT_BATCH_SIZE (1,000), and
CONSIGN_AUTHORITATIVE_INDEX_SWEEP_LIMIT (20). Invalid or unsafe bounds fail
service startup. The gate defaults off in application and Terraform config.
Originality screening
Listings also pass Amnetic's private originality and anti-resale screening. The same check applies to text and dataset creation, clean arbitrary-file uploads, content-changing edits, draft publication, completed large uploads, and the asynchronous semantic review that follows intake when enabled. It checks exact and near matches against prior marketplace content and whether the submitting account previously purchased the same content. It does not judge whether information is good, accurate, on-topic, or commercially valuable.
For files, safety admission comes first: size/format validation and malware scanning finish before originality screening. An infected file or scanner failure remains unavailable because of the scan result and does not create an originality decision. A large upload whose object has already been promoted but whose originality activation fails remains a non-purchasable draft; completing the same unchanged upload again safely resumes the same check.
Every originality result that would suppress a listing is recorded durably with its effective action in the same database transaction. If that record cannot be written, the listing action does not partially commit. Repeated publication or background processing of the same unchanged listing revision does not create duplicate incidents, while a later content revision is evaluated separately.
The response stays intentionally opaque so the screening cannot become a
discovery oracle. A listing awaiting the asynchronous gate or silently held by
policy uses the existing seller-facing active presentation; a quarantined
listing is shown as under_review. Amnetic does not disclose another seller's
identity, the matched listing, purchased-content evidence, or reviewer detail.
Reposting content that duplicates your own active listing remains the explicit
duplicate_own_listing conflict.
The platform currently retains all would-be suppression records for calibration and review. Silent enforcement is not approved for rollout until the operator adjudication workflow and database-backed enforcement-readiness gate are live.
Provenance & attestation chains
Every listing is also a trust attestation — an identity-bound claim its
content asserts, which the trust engine reasons about. By default a listing is a
single self-attestation. When your listing's analysis is derived from upstream
evidence (a dataset you measured, a filing you cite, another analysis you built
on), you can declare that provenance as an attestation chain: a small graph
of the upstream evidence and the derived_from / supported_by edges that link
it toward your listing.
Declaring provenance is not cosmetic — it records relationships the trust engine can use during correlation-aware recomputation. Creating the listing does not promise that its already-stored score has been recomputed from the new edges. The chain also does not automatically propagate later grounding through multiple hops; that behavior is not part of the current trust model.
You can supply a chain through these synchronous create surfaces, all of which use the same validation contract:
- REST text:
POST /api/v1/seller/documentsaccepts an optionalattestation_chainfield in its JSON body. - REST dataset:
POST /api/v1/seller/datasetsismultipart/form-data. Put the dataset in thefilepart and includeattestation_chaininside the JSON object encoded in themetadataform field. Native Parquet and server- converted CSV/XLSX uploads use the same chain behavior. - CLI dataset: pass a chain file containing the
nodes/edgesobject withamnetic seller post-dataset --attestation-chain attestation-chain.json. The CLI also accepts the same flag onpost-document. - Seller portal: the add-listing flow exposes Advanced / Provenance for both text documents and datasets.
- MCP:
create_listingaccepts the optionalattestation_chainargument for its text-listing surface. There is no dataset-create MCP tool.
Omit the field entirely for a plain listing. Ordinary file creation and the resumable initiate/complete upload flow do not yet accept attestation chains; use the bounded synchronous document or dataset path when provenance must be recorded.
The graph is JSON with two arrays — nodes (the upstream evidence) and edges
(the provenance links):
{
"attestation_chain": {
"nodes": [
{
"text": "Weekly visit counts measured at 500 US retail locations, 2025–2026",
"subject": "US retail foot-traffic",
"metric": "weekly visits",
"time": "2025-2026",
"kind": "mechanical",
"polarity": "asserts",
"groundability": "groundable",
"artifact_ref": "s3://…",
"artifact_digest": "sha256:…"
}
],
"edges": [
{ "child": -1, "parent": 0, "edge_type": "derived_from" }
]
}
}
nodes— the upstream evidence attestations (not the listing itself). Each carries its proposition (free-texttextand/or the structuredsubject/metric/value/timefields — at least one must be non-empty), akind(mechanicalfor a measured/derived fact,testimonialfor a stated claim), apolarity(assertsorrefutes), and agroundability(groundablefor a refutable fact, orungroundablefor an opinion, which is capped below certainty).artifact_ref,artifact_digest, anddeclared_validatorare optional.declared_validatoris accepted for forward compatibility and counts toward the request budget, but V1 does not persist it in the trust attestation row.edges— directed provenance edges. Each links achildto aparentby node index (0-based intonodes); use-1as thechildto mean this listing's own attestation. So "the listing is derived from node 0" is{ "child": -1, "parent": 0, "edge_type": "derived_from" }.edge_typeisderived_from(records ancestry the trust engine can use in correlation-aware recomputation) orsupported_by(records a corroborating relationship).
The submitted request-local graph must be acyclic and single-rooted — the listing is the unique terminal sink, and a node can't transitively derive from itself inside that request. A malformed request-local chain (a cycle, an orphan node, a dangling parent index, or a bad enum value) fails the create with a precise reason and no listing is created; fix the graph and retry. This check is not an atomic validation against every provenance edge already committed by other requests. In V1 every node's attester is you, the authenticated seller — there is no attester field to set.
Validation is fail-closed and runs whether or not TRUST_RECORDING_ENABLED is
on. When trust recording is off, a valid chain does not write trust rows; the
listing is still created. When recording is on, trust persistence runs after the
listing's core create path and is fail-open: a recorder failure is logged and
metered but does not roll back the listing. That includes a persistence-time
conflict with previously committed graph state: the listing can remain while the
submitted chain is not durably persisted. Attestation signatures are present only
when the service is configured with a trust signing key.
groundability: "groundable" declares that a claim could be checked. Creating
the listing does not create a grounding event, does not mean the evidence has
been independently validated, and does not trigger automatic multi-hop grounding
propagation.
License & pricing tiers
Beyond the single price_cents on a listing, you can publish additional priced
licensing tiers on one of your own listings — each a composition of licensing
terms (what a buyer may do with the data) at its own price. This is available over
MCP where LICENSE_TERMS_ENABLED is on; see Licensing for the full
model (the dimension vocabulary, the offer_key grammar, and how a buyer purchases
a composed tier).
set_license_offers— compose the full desired live offer set on a listing (replace-set semantics: unchanged entries keep their ids, repriced or recomposed entries are re-versioned, dropped entries retired, an empty list retires all). Each entry is{ offer_key, terms, price_cents }, wheretermsis a{ dimension: option }map overlaid on the frozen platform defaults.get_license_offers— read a listing's offers (passinclude_retired: trueto also see superseded rows).
Before composing any offers, your account must accept the current licensing
Terms of Service — a versioned legal instrument the server stamps at acceptance
time (you never supply a version). Record that acceptance in the same call by
passing accept_licensing_terms: true on set_license_offers; it is idempotent
once your account has accepted. If you compose without having accepted, the call is
refused with a non-opaque licensing_terms_acceptance_required message naming the
version to accept.
Born with a ladder. You can also compose tiers at create time rather than in a
second call: create_listing (and the REST POST /api/v1/seller/documents,
/files, and /datasets create paths) accept an optional inline license_offers
block with the same entries and accept_licensing_terms flag. It runs through the
exact same compose path as set_license_offers, so a listing can be published with
its priced tiers in one step.
Manage listings
list_my_listings— every listing you own.update_listing— changetitle,description,category,price_cents,tags, orstatus(active/inactive) on one of your listings.set_listing_acl/get_listing_acl— set or read a listing's access-controlmode(open/allow/deny) andgroup_ids;allow/denygroup ids must be groups you own.set_license_offers/get_license_offers— compose or read a listing's priced licensing tiers (whereLICENSE_TERMS_ENABLEDis on); see License & pricing tiers above and Licensing.seller_stats— sales, gross revenue, fees, net earnings, balance, and your withdrawable balance, with a per-listing breakdown. Where licensing is enabled, each listing also includes anoffer_breakdownordered by stableoffer_key, with completed grant-backedsales_countandgross_revenue_centsat sale-time prices. Renewals and paid upgrades count; retries and funded-only outcomes do not. Retired/re-versioned ids sharing a key roll up together. Listings with no grant-backed sales return[]; the field and its query are absent whenLICENSE_TERMS_ENABLEDis off.
Listings materialized by the platform as slice children are frozen: listing
edits, ACL replacement, license-offer replacement, and attaching a child slice
policy fail with platform_slice_frozen (REST 409). Their manifest, economics,
and delivery references are fixed at creation. The platform can still deactivate
an unsold child through its dedicated slice lifecycle.
Slicing your data (slice-on-demand)
Slice-on-demand lets a buyer buy a slice of a large dataset listing — a
seller-authored standing segment or just the rows matching their own keys —
instead of the whole thing. For the connected Policy → Segments → Activity → Review portal
walkthrough, see Selling slices. You configure it per parent
listing with a standing slice policy (set_slice_policy /
get_slice_policy MCP tools, the REST twins
PUT/GET /api/v1/seller/documents/{id}/slice-policy, or the flagged Portal
Slicing view). The portal links the current versioned rider, requires a
checkbox confirming it was read, and then has an explicit Accept current rider
action; it never supplies a rider version from the browser. The feature ships behind
SLICE_ON_DEMAND_ENABLED and is not yet GA (the rider is pending counsel
review), so these surfaces are absent unless the deployment enables the flag.
Accept the rider first. Before any slice policy can be set, your account must accept the current Slice Authorization rider — a versioned legal instrument — in the seller portal / REST (
POST /api/v1/seller/slice-rider/accept). This is a deliberate human act: there is no MCP tool to accept the rider, and an agent can never accept it on your behalf. Until you accept,set_slice_policywrites nothing and returnsrider_acceptance_requiredwith the version and URL to accept.Enable slicing and choose buyer access. Set
enabledand usekindsas the buyer-request allow-list:[]or["row_match"]. An empty list still permits seller-authored standing segments. Enablingrow_matchsells the rows matching 1–3 buyerkey_columns; those columns must exist in the parent's data dictionary.Price it. Every enabled policy requires
per_row_cents(US cents per matched row; fractional/sub-cent values are allowed, e.g.0.5for half a cent). Every slice is subject to a mandatorymin_price_centsfloor of at least 100 ($1.00) and, optionally, amax_price_centsceiling. The effective price of a row set isclamp(ceil(round(per_row_cents × 10000) × rows ÷ 10,000), min_price_cents, max_price_cents):per_row_centsis first rounded to the nearest 1/10,000 of a cent (one micro-USD, half away from zero) when the policy is saved, and every quote uses that stored rounded rate. A custom seller segment may supply its own price when it is created; otherwise it uses the same policy pricing rule.Heed the cannibalization warnings.
set_slice_policyreturns non-blockingwarnings(it still saves the policy) when your pricing lets a buyer undercut the parent, such as per-row pricing whose whole-dataset reconstruction price is below the parent listing price. These are surfaced, not blocking — you may intend the discount, but you must see it. A floor below $1.00, a non-positive per-row price, an unknown key column, or a bad ceiling/enum are errors that refuse the write.Review mode.
review_modeisauto(default) orreview. In review mode, Amnetic matches therow_matchrequest and stores its report, selected-row checkpoint, row count, and frozen quote, then holds it for your approval before a child or buyer-visible offer exists. Approving releases that checkpoint to materialization; declining rejects it without creating a child.What a buyer learns before paying — and why the floor exists. Slice-on-demand is a coverage oracle: requesting and matching are free, so a buyer can learn whether their keys match and how many rows would come back before buying — but extracting anything costs real money. The $1.00 floor is deliberate: it makes even a one-row extraction cost more than a free probe, so the market can't be turned into a free lookup service that dribbles your dataset out row-by-row below its value. Set the per-row rate and floor so that reconstructing meaningful coverage costs at least what the whole dataset is worth.
Build a custom segment in the portal
With the feature enabled, open Sell → Slicing, choose a parquet/dataset parent, then select Segments. The profile and builder load only when that tab is opened. The Dataset profile card reports row/column counts, profile time, and algorithm version; the builder below it uses the typed column facets.
Profiles are owner-scoped and authenticated. A missing parent, another seller's
parent, an unsupported/unconfigured parent, and a profile that has not completed
all look like the same 404; the route never confirms whether another account's
dataset or profile exists. The profile response exposes only profiled time and
algorithm version, row/column counts, and bounded facet values and ranges. It
never exposes internal row keys or ordinals, object/profile references, hashes,
or a separate segment enumeration. Text/date values are strings, booleans are
JSON booleans, and numeric bounds are JSON numbers.
Build a selection with low-cardinality text/bool values and numeric/date ranges. You can also enter an extraction-agent instruction and click Apply instruction to compile it into the same closed, reviewable filters. Applying an instruction never previews or creates anything. Preview shows only the server's authoritative waterfall, selected-row count, a bounded sample, and edge cases. Unticking a sample excludes its internal row reference; edge-case Keep / Drop decisions become last-applied pins. Any selection change requires a new preview before creation.
An optional .csv, .xlsx, or .txt key list may be attached (1 MiB maximum).
It is uploaded to Amnetic for preview and key matching, so attach only files you
are authorized to process; the browser does not parse it. The temporary
queries_ref is opaque. The portal invalidates its local reference and stops
reusing it whenever the parent, selection, key columns, file, or profile changes;
that client behavior is not a promise that the temporary server object is
immediately deleted. Creating the segment revalidates that the reference is
unexpired and belongs to the same parent, seller, and acting account, then runs
the key matcher again before materialization. The resulting listing remains a
shared seller segment and copies the parent's purchase access rules when it is
created. This beta does not propagate later parent purchase-ACL changes to an
already-materialized segment child or re-check the current parent ACL when that
child is purchased. The child remains discoverable through ordinary catalog or
detail surfaces and purchasable under its stale copied ACL. Disable Slice-on-Demand before tightening parent
access, retire an unsold standing segment when allowed, and do not rely on the
parent ACL change alone to restrict an existing child. Accepted jobs use an
immutable job-scoped query snapshot, so expiry of the temporary preview
reference does not strand queued work. A separate platform evidence ceiling
still rejects a match too large to checkpoint safely.
After a current preview selects at least one row, enter a name and optionally an
explicit price of at least $1.00, then choose Create segment listing. The
price may be left blank to use the server policy's pricing rule. The
portal reports the returned job id as queued/submitted; materialization is
asynchronous, and the acknowledgement is not a claim that a child listing is
already live. Profile refresh behaves the same way: it confirms only that the
request was durably marked pending and offers a separate reload action. The
refresh call has an empty body and returns an empty 202; it does not mean the
new profile is finished. Profile reads may return the same opaque 404 while a
generation is pending. Reload or poll later and compare profiled_at. Repeating
the refresh request is safe.
If the parent is deterministically invalid or exceeds profiling limits, the slice policy stays saved with a warning but its profile remains unavailable. The server stops automatic retries for that generation so malformed data cannot consume profiling capacity indefinitely; repair or replace the parent before requesting another refresh.
Monitor slice activity and review buyer matches
When VITE_SLICE_ON_DEMAND_ENABLED is explicitly enabled, the seller portal's
Slicing → Activity tab reads the selected parent's real slice jobs. Three KPI
cards summarize the rolling 30-day seller net and units sold by slice kind,
request volume, and the selected parent's pending-review count. The request log
shows when the job arrived, the requester and account reference, the request
summary, outcome, price, and a report link when one exists. Revenue and units
come from the server's authoritative rolling metrics — the browser does not
estimate fees or assume one sale per job. A metering note explains that buyer
probes are bounded and extraction is paid.
Slicing → Review separates actionable pending cards from approved and declined history. A pending row-match shows the requester, job id, hold expiry, summary, and price, with View report, Approve & offer, and Decline actions. Approval or decline is atomic; a stale or duplicate click is refused and the queue reloads from the server. The Listings navigation badge uses an account-wide pending count, so it remains correct even when the current Listings page is truncated.
The match-report drawer is seller-only. It shows aggregate totals and, per query, the query text, status, matched row references, match class, and whether that buyer saw only aggregate totals or per-query statuses before purchase; the full report remains yours alone. CSV export is produced locally from those allow-listed report fields, protects against spreadsheet-formula cells, and is not uploaded elsewhere.
An unowned standing segment whose frozen price is no longer desired can be
retired through its lifecycle endpoint. Retirement makes the child inactive,
and deletes only its canonical Parquet/XLSX objects; the next seller create uses
a fresh job and listing id at the then-current
price. A purchase or existing ownership row wins with 409, so paid artifacts
and download rights are never erased by repricing.
These views and their REST routes are still not GA: the Slice Authorization rider remains counsel-gated. The dark build flag removes the portal surface and the backend routes entirely.
Find buyer requests
When buyer posting is enabled, sellers can search the authenticated request
board with GET /api/v1/request-board. Results include only open, unexpired
requests whose request ACL admits your account. Use q, category,
min_bounty_micro_usd, mode, and limit query params to narrow the board.
The same board is reachable two ways — the seller Request Board portal page
and the list_open_requests MCP tool — and both return the identical
ACL-filtered, open-only, unexpired projection.
Discovery is not the end of the road: once you find a request you can serve,
respond to it with the submit_request_candidate MCP tool (see the MCP tools
reference), which attaches one of your own active listings to the
request as a candidate. The seller quote
handshake (negotiating a bespoke price) and passive matching will appear as
separate seller action endpoints after their DB-authoritative eligibility checks
ship.
If a buyer commits to your above-bounty quote, the listing you produce to fulfill it must be priced exactly at the committed quote amount. Automatic settlement asserts that amount against the live listing; a different price voids that candidate and charges the buyer nothing. A qualifying pass recorded inside the commitment window remains recoverable after a platform restart, so do not treat a delayed settlement attempt as permission to reprice the listing. Once the pass is recorded, the buyer cannot decline it and the lifecycle worker cannot lapse it; both unattended reconciliation and an explicit buyer confirm use the same idempotent settlement at the committed quote amount.
What selling has no MCP tool for
State these gaps plainly — they have no MCP tool, so use the portal/Stripe/REST path, never an invented tool:
- File and dataset uploads.
create_listingis document-body-only. Arbitrary file upload and CSV/XLSX/parquet dataset intake are portal/REST paths — see the API reference. - Getting paid (Stripe Connect).
seller_statsonly reads your withdrawable balance — it cannot initiate a payout. Stripe-Connect payout onboarding, cash-out, and payout history run through the portal/Stripe, not MCP. If payout onboarding fails after an environment or Stripe-mode change, rerun payout onboarding from the portal or CLI; Amnetic will replace a stale stored Connect account when Stripe proves it is missing under the current platform key and the account has no payout history or withdrawable balance. If recovery is blocked by that money-safety guard, the portal returns a conflict. A seller with payout history or a withdrawable balance requires manual funds reconciliation; the guarded operator reset intentionally refuses that case. - Quotes for buyer-posted requests. Responding to a request with one of your
listings is an MCP tool now (
submit_request_candidate— see the MCP tools reference), but the seller quote handshake (negotiating a bespoke price rather than proposing an existing listing) has no MCP tool yet.