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.

For the model-layer handling of content, see Where your data goes, our public seller and buyer disclosure.

The format you publish in decides how deeply a buyer agent can evaluate your listing before buying. A dataset gets its schema and sample rows read, and — if it is larger than the platform renders whole — targeted row-group reads on top. A text document gets its opening 256 KiB, and byte windows beyond that. Any other uploaded file (PDF, DOCX, zip, a raw CSV file) is metadata-only in the wall: its bytes are never read pre-purchase, so its title, description, and extracted search text carry the whole listing. Inside the wall has the per-format detail.

Consign verdict feed

If Consign Enforcer has a case about one of your datasets, the seller portal's Verdict feed shows the owner-scoped case history. The REST equivalent is GET /api/v1/seller/consign/cases; it accepts a Cognito ID token and never accepts seller or buyer identity in the URL or query. Account API keys cannot reach this seller-human surface.

Each reviewed case includes the sealed evidence-package reference and keeps the reasoning dimensions separate: fact, fingerprint match, statistical inference, contextual indicator, alternative explanation, and unverified assumption. A case without a review remains verdict: null; no intake snapshot is presented as a live verdict. Evidence object-store paths, reviewer identity and signatures, private account ids, and raw candidate membership are not exposed. The API reference documents the response shape.

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 (up to 32, each ≤64 chars), 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

Original-file intake preserves the uploaded bytes, including Excel formulas, formatting, macros, links, and worksheets. Optional query projections are separate artifacts: failed or over-budget conversion is disclosed in original_file.projection_gaps, not silently removed from the original.

For agent uploads, call stage_artifact with purpose: "listing_create", filename, content_type, size_bytes, and the full-file content_sha256. The default mode: "single_put" supports up to 256 MiB. Upload to the returned URL with every required header, then call create_listing with public metadata and staged_artifact: {"stage_id": "…"} instead of body.

For larger originals, mode: "multipart" supports up to 100 GiB and requires part_size_bytes (at least 5 MiB). Use presign_artifact_parts for additional or refreshed signed part URLs and complete_artifact_upload with the uploaded part numbers and ETags. Completion verifies the assembled upload; it does not publish a listing. abort_artifact_upload releases an abandoned upload. Shared staging limits are five open uploads and 100 GiB per account across purposes. The derivation staging purpose currently supports single PUT only.

To replace an original, stage with purpose: "listing_replace", parent_listing_id, and expected_version, then call replace_listing_artifact with listing_id and staged_artifact. Replacement appends a version; existing owners retain their purchased version. Derived listings remain subject to their separate mutation restrictions.

Publication malware-scans the first 2,000,000,000 bytes (the entire file if smaller). The remainder is not malware-scanned. The complete original is still hashed and checked against the declared digest. scan_coverage discloses the scanned byte count, total byte count, and whether coverage is partial. Malware found in the required region, incomplete scanning, and scanner failures block publication. A successful partial scan does not establish that the rest is safe. Executable content is flagged; platform services do not execute it.

