Skip to content
prodid

Publisher guide

Publish your products

Publishing to prodid means putting two files on the web and one record in DNS. On your own domain there is nothing to sign up for and no key to keep: the domain is the credential, and you change or withdraw what you publish by changing the files.

How will you publish?

Route 1

Publish on your own domain

Two files and one DNS record. A platform publishing on behalf of merchants does the same thing on each merchant domain, so every merchant keeps its own identity and its own trust level.

0 of 4 steps done
x
  1. Serve the discovery document

    One file, at a fixed path, listing where your product feeds are: https://acme.example/.well-known/prodid.json

    .well-known/prodid.jsonapplication/json
    {
    	"$type": "org.prodid.feed",
    	"version": 1,
    	"publisher": { "name": "Acme Tekstil", "contact": "data@acme.example" },
    	"license": "CC0-1.0",
    	"feeds": ["https://acme.example/prodid/items.ndjson"]
    }
    The example from spec/fixtures/v1/examples/prodid.json, rendered from the file itself.
  2. Serve the feed

    A feed is NDJSON: one complete JSON object per line, UTF-8, newline separated. No array around it, no commas between lines. That way a feed of two million products streams line by line, and one bad line costs you that line rather than the rest.

    prodid/items.ndjsonapplication/x-ndjson
    {"$type":"org.prodid.item","key":"TJ-2041-NVY-M","brand":"Acme","name":"Trail Jacket","lang":"en","group":"TJ-2041","variant":{"color":"navy","size":"M"},"identifiers":[{"scheme":"gtin","value":"09506000134352"},{"scheme":"mpn","value":"TJ-2041-NVY-M"}],"markets":["TR","DE"],"properties":{"material":"100% recycled polyester","madeIn":"TR","weight":{"value":480,"unit":"g"}},"media":[{"url":"https://acme.example/img/tj2041-navy.webp","license":"CC-BY-4.0","kind":"image"}]}
    {"$type":"org.prodid.item","key":"TJ-2041-NVY-L","brand":"Acme","name":"Trail Jacket","lang":"en","group":"TJ-2041","variant":{"color":"navy","size":"L"},"identifiers":[{"scheme":"gtin","value":"09506000134406"},{"scheme":"mpn","value":"TJ-2041-NVY-L"}],"markets":["TR","DE"],"properties":{"material":"100% recycled polyester","madeIn":"TR","weight":{"value":505,"unit":"g"}},"media":[{"url":"https://acme.example/img/tj2041-navy.webp","license":"CC-BY-4.0","kind":"image"}]}
    {"$type":"org.prodid.item","key":"TJ-2041-OLV-M","brand":"Acme","name":"Trail Jacket","lang":"en","group":"TJ-2041","variant":{"color":"olive","size":"M"},"identifiers":[{"scheme":"gtin","value":"09506000134413"},{"scheme":"mpn","value":"TJ-2041-OLV-M"}],"markets":["TR"],"properties":{"material":"100% recycled polyester","madeIn":"TR","weight":{"value":480,"unit":"g"}},"media":[{"url":"https://acme.example/img/tj2041-olive.webp","license":"CC-BY-4.0","kind":"image"}]}
    Three variants of one product, from spec/fixtures/v1/examples/items.ndjson. They share a group, so the index builds one family with three variants from them.
  3. Claim the identity in DNS

    Once the files are up, publish one record naming your own domain as the host.

    _prodid.acme.example TXT
    _prodid.acme.example. IN TXT "v=prodid0; host=acme.example"
  4. Tell the index it changed

    Optional. Every known domain is re-crawled on a schedule, and a ping only makes the next crawl happen sooner.

    ask the index to run discovery now
    curl -X POST https://prodid.org/ping \
      -H 'content-type: application/json' \
      -d '{"domain": "acme.example"}'
    
    HTTP/1.1 202 Accepted
    Content-Type: application/json
    
    {"status":"accepted"}

Route 2

Delegate the hosting, keep the identity

Plenty of manufacturers own a domain and cannot put a file on it. The feed is hosted somewhere else and the domain proven with DNS, so the source identity is still the domain and the trust level still verified. Create a site at publish.prodid.org, enter your domain, and it shows the exact record to add and checks it for you.

What it looks like

0 of 4 steps done
x
  1. A catalogue uploaded at a hosted slug

    say acme.prodid.org — which serves both the discovery document and the feed.

  2. One TXT record on the domain you own

    , naming that host. A host, never a URL, so a record can never aim the crawler at an arbitrary address.

    _prodid.acme.example TXT
    _prodid.acme.example. IN TXT "v=prodid0; host=acme.prodid.org"
  3. A document that names your domain back.

    DNS says my document is over there; the document says I speak for that domain. A delegated document without domain is rejected — absence is never read as consent, which is what stops anyone pointing a record at somebody else's feed.

    acme.prodid.org/.well-known/prodid.jsonapplication/json
    {
    	"$type": "org.prodid.feed",
    	"version": 1,
    	"domain": "acme.example",
    	"publisher": { "name": "Acme Tekstil", "contact": "data@acme.example" },
    	"license": "CC0-1.0",
    	"feeds": ["https://acme.prodid.org/feeds/items.ndjson"]
    }
    From spec/fixtures/v1/examples/prodid.delegated.json. The only difference from route 1 is the domain field, and here it is mandatory.
  4. Create your site

    The hosted publisher writes domain into the document only after it has seen your TXT record itself, so there is no window in which a document claims a domain that has not asked for it.

Route 3

Publish without a domain

0 of 1 steps done
x
  1. Create your site

    A producer with no domain at all publishes at a hosted slug, <slug>.prodid.org, under its own name. Claim one at publish.prodid.org. The source identity is the slug host and the trust level unverified, because nothing has been proven about a domain. The records are real records: identifiers, resolution, attribution.

Check your domain

Done publishing? Enter your domain. The index is asked to look at it now, and you land on its live status page.

The next crawl updates the status in a few minutes.

Check your files before you ping

prodid-batch check validates your discovery document and each feed exactly as the index would, with no token and without asking the index anything. It runs from a clone of the repository with Bun 1.4.4. Give it the files themselves: a path ending in .json is read as one document, any other path as an NDJSON feed.

validate what you serve, as the index will
git clone https://github.com/productdevbook/prodid
cd prodid
bun install --frozen-lockfile
curl -fsSo prodid.json https://acme.example/.well-known/prodid.json
curl -fsSo items.ndjson https://acme.example/prodid/items.ndjson
bun apps/batch/src/cli.ts check prodid.json items.ndjson

prodid.json: ok, 1 record
items.ndjson: ok, 3 records

A file with no problem prints ok and the number of records the index would take from it. Otherwise it prints one line per problem, as items.ndjson:12: message with the feed line it is on, and exits non-zero. It reads the files, not your DNS or your server, so the TXT record, the content type and the caching headers in the list above are still yours to check.

The rules a record is validated against are in the specification, the schemas are served at /spec/schemas/v1, and /spec/fixtures/v1 holds machine-readable vectors you can run your exporter against before anyone crawls it.

Status: Spec v1. The index and the hosted publisher run on prodid.org. Anyone can publish, on their own domain or hosted. The file formats on this page are the ones the index reads when you ping it.

Reference

prodid is open source. Help it grow.

Star the repository so others find it, tell us what is wrong, send a fix, or write to us here without a GitHub account.