Publish a digital product passport

Publish a passport, choose who sees which fields, export it as JSON-LD and have it verified independently.

On this page

When you leave this page, you will know what a SealTrust passport contains, how to publish it from the console, how its content is filtered according to the person reading it, how to retrieve it as JSON or as JSON-LD, and how a third party verifies its signature without trusting us.

Every address quoted here is public. Reading a passport at the public tier requires no API key.

#What a passport covers

A passport attaches either to a product model or to a specific item.

The reference passport is attached to the model. It is shared by every item of the model. It is the level that suits most products, and it does not require serializing every item.

A batch is covered by the reference passport of its model. The ESPR regulation allows a passport at model, batch or item level. SealTrust attaches a passport to a model or to a unit, so batch production is covered by the model's reference passport: it holds for every item that shares the same product code. You do not have to create one passport per item in order to produce in batches. Only certain batteries must carry a per-item passport, under Regulation (EU) 2023/1542.

The item passport is attached to a physical unit. It carries the data specific to that unit, for example a battery state of health or a declaration tied to its serial number.

When you ask for a unit's passport, the server first looks for a passport attached to that unit. If it finds none, it serves the reference passport of its model. It never serves in its place a passport attached to another unit of the same model.

#What a passport contains

The passport content is a free-form JSON document, organized in sections. The access rules are written against the paths of those sections, which fixes the list of names used:

SectionContent
product_identityGTIN, model, brand, country of manufacture, production site
labelslabels and markings carried on the product
complianceESPR and REACH compliance, CE marking
circularityrecyclability, recycled content, repairability index, disassembly instructions
environmental_impactcarbon footprint, water, energy, transport
carbon_footprintdetailed carbon footprint
energy_efficiencyenergy class, EPREL registration
durabilityexpected service life
materialsmaterial composition
substances_of_concernsubstances of concern and safety data sheets
bill_of_materialsbill of components
manufacturingmanufacturing data
supply_chainsupply chain
battery_specificationgeneral specification of a battery
state_of_healthstate of health of a battery

The last two sections come from the batteries regulation (EU) 2023/1542. Their presence in a passport is enough to classify it as a battery passport when choosing the access rules.

Around this data, the per-unit read also carries elements the server computes itself: the product name and the brand name, the model photo, the GTIN, the GS1 Digital Link, a warranty summary, and an information provenance block described below. Reading a model's reference passport carries fewer of them, the exact list appears below.

#Publishing a passport

In the console, open DPP Compliance, then the DPP Passports tab. There you create a version, you fill it in, and you publish it.

Four things happen at the moment you publish.

The other published versions of the same model stop being published. They stay recorded, their publication date is cleared and their visibility moves to "brand only".

The server seals the version. Its content becomes immutable. The server refuses any change to the content of a sealed version, with code 409. To correct a piece of data, you publish a new version. Unpublishing a version does not unseal it.

The server deposits a public copy on IPFS. It deposits only the public projection of the passport. The fields reserved for the professional tiers do not appear in it. If the deposit fails, publication still succeeds and the passport remains without an IPFS copy.

The server issues or refreshes the signed credential. The signed passport automatically follows the published version, with no extra step. If signing fails, publication still succeeds and the passport remains without a credential.

Each version carries a number that increments. As soon as the product belongs to a model, the server counts that number per model. It counts it per unit only for a product that belongs to no model.

Three visibilities exist: public, owner_only and brand_only. The server serves only public to an anonymous visitor. It additionally serves owner_only to the unit's current owner when that owner is authenticated. It never serves brand_only through the public route.

#Reading the passport

The endpoint is GET /v1/passport/{identifier}.

curl "https://api.sealtrust.io/v1/passport/EXEMP1E00001"

The response, at the public tier, has this form. The values are made up.