The portal and REST API use the same original-preserving intake:

  • POST /api/v1/seller/files uploads any file format up to 256 MiB. The multipart metadata JSON must include title, description, price_cents, and content_type (a media type such as application/pdf or application/zip) plus the file part. The original file is stored and delivered after purchase only after the required scan region is accepted. 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/datasets uploads CSV, XLSX, or parquet using the same original-preserving intake. Query conversion remains bounded separately from the upload limit. A data dictionary describes queryable data; it does not replace or alter the uploaded original.

    An XLSX with multiple data-bearing worksheets and no explicit legacy sheet selector is published as one whole-workbook listing. Worksheets are not sold separately. Visible, hidden, and very-hidden data-bearing worksheets are preserved, including empty, cover-only, and chart-only sheets. A sheet without usable tabular data may have no query projection; that does not remove it from the original file.

    The listing's deliverable is the original uploaded workbook, not a values-only reconstruction. Before upload, the portal requires the seller to accept the versioned workbook-publication acknowledgement shown below; REST clients must send the same acknowledgement metadata:

    {
      "workbook_publication_acknowledged": true,
      "workbook_publication_ack_version": "workbook-original-publication-v1"
    }
    

    Acknowledgement covers original-workbook publication, including its logic and embedded features. It is required for multi-worksheet and binary Excel originals. Worksheets are not separately priced products.

    A data row wider than its header is preserved losslessly under generated column labels and reported as data_row_wider_than_header, with its sheet, row, first extra column, correction, and reduced column_labels feature. Invalid or encrypted packages are refused. Projection limits produce query gaps while preserving the accepted original.

  • The REST large-upload flow uses LARGE_LISTING_ENABLED. POST /api/v1/seller/uploads starts a resumable direct-to-object-store upload for larger files. The API returns upload_id, listing_id, part_size_bytes, and short-lived part-scoped presigned PUT URLs. Use POST /api/v1/seller/uploads/{upload_id}/parts to mint more part URLs, POST /api/v1/seller/uploads/{upload_id}/complete with the uploaded part ETags to verify and publish, and DELETE /api/v1/seller/uploads/{upload_id} to abort. The listing remains draft_uploading with file_scan_status=pending until the stored object passes the server-side size check, content-type/magic check, parquet footer validation when content_type is application/vnd.apache.parquet, malware scan, digest, and promote step. An invalid parquet footer fails closed with 400 (quarantine object deleted; listing not purchasable). Failed or infected uploads are not purchasable and do not expose a download URL. Once any buyer has purchased a listing its bytes are final: complete refuses with 409 rather than replacing them, and the purchased object is left untouched, so every past buyer keeps re-downloading exactly the bytes they paid for — sell changed content through a new version using the staged replacement flow. Agents can use the multipart MCP staging tools described above.

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. Multi-sheet XLSX publication additionally requires the current workbook acknowledgement fields above; the retired worksheet_price_cents field is rejected. Single-sheet XLSX, CSV, and native Parquet uploads keep their singular response shape, while a single-sheet XLSX may add top-level workbook-format warnings. 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.

Consign canary registration (dark; pending counsel)

Where CONSIGN_ENFORCER_ENABLED and the separate CONSIGN_CANARY_ENABLED gate are both on, a seller using an account session may register one reviewed canary set with POST /api/v1/seller/consign/canary-config. The route uses a Cognito ID token; an amn_… account API key cannot perform this legal acceptance. Cognito authentication does not itself prove physical human presence. The route is absent while dark.

The registration names one of your listings, its current licensed-delivery context (license_grant_id), a closed dataset classification, and one to 100 fabricated JSON-object values. Each value has a unique record_key and a plausibility status of pending, approved, or rejected. general datasets default to seeded and may explicitly choose sidecar. regulated_ttb_cola datasets always resolve to sidecar; the API rejects seeded so a fabricated identifier is never inserted into TTB's real COLA namespace.

First GET /api/v1/seller/consign/canary-config and present its exact document_markdown. Registration must send both accept_authorization: true and the returned full authorization_sha256; a stale or missing hash is rejected. The server pins and stamps the current draft version and SHA-256 (canonical source: docs/legal/consign-canary-authorization.md). The draft is pending counsel review (OD-CE1), is not yet effective, and is not legal advice. Any wording change mechanically supersedes it with a new content-derived version. Acceptance, the current ready-generation fence, rotation cycle, encrypted canaries, and the listing's enforcement opt-in commit together or not at all. Exact retries return the original evidence. Canary plaintext is never returned and is stored only as a dedicated KMS-wrapped envelope.

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.

If Amnetic cannot finish processing a listing at all — a platform-side failure, not a decision about your content — the listing is set to inactive and flagged for an operator. That is deliberately distinct from under_review: nothing about it is a finding against you. It is also deliberately not the opaque active presentation above, so a listing that is not live never reads as live.

To retry it, set the listing's status back to active. That is what re-runs processing from scratch; editing an inactive listing's content on its own does not — an inactive listing stays inactive until you explicitly reactivate it, so change whatever you want to change first and then set status to active in the same request or a following one.

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.

Delivery enforcement (dark; pending counsel)

Parquet-backed dataset listings can opt in to delivery enforcement: when enabled, each buyer download is served as a signed per-delivery bundle (your authoritative parquet unchanged, plus a per-delivery license notice) instead of the bare object, and every delivery is recorded on an append-only ledger. This is a per-listing opt-in (enforcement_enabled, default off) gated behind the platform flag CONSIGN_ENFORCER_ENABLED, which is off by default and stays dark pending counsel — no seller-visible download behavior changes until it is enabled. Enabling it on a listing that already has buyers does not re-mark past downloads; each buyer's next download is the first enforced one. Enforcement is supported only for parquet datasets — a non-parquet listing is not eligible. Enforcement should not be enabled on a non-parquet listing; if it nonetheless is, that listing's downloads fail closed with 503 materialization_unavailable (the bare, unmarked object is never served) rather than silently bypassing the marking choke-point.

