Endpoints

Complete reference for the public Universal Goods API endpoints. All paths are relative to /api.

Tip

All endpoints require a Bearer token. See Authentication for details on creating and using API keys.

Products

Product definitions. Each product belongs to an organization and can have structured data sections, batches, and items.

CRUD

MethodPathDescriptionKey parameters
GET/productsList all productsorganizationId, categoryId, limit, page
POST/productsCreate a new productBody: see below
GET/products/{id}Get product by IDid (path)
PATCH/products/{id}Update a productid (path); Body: any create field except organizationId
DELETE/products/{id}Delete a productid (path)

Create request body:

{
  "organizationId": "org_abc123",
  "title": "Premium Leather Handbag",
  "description": "Full-grain Italian leather, brass hardware",
  "uidPrefix": "PLH",
  "imageUrl": "https://cdn.example.com/plh.jpg",
  "categoryId": "cat_uuid"
}

Only organizationId and title are required. All other fields are optional.

Data sections

Each product can have multiple detail sections (materials, sustainability, certifications, etc.) managed through the metadata API.

MethodPathDescriptionKey parameters
GET/metadata/product/{productId}/detailsList all detail sectionsproductId (path)
GET/metadata/product/{productId}/detail/{slug}Get a detail sectionproductId, slug (path)
PUT/metadata/product/{productId}/detail/{slug}Create or update a detail sectionproductId, slug (path); Body: see below
DELETE/metadata/product/{productId}/detail/{slug}Delete a detail sectionproductId, slug (path)

Detail upsert body:

{
  "slug": "materials",
  "title": "Material Composition",
  "visibility": "public",
  "description": "Breakdown of materials used",
  "data": {
    "primary": "Full-grain bovine leather",
    "lining": "Cotton",
    "hardware": "Brass"
  },
  "schema": {
    "version": "1.0",
    "jsonSchema": { "type": "object", "properties": { "..." : "..." } },
    "uiSchema": {}
  },
  "includeInDefaultPassport": true,
  "typeUri": "https://schema.universalgoods.io/materials/v1"
}

Only slug and title are required. Set visibility to "public" or "private" to control passport disclosure. Set includeInDefaultPassport to include the section in shared passports by default.

Credentials

MethodPathDescription
POST/metadata/product/{productId}/credentialsPublish a verifiable credential for the product
GET/metadata/product/{productId}/credentials/{credentialId}Get a specific credential
GET/products/{id}/sync-statusCheck credential sync status

Batches

Production batches within a product. Batches can be created under a product and also accessed directly by ID.

Create and list (nested under products)

MethodPathDescriptionKey parameters
GET/products/{productId}/batchesList batches for a productproductId (path), limit, offset, search
POST/products/{productId}/batchesCreate a new batchproductId (path); Body: see below

Create request body:

{
  "productId": "uuid",
  "uidPrefix": "PLH-2603",
  "friendlyUid": "March 2026, Milan Factory",
  "description": "Spring production run",
  "state": "draft"
}

Only productId is required. state defaults to "draft".

Direct batch access

MethodPathDescriptionKey parameters
GET/batches/{batchId}Get batch by IDbatchId (path)
PATCH/batches/{batchId}Update batchbatchId (path); Body: uidPrefix, friendlyUid, description, state
DELETE/batches/{batchId}Delete batchbatchId (path)

Batch states

StateMeaning
draftBatch is being prepared. Items can be added and data can be edited.
readyBatch is complete and ready for tokenization.
lockedBatch is finalised on-chain. Item list is frozen; metadata can still be updated.

Data sections

MethodPathDescriptionKey parameters
GET/metadata/batch/{batchId}/detailsList all detail sectionsbatchId (path)
GET/metadata/batch/{batchId}/detail/{slug}Get a detail sectionbatchId, slug (path)
PUT/metadata/batch/{batchId}/detail/{slug}Create or update a detail sectionbatchId, slug (path); Body: same as product detail
DELETE/metadata/batch/{batchId}/detail/{slug}Delete a detail sectionbatchId, slug (path)

Items

Individual tokenised items within a batch. Items can be created one at a time or in bulk, and accessed directly by ID.

Create and list (nested under batches)

MethodPathDescriptionKey parameters
GET/products/{productId}/batches/{batchId}/itemsList items in a batchproductId, batchId (path), limit, offset, search
POST/products/{productId}/batches/{batchId}/itemsCreate a single itemproductId, batchId (path); Body: see below
POST/products/{productId}/batches/{batchId}/items/bulkBulk create itemsproductId, batchId (path); Body: { "items": [...] }

Create request body:

{
  "batchId": "uuid",
  "friendlyUid": "PLH-2603-0001",
  "ethereumAddress": "0x1234...abcd",
  "metadata": {}
}

batchId, friendlyUid, and ethereumAddress are required. The ethereumAddress cannot be changed after creation.

Bulk create body:

{
  "items": [
    { "friendlyUid": "PLH-2603-0001", "ethereumAddress": "0x1234..." },
    { "friendlyUid": "PLH-2603-0002", "ethereumAddress": "0x5678..." }
  ]
}

Direct item access

MethodPathDescriptionKey parameters
GET/items/{itemId}Get item by IDitemId (path)
PATCH/items/{itemId}Update itemitemId (path); Body: friendlyUid, metadata
DELETE/items/{itemId}Delete itemitemId (path)
Note

The ethereumAddress field is immutable. It is set at creation and cannot be updated.

Data sections

MethodPathDescriptionKey parameters
GET/metadata/item/{itemId}/detailsList all detail sectionsitemId (path)
GET/metadata/item/{itemId}/detail/{slug}Get a detail sectionitemId, slug (path)
PUT/metadata/item/{itemId}/detail/{slug}Create or update a detail sectionitemId, slug (path); Body: same as product detail
DELETE/metadata/item/{itemId}/detail/{slug}Delete a detail sectionitemId, slug (path)

Common patterns

Pagination

List endpoints accept limit and offset (or page) query parameters:

ParameterDefaultDescription
limit20Maximum number of results (max 100)
offset0Number of records to skip
page1Page number (alternative to offset)
searchFree-text search (batches and items only)

Response format:

{
  "items": [],
  "total": 42,
  "limit": 20,
  "offset": 0
}

Error responses

Errors follow a consistent shape:

{
  "error": "NOT_FOUND",
  "message": "Product with ID 999 not found"
}
CodeMeaning
400Bad request: missing or invalid parameters
401Unauthorized: missing or invalid authentication
403Forbidden: insufficient permissions
404Not found
409Conflict: duplicate resource
422Validation error
429Rate limited
500Internal server error