200 OK
{
  "id": 1,
  "product_id": 1,
  "brand_id": 42,
  "schema_version": "1.0",
  "passport_version": 3,
  "data": {
    "product_identity": {
      "gtin": "03701234567890",
      "model": "Cartable Exemple 32",
      "brand": "Exemple SAS",
      "made_in": "FR",
      "production_facility": "Atelier Exemple Nord"
    },
    "compliance": {
      "eu_espr": true,
      "reach": true
    },
    "circularity": {
      "recyclability_percentage": 62,
      "recycled_content_percentage": 0
    }
  },
  "data_hash": "0000000000000000000000000000000000000000000000000000000000000000",
  "ipfs_uri": null,
  "ipfs_gateway_url": null,
  "visibility": "public",
  "access_tier": "public",
  "is_owner": false,
  "published_at": "2026-08-01T09:00:00+00:00",
  "product_name": "Cartable Exemple 32",
  "brand_name": "Exemple SAS",
  "image_url": "https://exemple-sas.test/images/cartable-32.jpg",
  "gtin": "03701234567890",
  "gs1_digital_link": "https://id.gs1.org/01/03701234567890/21/EXEMP1E00001",
  "warranty": {
    "status": "active",
    "ends_at": "2028-08-01T09:00:00+00:00",
    "duration_months": 24,
    "transferable": true,
    "remaining_days": 730
  },
  "evidence": {
    "sections": {
      "identity": "verified",
      "integrity": "declared",
      "composition": "declared",
      "substances_of_concern": "declared"
    },
    "legend": {
      "declared": "Stated by the brand. Recorded, dated and attributable, but not independently checked.",
      "verified": "Checked mechanically against a public record; no declaration involved."
    },
    "derived": true,
    "note": "Statuses are computed by SealTrust from verifiable facts. A brand cannot set them."
  }
}

The domain of the gs1_digital_link field is that of the resolver configured for your integration. https://id.gs1.org is only the fallback value, used when no resolver is configured. Do not hard-code that domain, read the value the response returns.

Two blocks appear only when there is reason for them. lifecycle appears when the unit has been replaced or withdrawn: the server keeps serving its passport so that the identifier always resolves. integrity appears when you add the verify_integrity=true parameter and you call at a professional or authority tier. That block is described below.

#The information provenance block

The evidence block says on what basis each part of the passport can be believed. Three values exist:

  • verified: checked mechanically against a public record, with no declaration involved.
  • document_backed: a third-party document is attached and can be retrieved. Its content has not been audited.
  • declared: stated by the brand. Recorded, dated, attributable, not independently checked.

SealTrust computes these values. A brand cannot choose them. The identity section moves to verified when the unit exists on the chain. The integrity section moves to verified when the hash of this version has been anchored and still matches the recorded data.

#A model's reference passport

To read the passport that the GTIN announces for the model, use GET /v1/passport/01/{gtin}.

curl
curl "https://api.sealtrust.io/v1/passport/01/03701234567890"

The response has this form, and it has no other. The values are made up.

200 OK
{
  "id": 7,
  "product_id": null,
  "product_model_id": 4,
  "gtin": "03701234567890",
  "level": "model",
  "brand_id": 42,
  "schema_version": "1.0",
  "passport_version": 2,
  "data": {
    "product_identity": {
      "gtin": "03701234567890",
      "model": "Cartable Exemple 32",
      "brand": "Exemple SAS",
      "made_in": "FR",
      "production_facility": "Atelier Exemple Nord"
    },
    "compliance": {
      "eu_espr": true,
      "reach": true
    },
    "circularity": {
      "recyclability_percentage": 62,
      "recycled_content_percentage": 0
    }
  },
  "data_hash": "0000000000000000000000000000000000000000000000000000000000000000",
  "ipfs_uri": "ipfs://bafyexemple0000000000000000000000000000000000000000000",
  "visibility": "public",
  "published_at": "2026-08-01T09:00:00+00:00",
  "product_name": "Cartable Exemple 32",
  "brand_name": "Exemple SAS"
}