Buyer materialization is protected by database-coordinated global and per-buyer/listing attempt limits plus an expiring fleet concurrency lease. The gate runs only after authorization and format checks, but before source download or bundle work. Quota exhaustion returns 429 materialization_rate_limited with Retry-After; capacity or dependency failure remains 503 materialization_unavailable. Each admitted request still receives unique delivery attribution—there is no final-artifact reuse.

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/documents accepts an optional attestation_chain field in its JSON body.
  • REST dataset: POST /api/v1/seller/datasets is multipart/form-data. Put the dataset in the file part and include attestation_chain inside the JSON object encoded in the metadata form field. Native Parquet and server- converted CSV/XLSX uploads use the same chain behavior.
  • CLI dataset: pass a chain file containing the nodes/edges object with amnetic seller post-dataset --attestation-chain attestation-chain.json. The CLI also accepts the same flag on post-document.
  • Seller portal: the add-listing flow exposes Advanced / Provenance for both text documents and datasets.
  • MCP: create_listing accepts the optional attestation_chain argument 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-text text and/or the structured subject / metric / value / time fields — at least one must be non-empty), a kind (mechanical for a measured/derived fact, testimonial for a stated claim), a polarity (asserts or refutes), and a groundability (groundable for a refutable fact, or ungroundable for an opinion, which is capped below certainty). artifact_ref, artifact_digest, and declared_validator are optional. declared_validator is 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 a child to a parent by node index (0-based into nodes); use -1 as the child to mean this listing's own attestation. So "the listing is derived from node 0" is { "child": -1, "parent": 0, "edge_type": "derived_from" }. edge_type is derived_from (records ancestry the trust engine can use in correlation-aware recomputation) or supported_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 terms

Every listing receives one real, immutable platform-default base offer at its price_cents. The previous seller set_license_offers / get_license_offers tools and inline create-time tier block are no longer available; seller-authored policy returns through the replacement terms workflow. See Licensing for the buyer-facing menu and offer-bound purchase contract.

Manage listings

  • seller_readiness — check this before you build a payload. No arguments, read-only, free. It answers "am I able to do this yet?" for your account: whether Stripe payout onboarding is complete (so a priced listing goes live instead of landing as a draft, a priced derived child is accepted, and your listings reach the buyer slice catalog), and — where those rails are enabled — whether you have accepted the one-time Derivation Authorization and Slice Authorization instruments. Each row carries a stable check id, a ready flag, what it means, the remedy when it is not ready, and the gated_tools it blocks. A gate that is not enabled here produces no row at all rather than a misleading true, and a report with no rows reads ready: false rather than claiming a readiness it never measured. Without it, every one of these is learned only by sending a real write and reading the refusal — or, in the slice catalog's case, never learned at all, because that one fails silently. It is account-scoped: for a single listing's publication state, see Is my listing in the public catalog? below.

  • list_my_listings — every listing you own.

  • update_listing — change title, description, category, tags (same bounds as on create), sample_data_enabled, or status (active / inactive) on one of your listings. A sidecar-less agent-derived child accepts only the one-way retirement {status: "inactive"} with no other non-null update field; it cannot be reactivated. Hosted seller-agent credentials remain unable to use this broad update tool.

  • get_listing_policy / set_listing_policy — the only way to change a listing's price or derivation pricing (REST twin: GET / PUT /api/v1/seller/documents/{id}/policy). The base rung's price_cents is the listing price. The policy envelope carries seller-private SAT clamps (min_price_cents, optional max_price_cents, and counter_floor_cents, in integer cents), and derivation_pricing carries the per-row rate (integer micro-USD) and optional buyer_type_adjustments. Adjustments are currently limited to business and enterprise, each either a positive-basis-point multiplier (multiplier_bp, 10000 = 1.0x) or an absolute override (price_cents).

    A derived purchase is priced at ceil(rows × per-row rate), clamped to the envelope; the listing price applies only when there is no per-row rate, and the two are never added. Enabling derivation pricing is a complete, atomic listing write: the listing price must be positive and within the listing cap, the clamps must meet the transaction floor, and a per-row rate is required — derivation_pricing.enabled: true without a rows cell is refused as derivation_row_rate_required, and any columns cell is refused as column_pricing_unsupported (column pricing is not yet supported). Valid clamp and adjustment values may remain dormant while pricing is disabled; their closed shape and bounds validate on every write. Seller listing reads include a read-only derived_pricing projection with the reconstructed total and a below-floor warning; buyers and public catalog reads receive none of the private pricing fields. Each price, policy, dictionary/default-rate, or deletion change also atomically recompiles the seller's complete immutable pricing evidence. An identical canonical result reuses the active version; a changed result appends exactly one version. Historical evidence is read-only: restoring old values is another reviewed policy write, never a whole-document write or activation.

  • set_listing_acl / get_listing_acl — set or read a listing's access-control mode (open / allow / deny) and group_ids; allow/deny group ids must be groups you own.

  • seller_stats — sales, gross revenue, fees, net earnings, balance, and your withdrawable balance, with a per-listing breakdown. Each listing also includes an offer_breakdown ordered by stable offer_key, with completed grant-backed sales_count and gross_revenue_cents at 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 is always present.

Is my listing in the public catalog?

status and public-catalog visibility are two different questions, and a listing can read active to you while the anonymous catalog does not serve it. GET /api/v1/listings/{id} returns a listing only when it is active, its purchase ACL is open, and — for a listing inside a sealed aggregate (a derived child, or a workbook root/worksheet) — that aggregate is published. Everything else — restricted, not active, awaiting sign-off, or held by the asynchronous originality screening described above — collapses to the same opaque 404 {"error":"listing not found"}, so the endpoint can never be used to probe another seller's inventory.

So that you never have to guess, the seller surfaces answer it directly for your own listings, with the same public_visibility block everywhere:

{"public_visibility": {"visible": true, "reason": "visible"}}

It rides on every seller-facing view of your own listing, not just the list:

  • GET /api/v1/seller/documents (the list), and on MCP the per-listing read get_my_listing and the list list_my_listings;
  • the create responses — POST /api/v1/seller/documents, /datasets, /files, and the large-upload completion — and MCP create_listing;
  • the edit and publish responses — PATCH /api/v1/seller/documents/{id} and POST /api/v1/seller/documents/{id}/publish — and MCP update_listing.

The one exception is a multi-sheet workbook upload, whose create response is the workbook aggregate (root plus its workbook block) and carries no public_visibility; read the root's state from the list or get_my_listing.

This matters most on the write responses: an accepted write reports the status you asked for, which is not a claim that the catalog is already serving the listing. A create or a content edit re-runs the screening above, so a response can honestly say "status": "active" and {"visible": false, "reason": "pending_publication"} in the same breath. public_visibility.visible is the field that answers "is it live?".

visible is evaluated with the same predicate the public route runs — it is exactly "that public URL resolves". The reason vocabulary is unchanged and identical on every one of those surfaces: visible, not_active, restricted_access, pending_publication. When visible is false, reason is one of:

reason Meaning
not_active The listing is not active — your own status (draft, unlisted, under review) already says so.
restricted_access You restricted who may see and buy it; eligible buyers still find it inside the wall.
pending_publication It reads as active and is not access-restricted, but the catalog is not serving it yet.

pending_publication is deliberately one coarse bucket covering every remaining cause, including asynchronous screening: Amnetic does not report a screening disposition to anyone, including the listing's own seller. The portal gates its "View public listing" affordance on visible and shows the reason inline instead of sending you to the opaque 404.

If public_visibility is absent from a response, the check was unavailable — that is unknown, not hidden. It never fails a read or a write; re-read the listing to get an answer.

Listings materialized by the platform as slice children are frozen: listing edits, ACL 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.

Workbook roots and their worksheet listings are also frozen after publication. Seller listing and ACL mutations return workbook_frozen (REST 409). Deactivating the owned root retires the complete aggregate; its membership, artifacts, prices, and provenance remain immutable.

Deriving listings with a Seller's Agent

Opt-in seller capability. The surfaces below exist only when DERIVED_LISTINGS_ENABLED is on. They are enabled on the reviewed production seller-derivation deployment and remain off by default elsewhere. The parallel Derivation Authorization counsel track (AMN-655/AMN-656) is not a prerequisite for that deployment's Terms-of-Service consent posture.