This endpoint returns fewer fields than the per-unit read. The response contains no owner, no end-of-life banner and no warranty: none of these three exists at model level. It also does not contain the model photo, the GS1 Digital Link, the information provenance block, the is_owner field, or the access_tier field. If your integration reads one of these fields, read the passport per unit.

Here the server returns the ipfs_uri field to every caller, including anonymous ones, whatever tier is requested. Treat the content of that IPFS copy as public. This endpoint never returns an ipfs_gateway_url field.

The access_tier parameter works here as it does on the per-unit read: it filters the content of data, and the professional and authority tiers require the same authentication. The format=jsonld parameter and the verify_integrity parameter, for their part, do not exist on this endpoint.

#Access tiers, who sees what

The access_tier parameter decides which fields are returned. It accepts six values.

TierWhat it addsWho gets it
publicidentification, labels, ESPR / REACH / CE compliance, recyclability and recycled content, general battery specificationeveryone, without authentication
end_userenvironmental impact, full circularity, primary material, durability, energy efficiency, carbon footprinteveryone, without authentication
repairerbill of materials, disassembly instructions, repairability index, battery state of healthauthenticated account accredited as a repairer on the brand
recyclermaterial composition, substances of concern, disassembly instructions, battery state of healthauthenticated account accredited as a recycler on the brand
upstreammaterial composition, substances of concern, manufacturing, supply chainauthenticated account of the brand, or an authority
authoritythe entire data setauthenticated account carrying the market surveillance authority role

⚠️ These six values are not ordered from narrowest to widest. repairer, recycler and upstream are three distinct audiences: each one adds, to the consumer baseline, what its own trade requires, and none of them contains another's fields. An accredited recycler therefore does not read the bill of materials reserved for the repairer, and holding one accreditation opens none of the others. Only authority receives the entire set.

The three professional tiers and the authority tier require authentication. It is a user account session, presented either through the Authorization: Bearer <jeton de session> header, or through the session cookie set at login. A partner API key does not open these tiers.

Three of these tiers correspond to a trade: repairer, recycler and upstream. They are not ranked one above another. A recycler is not above a repairer. Each inherits the public tier and the end-user tier, then adds what its trade requires. The accreditations issued to a partner are of two kinds, repairer and recycler, so no accreditation opens upstream by itself.

The passport access profiles, side by side

A common base occupies the top of the diagram, served without authentication: the public tier carries identification, labels, compliance, recyclability and the general battery specification, then the end_user tier adds environmental impact, circularity, durability and carbon footprint. From that base start three arrows, toward three cards of the same size placed at the same height. The first, repairer, goes to an account accredited as a repairer on the brand and adds the bill of materials, the disassembly instructions, the repairability index and the battery state of health. The second, recycler, goes to an account accredited as a recycler and adds material composition, substances of concern, disassembly instructions and battery state of health. The third, upstream, goes to the brand team or to an authority, and adds material composition, substances of concern, manufacturing and supply chain. Between each pair of cards, a cross-shaped sign recalls that none of the three receives what its neighbor adds. A separate card at the bottom carries the market surveillance authority tier, which receives the entire data set whatever the trade.

Ask for the tier of your trade. It is the one that describes what you are, and it returns what your trade requires.

curl
curl -H "Authorization: Bearer JETON_DE_SESSION_FICTIF" \
  "https://api.sealtrust.io/v1/passport/EXEMP1E00001?access_tier=repairer"

Refusals are explicit:

CodeCondition
401professional or authority tier requested without authentication
403authenticated, but without the matching accreditation on that brand, and without access to the brand
403authority tier requested by an account that does not carry that role
404product not found, or no passport published for that product

JSON responses carry the X-DPP-Access-Tier header with the tier actually served. Read it rather than assuming. The JSON-LD export, described below, does not carry that header.

Two behaviors to know about.