A derived listing is a smaller, purpose-built piece of a dataset you own — an extract, a row subset, a column projection, or a summary — sold on its own. Your own Seller's Agent (loaded from src/agent-skills/seller-agent/SKILL.md) downloads one of your parent listings (owners may ownership_download their own listings), derives a faithful extract, self-verifies it against the buyer's ask, and stages it with create_derived_listing (large artifacts first go through stage_derived_artifact — a presigned quarantine PUT ≤256 MiB — rather than raised inline MCP body caps). Every derived listing is born pending sign-off: hidden, uncatalogued, and unpurchasable until you approve it.

If a pending SAT deliverable needs correction, the human seller can use the portal's Replace pending deliverable control on its Change Request. This is a separate Cognito-only REST intake: replacement bytes are uploaded to a quarantine URL, checked for malware and resale eligibility, and then exposed as a new immutable child and successor Change Request only after an atomic gate success. The predecessor remains actionable during intake and gating; failures, timeouts, cancellation, and predecessor drift leave it unchanged. Replacement upload and status polling never approve a Change Request, create an offer, or move money. The seller must explicitly review and approve the successor through the normal Change Request route. Seller agents and MCP have no replacement upload tool.

  • Two one-time onboarding steps gate it, both steady-state (the same class as payout setup): consent to derive, and complete Stripe payout onboarding for any priced derived listing. Amnetic takes consent to derive from the general Terms of Service you accepted at signup, so there is no extra step. A deployment can instead record a separate per-account Derivation Authorization instrument, accepted once at POST /api/v1/seller/derivation-authorization/accept; where it does, an earlier create_derived_listing returns an actionable error naming the instrument and accept URL. Read both with seller_readiness before you build the artifact — it reports them as the derivation_authorization and payout_onboarding rows, so you do not have to discover them by shipping a full artifact and reading the refusal.
  • You review; the agent cannot approve. For a Change Request-backed derived listing, approving is your licensing act through your Cognito session at POST /api/v1/seller/change-requests/{id}/approve; request changes and decline use the sibling /return and /decline routes and echo the complete pin set. Read the owner projection at GET /api/v1/seller/change-requests/{id} and the review packet through the Inbox. The portal re-reads that owner projection immediately before every approve, return, or decline and echoes only its fresh complete pin set. The Inbox and owner Change Request are the only human review and decision path. list_pending_signoffs and get_signoff_item are read-only CR-backed MCP projections; there is no sign_off MCP tool.
  • A derived listing cannot be edited after creation. Its content, price, ACL, offers, and slice policy stay frozen, and attempts to change them return "derived listing is frozen" over MCP (409 platform_slice_frozen over REST). A sidecar-less agent-derived child has one lifecycle-only exception: on your explicit direction, update_listing with only status: "inactive" retires it. Retirement removes it from the sign-off queue and buyer surfaces and stops new purchases, but preserves its immutable record and prior buyers' ownership and download rights. It cannot be reactivated in this release. Decline alone never retires it automatically. Platform-created slices keep their dedicated unsold-child lifecycle. The child carries the standard license composition frozen at creation, so a buyer is only ever quoted the terms you signed. To change a derived listing, create a new one. Editable derived listings — where an unsigned edit pauses the listing until you re-approve it — are planned and not yet available.

See Selling derived listings for the full seller walkthrough and the MCP tools reference for the three tool schemas.

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 derived listings. 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.

  1. 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_policy writes nothing and returns rider_acceptance_required with the version and URL to accept.

  2. Enable slicing and choose buyer access. Set enabled and use kinds as the buyer-request allow-list: [] or ["row_match"]. An empty list still permits seller-authored standing segments. Enabling row_match sells the rows matching 1–3 buyer key_columns; those columns must exist in the parent's data dictionary.

  3. Price it. Every enabled policy requires per_row_cents (US cents per matched row; fractional/sub-cent values are allowed, e.g. 0.5 for half a cent). Every slice is subject to a mandatory min_price_cents floor of at least 100 ($1.00) and, optionally, a max_price_cents ceiling. The effective price of a row set is clamp(ceil(round(per_row_cents × 10000) × rows ÷ 10,000), min_price_cents, max_price_cents): per_row_cents is 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.

  4. Heed the cannibalization warnings. set_slice_policy returns non-blocking warnings (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.

  5. Review mode. review_mode is auto (default) or review. In review mode, Amnetic matches the row_match request 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.

  6. 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.

Seller-agent transactions (SAT)

SAT is the workflow for a buyer whose request needs a new derived child. It is enabled in production; routes and tools 404 by absence only if the recovery kill-switch (SELLER_TXN_ENABLED) is off (rollback: .github/workflows/flag-rollout.yml). See the SAT API reference, MCP contracts, and the selling-derived-listings flow for the complete surface.

Your active, publicly visible listings are what your agent can discuss — activate or deactivate a listing and the front agent's view follows, with no separate publish step to manage. The platform compiles that view from your publicly visible listings at episode provisioning time, and the front agent can refresh it mid-episode. The seller also manages the listing-owned pricing source; the platform compiles that source into immutable, listing-scoped pricing-rules/v2 evidence, and there is no seller-wide default or fallback and no separate pricing-document authoring surface. A scoped seller-front agent reads that compiled view, talks to the buyer in a bounded thread, and asks the platform's pricing engine for a quote; it never chooses or invents a price. A scoped seller-worker then executes the approved Spec, creates the derived child together with a non-actionable draft Change Request, then submits the transaction packet to activate that same Change Request. The human seller reviews the pinned child/artifact and approves the Change Request through Cognito. That approval atomically creates and sends the pinned offer from the platform's pricing engine; the buyer's later acceptance and settlement, not approval, complete the sale and unlock delivery.

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 or post_request_quote MCP tools (see the MCP tools reference). submit_request_candidate attaches one of your own active listings to the request as a candidate, while post_request_quote offers an above-bounty bespoke price (the seller quote handshake); both run their DB-authoritative eligibility checks in one authoritative service transaction shared with the REST routes.

Submitting a candidate captures the listing's current standard offer_id and returns it with the candidate. Settlement uses that captured row. Replacing or retiring the offer makes the candidate unavailable even when the replacement has the same price; submit a new candidate for the replacement instead.

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 candidate's captured offer; 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_listing is 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_stats only reads your withdrawable balance — it cannot initiate a payout. Cash-out is portal-only: Account → Billing & credits. That screen shows your spendable credits (wallet for purchases and run cost) separately from your withdrawable balance (sale proceeds you can cash out). Use Withdraw to enter an amount in USD, or keep the defaulted withdrawable amount, confirm, and the portal POSTs to the existing seller payout route (POST /api/v1/seller/payouts). The minimum is typically $1.00 (the server is authoritative). Stripe Connect must have payouts enabled — finish Set up payouts first. Publish-ready (details submitted so priced listings can go live) is not the same as withdraw-ready; a 409 usually means finish Connect onboarding before withdrawing (if your stored Connect account can no longer be used and cannot be replaced safely, the portal asks you to contact support instead). Enter plain dollars and cents (25 or 25.00); amounts with fractions of a cent, exponents, or separators are refused rather than rounded. If the portal cannot confirm the result (a transfer error, a dropped connection, or a server error), it says so — the payout may still have gone through — and asks you to check the Payouts entries before retrying. It locks the amount and remembers the request for that browser tab — across a reload too, when the browser lets the portal store it: withdrawing again re-sends the same request, which Amnetic recognises instead of starting a second withdrawal. If a retry is answered but still cannot be confirmed, Start over lets you enter a new amount — only after you have checked Payouts, because it does not cancel the earlier request: if that completes too, you are paid twice, up to your withdrawable balance. Another tab does not share the remembered request; a withdrawal started there is a new request, limited by your withdrawable balance. If the portal could not load your account, Withdraw stays unavailable until you reload the page, and it tells you if the tab holds an unconfirmed withdrawal, so you can check Payouts first. A withdrawal that failed is reported as failed, with the amount returned to your withdrawable balance. Success may be immediate (paid) or pending — in progress while the transfer settles and the amount stays reserved; check Payouts for the final result. Payout history appears under the Billing ledger (including the Payouts chip). Onboarding recovery is unchanged: if payout onboarding fails after an environment or Stripe-mode change, rerun it 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.
  • Passive matching. Responding to a request by proposing one of your listings (submit_request_candidate) or by offering an above-bounty price (post_request_quote — see the MCP tools reference) are MCP tools. Passive matching (the platform proactively suggesting your listings for eligible requests) has no MCP tool yet.