The current owner sees more. If the call carries a valid session and the account is the unit's current owner, the server answers at the end_user tier to a request at the public tier, and it additionally serves passports with owner_only visibility. The is_owner field and the X-DPP-Access-Tier header signal that change. This elevation holds only for the JSON and JSON-LD reads, it does not apply to the signed credential.

No response is cached. The server serves every response with Cache-Control: no-store, max-age=0, whatever tier is requested and whatever the format. A shared cache therefore never returns a privileged body to the next caller. To reduce the number of your calls, keep the result in your own application cache, with the freshness window your usage tolerates.

The IPFS link depends on the tier, and on the endpoint. On GET /v1/passport/{identifier}, the ipfs_uri and ipfs_gateway_url fields stay null at the public and end_user tiers. The server fills them in only at the professional and authority tiers, which are authenticated. On GET /v1/passport/01/{gtin}, the server returns ipfs_uri to every caller, including anonymous ones.

#Defining your own access rules

By default, the distribution of fields between tiers is the one described above. You can replace it, brand by brand, in DPP Compliance then Access rules. A rule associates a field path with a tier. Paths accept the wildcard character, for example materials.*.

Rules can be carried per product group. The rules of the general group apply everywhere; the rules of the battery group apply in addition to battery passports.

Deleting all of a brand's rules at once requires retyping a confirmation sentence. That sentence contains the exact number of rules that would be deleted and the scope of the operation. The screen that lists the rules supplies it to you. Without it, the deletion is refused.

This confirmation exists because the operation is a confidentiality change on regulated data. The fields you had restricted revert to the built-in rules, which can make them publicly readable.

#JSON-LD export

Add format=jsonld to get the passport as linked data, in Schema.org and GS1 vocabulary. The server returns this response with the content type application/ld+json.

curl -H "Accept: application/ld+json" \
  "https://api.sealtrust.io/v1/passport/EXEMP1E00001?format=jsonld"
200 OK
{
  "@context": {
    "@vocab": "https://schema.org/",
    "gs1": "https://gs1.org/voc/",
    "espr": "https://data.europa.eu/espr/"
  },
  "@type": "Product",
  "identifier": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "gtin": "03701234567890",
  "name": "Cartable Exemple 32",
  "brand": {
    "@type": "Brand",
    "name": "Exemple SAS",
    "url": "https://exemple-sas.test"
  },
  "countryOfOrigin": "FR",
  "gs1:productionFacility": "Atelier Exemple Nord",
  "material": [],
  "additionalProperty": [
    {
      "@type": "PropertyValue",
      "name": "Recyclability (EN 45555)",
      "value": 62,
      "unitText": "percent"
    },
    {
      "@type": "PropertyValue",
      "name": "gs1:recycledContentPercentage",
      "value": 0,
      "unitText": "percent"
    }
  ],
  "espr:compliance": {
    "@type": "espr:ComplianceDeclaration",
    "espr:euEsprCompliant": true,
    "espr:reachCompliant": true
  },
  "warranty": {
    "@type": "WarrantyPromise",
    "durationOfWarranty": {
      "@type": "QuantitativeValue",
      "value": 24,
      "unitCode": "MON"
    },
    "espr:warrantyStatus": "active",
    "espr:warrantyEndDate": "2028-08-01T09:00:00+00:00",
    "espr:transferable": true
  }
}

Four points to remember.

The JSON-LD export is built after the tier filtering. An anonymous call therefore gets the public projection in JSON-LD, and nothing more. To get the professional fields in JSON-LD, add access_tier and authenticate.

Key names change between the two formats. The compliance section of the JSON becomes an espr:compliance node of type espr:ComplianceDeclaration, whose keys are espr:euEsprCompliant, espr:reachCompliant, espr:ceMarking and their neighbors. Do not expect the names of the JSON response in the JSON-LD.

The server removes keys without a value from the document. A field you have not filled in does not appear as null.

This response does not carry the X-DPP-Access-Tier header, unlike the JSON response. The tier served is the one you asked for in access_tier.

The format parameter exists only on GET /v1/passport/{identifier}. The endpoint for a GTIN's reference passport does not offer it.

#The signed passport and its verification

The server also issues every published passport as a verifiable credential, in SD-JWT-VC format. That credential lets a third party check that the brand did issue this passport, without going through our verification endpoint and without granting us any trust.

#The issuer

The issuer is a did:web decentralized identifier carried by the brand. Its default form is did:web:api.sealtrust.io:brand:{brand_id}, which resolves, per the did:web specification, to the address https://api.sealtrust.io/brand/{brand_id}/did.json.

curl
curl "https://api.sealtrust.io/brand/4242/did.json"

The document returned lists all the brand's non-revoked public keys, each as a JsonWebKey2020. The identifier of a key is <did>#key-<version>, which lets an old credential stay verifiable after a key rotation, as long as the old version is not revoked.

A brand can also carry its identifier on its own domain. The document is then found at https://<domaine de la marque>/.well-known/did.json, and the identifier takes the form did:web:<domaine de la marque>. The brand thus keeps ownership of its issuer identity.

#Retrieving the credential

GET /v1/passport/{identifier}/vc returns the presentation filtered according to the tier requested. This route applies the same tier grid as the JSON read, with two differences:

  • the automatic owner elevation does not apply. An authenticated owner stays at the tier they ask for, and the server does not serve passports with owner_only visibility;
  • a replaced or withdrawn unit returns 404, whereas the JSON read keeps serving its passport with the lifecycle banner.

These two differences hold for the three addresses of the credential: /vc, /vc/verify and /vc/preview.

curl
curl "https://api.sealtrust.io/v1/passport/EXEMP1E00001/vc"
200 OK
{
  "passport_id": 1,
  "issuer": "did:web:api.sealtrust.io:brand:4242",
  "vct": "https://schema.sealtrust.io/vct/digital-product-passport",
  "access_tier": "public",
  "format": "dc+sd-jwt",
  "sd_jwt_vc": "eyJFWEVNUExFIn0.eyJFWEVNUExFIn0.RVhFTVBMRQ~WyJFWEVNUExFIl0~"
}

Disclosure is selective. The server leaves the fields of the public tier in the clear and replaces the others with hashes. It reveals a hash only if the tier requested entitles you to it.

Two blocks escape this rule. The server always carries them in the clear, whatever tier is requested, and it never masks them:

  • the brand identity: legal name, LEI identifier, EORI number, website, postal address and contact email;
  • the unit identity: UID hash, token id and product name.

A presentation at the public tier therefore reveals the public projection, plus these two blocks. The JSON read at the public tier, for its part, returns only the brand's name. Check the content of your brand record before distributing credentials: its postal address and its contact email go with them.

If no credential has been issued yet for this passport, the response is 404.

#Verifying the credential

Two paths exist, and the second does not depend on us.

Through our endpoint. GET /v1/passport/{identifier}/vc/verify checks the presentation matching the tier requested against the brand's public key.

curl "https://api.sealtrust.io/v1/passport/EXEMP1E00001/vc/verify"
200 OK
{
  "passport_id": 1,
  "issuer": "did:web:api.sealtrust.io:brand:4242",
  "vct": "https://schema.sealtrust.io/vct/digital-product-passport",
  "key_version": 1,
  "access_tier": "public",
  "verified": true,
  "error": null,
  "credential_subject": {
    "product_identity": {
      "gtin": "03701234567890",
      "model": "Cartable Exemple 32",
      "brand": "Exemple SAS",
      "made_in": "FR",
      "production_facility": "Atelier Exemple Nord"
    }
  }
}

On failure, verified is false, error is verification_failed and credential_subject is null. The original error message is never returned.

By your own means. Retrieve the presentation with GET /v1/passport/{identifier}/vc, retrieve the issuer document at the did.json address given above, choose the key whose identifier matches the kid field of the credential header, and check the signature. The signature algorithm is ES256, and the type declared in the header is dc+sd-jwt. Any standard did:web and SD-JWT-VC library is enough.

#Seeing the content before signing

GET /v1/passport/{identifier}/vc/preview returns the envelope of the credential and its content for a given tier, without a signature. The response carries "signed": false.

The preview returns the content of the tier requested in JSON-LD format. The signed credential, for its part, carries the raw data tree of the passport, with the section names of the JSON response. Use the preview to check which fields a given tier sees. Its structure is not that of the credential, so do not use it as an integration template.

#Integrity proofs

Two public endpoints accompany the passport. They require no authentication.

#The integrity check

GET /v1/passport/{identifier}/verify recomputes the hash of the data, compares it with the recorded one, compares the IPFS copy when it exists, and recomputes the version hash of a sealed version.

200 OK
{
  "db_hash_match": true,
  "ipfs_match": true,
  "ipfs_uri": "ipfs://bafyexemple0000000000000000000000000000000000000000000",
  "ipfs_gateway_url": "https://ipfs.io/ipfs/bafyexemple0000000000000000000000000000000000000000000",
  "data_hash": "0000000000000000000000000000000000000000000000000000000000000000",
  "computed_hash": "0000000000000000000000000000000000000000000000000000000000000000",
  "passport_version": 3,
  "seal": {
    "sealed": true,
    "sealed_at": "2026-08-01T09:00:00+00:00",
    "algorithm": "st-dpp-chain-v1",
    "version_hash": "0000000000000000000000000000000000000000000000000000000000000000",
    "prev_version_hash": "0000000000000000000000000000000000000000000000000000000000000000",
    "linked": true,
    "chain_link_match": true
  }
}

Be precise about what each line establishes.

db_hash_match compares data we hold with a hash we hold. It is an internal consistency check.

ipfs_match compares the public copy deposited on IPFS with the expected public projection. The copy is addressed by its hash, so it cannot be modified without changing address. When the copy cannot be retrieved, the value is null, which means "unknown". It never flips to false for a mere gateway outage.

version_hash carries the hash of this version, 64 hexadecimal characters. prev_version_hash carries the hash of the previous version, and it is null for the first sealed version. Each version hash covers the one before it, so rewriting a version detaches every version published after it.

chain_link_match is the tampering signal. If chain_link_match is false, the version was modified after its publication. A passport published before this chaining existed carries linked: false and reason: sealed_before_chain, and no version hash is manufactured after the fact for a publication we cannot date.

The server returns the IPFS link here only if the deposited copy is proven identical to the public projection. Otherwise, it removes the link from the response.

#The proof summary

GET /v1/passport/{identifier}/proof gathers everything a third party can check. The same summary exists for a reference passport at GET /v1/passport/01/{gtin}/proof.

200 OK
{
  "passport_version": 3,
  "data_hash": "0000000000000000000000000000000000000000000000000000000000000000",
  "ipfs_uri": "ipfs://bafyexemple0000000000000000000000000000000000000000000",
  "ipfs_gateway_url": "https://ipfs.io/ipfs/bafyexemple0000000000000000000000000000000000000000000",
  "anchor": {
    "chain": "base",
    "chain_id": 8453,
    "type": "merkle_batch",
    "tx_hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "basescan_url": "https://basescan.org/tx/0x0000000000000000000000000000000000000000000000000000000000000000",
    "merkle_root": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "anchored": true,
    "proves": "batch_inclusion"
  },
  "passport_anchor": {
    "chain": "base",
    "chain_id": 8453,
    "tx_hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "basescan_url": "https://basescan.org/tx/0x0000000000000000000000000000000000000000000000000000000000000000",
    "merkle_root": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "leaf": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "leaf_index": 0,
    "proof": [],
    "anchored_at": "2026-08-01T10:00:00+00:00",
    "data_hash_matches": true,
    "proves": "content_existed_at_or_before_tx"
  },
  "seal": {
    "sealed": true,
    "sealed_at": "2026-08-01T09:00:00+00:00",
    "algorithm": "st-dpp-chain-v1",
    "version_hash": "0000000000000000000000000000000000000000000000000000000000000000",
    "prev_version_hash": "0000000000000000000000000000000000000000000000000000000000000000",
    "linked": true,
    "chain_link_match": true
  },
  "vc": {
    "issued": true,
    "vct": "https://schema.sealtrust.io/vct/digital-product-passport",
    "issued_at": "2026-08-01T09:00:00+00:00"
  },
  "verifications": {
    "count": 12,
    "last_verified_at": "2026-08-12T14:32:00+00:00"
  }
}

Two blocks carry an on-chain reference, and they do not say the same thing.

anchor dates the item. Read the anchored field. The name of the key is not enough. When anchored is true, type is merkle_batch and proves is batch_inclusion: the root of the batch has been written to the chain and the membership of the unit in that batch is demonstrable. When anchored is false, type is mint_transaction and proves is token_minted: the transaction only proves that the token exists. It says nothing about the batch, and nothing about the content of the passport.

passport_anchor dates the document. It carries the hash of this precise version, its proof of membership in a tree, and the transaction that wrote the root. It establishes a single property, content_existed_at_or_before_tx: this content existed no later than that transaction. It does not make the content true, and it does not prevent publishing a correction as the next version. The data_hash_matches field at false means that the recorded data no longer matches what was anchored.

The write happens on Base, chain_id 8453.

Between publication and a possible anchoring, the seal is what holds. It asks nothing of the chain: it chains each version to the previous one and is recomputed at read time.

For a reference passport, two blocks are absent and that absence is the right answer: anchor dates an item, and a reference has none, and verifications counts UID verifications, and a reference has no UID. The response additionally carries "level": "model" and the GTIN.

#Checking the IPFS copy from the read

Add verify_integrity=true to the passport read to additionally get an integrity block. The server computes that block only if it returns the IPFS link to you, so only at the professional and authority tiers. The call must be authenticated and carry an access_tier:

curl
curl -H "Authorization: Bearer JETON_DE_SESSION_FICTIF" \
  "https://api.sealtrust.io/v1/passport/EXEMP1E00001?access_tier=repairer&verify_integrity=true"

An anonymous call at the public or end_user tier never receives this block, even with verify_integrity=true. For an integrity check without authentication, use GET /v1/passport/{identifier}/verify described just above.

#Rate limit and errors

Every address starting with /passport shares a limit of 60 calls per 60-second window and per IP address. The /passport/... and /v1/passport/... forms count against the same counter. Going over returns 429, with the Retry-After header in seconds.

CodeConditionWhat to do
401professional or authority tier requested without a valid sessionauthenticate
403valid session, but without rights on that tier for that brandask for the tier that matches your accreditation
404Product not found: no product matches the identifiercheck the identifier
404No published passport found for this product: the product exists, no passport publishedpublish a version
404Unknown GS1 Digital Link: no public reference passport answers for that GTINcheck the GTIN
404no credential issued for that passportrepublish the passport to trigger issuance
409change to the content of a sealed versionpublish a new version
429rate limit exceededslow down

#What to remember

The passport covers a model or an item. A batch is covered by the reference passport of its model. A published version is sealed and no longer changes; a correction becomes the next version, and publishing unpublishes the whole model.

The content returned depends on the tier requested, and the professional tiers require an authenticated session together with the matching accreditation on the brand. The three trades sit side by side: each starts from the common base, and none receives what another adds.

The JSON-LD carries the same content, filtered the same way, with different key names and without the headers of the JSON response.

The signed credential allows an independent verification. The seal, the IPFS copy and the anchoring each say one precise thing, and the response names that thing rather than leaving it to be guessed.

#Going further

Your answer opens a pre-filled email in your mail app, addressed to contact@sealtrust.io. You read it over before sending it.

Suggest a correctionReport a problem