Fulfil Products & Variants API
Manage the product catalog through the product.template (product groupings) and product.product (individual sellable variants / SKUs) models - codes, barcodes, pricing, attributes, and their relationships.
Manage the product catalog through the product.template (product groupings) and product.product (individual sellable variants / SKUs) models - codes, barcodes, pricing, attributes, and their relationships.
openapi: 3.0.3
info:
title: Fulfil REST API (v2)
description: >-
Fulfil is a cloud ERP and operations platform for e-commerce, DTC, and
wholesale merchants. Its REST API (v2) exposes every ERP resource through a
single, uniform "model interface": each business object (sales orders,
products, stock moves, parties, shipments, purchases, production, and
thousands more) is a named model addressed at
`/api/v2/model/{model.name}`, with generic CRUD, a `search_read` search,
`count`, and the ability to call arbitrary model methods (actions), run
reports, and drive wizards. Fulfil advertises 6,000+ endpoints across all
models.
The base URL is per-merchant: `https://{merchant_id}.fulfil.io/api/v2`.
Authentication is either a personal access token sent in the `X-API-KEY`
header (or as HTTP Basic) for private integrations, or OAuth 2.0 for public
apps. Use `GET /api/v2/test` to verify credentials.
Fulfil records are model-specific and dynamically typed, so request and
response bodies below are represented as open JSON objects (a `Record` is
an object with an integer `id` plus arbitrary model fields). This document
models the generic interface plus concrete headline models; the same
interface applies to any other Fulfil model by substituting its name.
version: '2.0'
contact:
name: Fulfil Developer Documentation
url: https://developers.fulfil.io/
license:
name: Proprietary
url: https://www.fulfil.io/
servers:
- url: https://{merchant_id}.fulfil.io/api/v2
description: Fulfil merchant instance
variables:
merchant_id:
default: mystore
description: Your Fulfil merchant subdomain.
security:
- apiKeyAuth: []
- oauth2: []
tags:
- name: Authentication
description: Verify API credentials.
- name: Sales Orders
description: Sales orders via the sale.sale model.
- name: Products & Variants
description: Catalog via product.template and product.product models.
- name: Inventory & Stock
description: Inventory via stock.move and stock.location models.
- name: Customers
description: Customers, suppliers, and contacts via the party.party model.
- name: Shipments
description: Fulfillment via stock.shipment.out and stock.shipment.in models.
- name: Purchases
description: Purchase orders via the purchase.purchase model.
- name: Manufacturing
description: Production orders via the production model.
- name: Webhooks
description: Webhook subscriptions for real-time ERP events.
- name: Model Interface
description: The generic model interface applicable to any Fulfil model.
paths:
/test:
get:
operationId: testCredentials
tags:
- Authentication
summary: Test API credentials
description: Verifies that the supplied API key or token is valid.
responses:
'200':
description: Credentials are valid.
'401':
$ref: '#/components/responses/Unauthorized'
/model/{model_name}:
parameters:
- $ref: '#/components/parameters/ModelName'
get:
operationId: searchModel
tags:
- Model Interface
summary: Search records of a model
description: >-
Searches records of the given model. Accepts a domain filter, ordering,
and pagination as query parameters. Returns matching record ids (and
fields when requested).
parameters:
- $ref: '#/components/parameters/Filter'
- $ref: '#/components/parameters/Fields'
- $ref: '#/components/parameters/Page'
- $ref: '#/components/parameters/PerPage'
- $ref: '#/components/parameters/Order'
responses:
'200':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
post:
operationId: createModelRecords
tags:
- Model Interface
summary: Create records of a model
description: Creates one or more records of the given model.
requestBody:
$ref: '#/components/requestBodies/RecordArray'
responses:
'201':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
'422':
$ref: '#/components/responses/ValidationError'
/model/{model_name}/{id}:
parameters:
- $ref: '#/components/parameters/ModelName'
- $ref: '#/components/parameters/Id'
get:
operationId: readModelRecord
tags:
- Model Interface
summary: Read a record
description: Retrieves a single record of the given model by its id.
parameters:
- $ref: '#/components/parameters/Fields'
responses:
'200':
$ref: '#/components/responses/Record'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
put:
operationId: updateModelRecord
tags:
- Model Interface
summary: Update a record
description: Updates a single record of the given model by its id.
requestBody:
$ref: '#/components/requestBodies/Record'
responses:
'200':
$ref: '#/components/responses/Record'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/ValidationError'
delete:
operationId: deleteModelRecord
tags:
- Model Interface
summary: Delete a record
description: Deletes a single record of the given model by its id.
responses:
'200':
description: Deletion confirmation.
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
/model/{model_name}/search_read:
parameters:
- $ref: '#/components/parameters/ModelName'
put:
operationId: searchReadModel
tags:
- Model Interface
summary: Search and read records in one call
description: >-
Combines search and read: accepts a domain filter, a list of fields,
ordering, offset, and limit in the request body, and returns the
matching records with the requested fields populated.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SearchReadRequest'
responses:
'200':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
/model/{model_name}/count:
parameters:
- $ref: '#/components/parameters/ModelName'
put:
operationId: countModel
tags:
- Model Interface
summary: Count records
description: Returns the number of records matching a domain filter.
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
filter:
$ref: '#/components/schemas/Domain'
responses:
'200':
description: The matching record count.
content:
application/json:
schema:
type: integer
'401':
$ref: '#/components/responses/Unauthorized'
/model/{model_name}/{method}:
parameters:
- $ref: '#/components/parameters/ModelName'
- name: method
in: path
required: true
description: Name of the model method (action) to invoke.
schema:
type: string
put:
operationId: callModelMethod
tags:
- Model Interface
summary: Call a model method (action)
description: >-
Invokes an arbitrary method exposed by the model, passing positional
arguments as a JSON array. Used for state transitions and business
actions (for example confirming an order or processing a shipment).
requestBody:
required: false
content:
application/json:
schema:
type: array
items: {}
responses:
'200':
description: The method result (shape depends on the method).
'401':
$ref: '#/components/responses/Unauthorized'
/model/sale.sale:
get:
operationId: searchSalesOrders
tags:
- Sales Orders
summary: Search sales orders
description: Searches sales orders (sale.sale). Same interface as GET /model/{model_name}.
parameters:
- $ref: '#/components/parameters/Filter'
- $ref: '#/components/parameters/Fields'
- $ref: '#/components/parameters/Page'
- $ref: '#/components/parameters/PerPage'
responses:
'200':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
post:
operationId: createSalesOrders
tags:
- Sales Orders
summary: Create sales orders
description: Creates one or more sales orders (sale.sale).
requestBody:
$ref: '#/components/requestBodies/RecordArray'
responses:
'201':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
'422':
$ref: '#/components/responses/ValidationError'
/model/sale.sale/search_read:
put:
operationId: searchReadSalesOrders
tags:
- Sales Orders
summary: Search and read sales orders
description: Search and read sales orders (sale.sale) in a single call.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SearchReadRequest'
responses:
'200':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
/model/product.template:
get:
operationId: searchProductTemplates
tags:
- Products & Variants
summary: Search product templates
description: Searches product templates (product.template) - the groupings that own variants.
parameters:
- $ref: '#/components/parameters/Filter'
- $ref: '#/components/parameters/Fields'
- $ref: '#/components/parameters/Page'
- $ref: '#/components/parameters/PerPage'
responses:
'200':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
post:
operationId: createProductTemplates
tags:
- Products & Variants
summary: Create product templates
description: Creates one or more product templates (product.template).
requestBody:
$ref: '#/components/requestBodies/RecordArray'
responses:
'201':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
'422':
$ref: '#/components/responses/ValidationError'
/model/product.product:
get:
operationId: searchProductVariants
tags:
- Products & Variants
summary: Search product variants
description: Searches product variants / SKUs (product.product) - the individual sellable items.
parameters:
- $ref: '#/components/parameters/Filter'
- $ref: '#/components/parameters/Fields'
- $ref: '#/components/parameters/Page'
- $ref: '#/components/parameters/PerPage'
responses:
'200':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
post:
operationId: createProductVariants
tags:
- Products & Variants
summary: Create product variants
description: Creates one or more product variants (product.product).
requestBody:
$ref: '#/components/requestBodies/RecordArray'
responses:
'201':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
'422':
$ref: '#/components/responses/ValidationError'
/model/product.product/search_read:
put:
operationId: searchReadProductVariants
tags:
- Products & Variants
summary: Search and read product variants
description: Search and read product variants (product.product) in a single call.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SearchReadRequest'
responses:
'200':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
/model/stock.move:
get:
operationId: searchStockMoves
tags:
- Inventory & Stock
summary: Search stock moves
description: Searches inventory movements (stock.move) - transfers, adjustments, and shipment moves.
parameters:
- $ref: '#/components/parameters/Filter'
- $ref: '#/components/parameters/Fields'
- $ref: '#/components/parameters/Page'
- $ref: '#/components/parameters/PerPage'
responses:
'200':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
post:
operationId: createStockMoves
tags:
- Inventory & Stock
summary: Create stock moves
description: Creates one or more inventory movements (stock.move).
requestBody:
$ref: '#/components/requestBodies/RecordArray'
responses:
'201':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
'422':
$ref: '#/components/responses/ValidationError'
/model/stock.location:
get:
operationId: searchStockLocations
tags:
- Inventory & Stock
summary: Search stock locations
description: Searches warehouses, zones, and bins (stock.location).
parameters:
- $ref: '#/components/parameters/Filter'
- $ref: '#/components/parameters/Fields'
- $ref: '#/components/parameters/Page'
- $ref: '#/components/parameters/PerPage'
responses:
'200':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
/model/party.party:
get:
operationId: searchParties
tags:
- Customers
summary: Search parties
description: Searches parties (party.party) - customers, suppliers, and contacts.
parameters:
- $ref: '#/components/parameters/Filter'
- $ref: '#/components/parameters/Fields'
- $ref: '#/components/parameters/Page'
- $ref: '#/components/parameters/PerPage'
responses:
'200':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
post:
operationId: createParties
tags:
- Customers
summary: Create parties
description: Creates one or more parties (party.party).
requestBody:
$ref: '#/components/requestBodies/RecordArray'
responses:
'201':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
'422':
$ref: '#/components/responses/ValidationError'
/model/party.party/search_read:
put:
operationId: searchReadParties
tags:
- Customers
summary: Search and read parties
description: Search and read parties (party.party) in a single call.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SearchReadRequest'
responses:
'200':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
/model/stock.shipment.out:
get:
operationId: searchOutboundShipments
tags:
- Shipments
summary: Search outbound shipments
description: Searches customer (outbound) shipments (stock.shipment.out).
parameters:
- $ref: '#/components/parameters/Filter'
- $ref: '#/components/parameters/Fields'
- $ref: '#/components/parameters/Page'
- $ref: '#/components/parameters/PerPage'
responses:
'200':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
/model/stock.shipment.in:
get:
operationId: searchInboundShipments
tags:
- Shipments
summary: Search inbound shipments
description: Searches supplier (inbound) shipments / receipts (stock.shipment.in).
parameters:
- $ref: '#/components/parameters/Filter'
- $ref: '#/components/parameters/Fields'
- $ref: '#/components/parameters/Page'
- $ref: '#/components/parameters/PerPage'
responses:
'200':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
/model/purchase.purchase:
get:
operationId: searchPurchaseOrders
tags:
- Purchases
summary: Search purchase orders
description: Searches purchase orders (purchase.purchase).
parameters:
- $ref: '#/components/parameters/Filter'
- $ref: '#/components/parameters/Fields'
- $ref: '#/components/parameters/Page'
- $ref: '#/components/parameters/PerPage'
responses:
'200':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
post:
operationId: createPurchaseOrders
tags:
- Purchases
summary: Create purchase orders
description: Creates one or more purchase orders (purchase.purchase).
requestBody:
$ref: '#/components/requestBodies/RecordArray'
responses:
'201':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
'422':
$ref: '#/components/responses/ValidationError'
/model/production:
get:
operationId: searchProductionOrders
tags:
- Manufacturing
summary: Search production orders
description: Searches manufacturing / production orders (production).
parameters:
- $ref: '#/components/parameters/Filter'
- $ref: '#/components/parameters/Fields'
- $ref: '#/components/parameters/Page'
- $ref: '#/components/parameters/PerPage'
responses:
'200':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
post:
operationId: createProductionOrders
tags:
- Manufacturing
summary: Create production orders
description: Creates one or more production orders (production).
requestBody:
$ref: '#/components/requestBodies/RecordArray'
responses:
'201':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
'422':
$ref: '#/components/responses/ValidationError'
/model/webhook.webhook:
get:
operationId: listWebhooks
tags:
- Webhooks
summary: List webhook subscriptions
description: >-
Lists registered webhook subscriptions. Webhooks are Fulfil records like
any other model, so they are managed through the same model interface.
parameters:
- $ref: '#/components/parameters/Filter'
- $ref: '#/components/parameters/Fields'
responses:
'200':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
post:
operationId: createWebhook
tags:
- Webhooks
summary: Create a webhook subscription
description: >-
Registers a new webhook subscription for a model/event. Fulfil delivers
matching events as HTTP POSTs to the configured URL, signed with
HMAC-SHA256. A Google Pub/Sub delivery option is available for high
volume.
requestBody:
$ref: '#/components/requestBodies/RecordArray'
responses:
'201':
$ref: '#/components/responses/RecordList'
'401':
$ref: '#/components/responses/Unauthorized'
'422':
$ref: '#/components/responses/ValidationError'
components:
securitySchemes:
apiKeyAuth:
type: apiKey
in: header
name: X-API-KEY
description: >-
Personal access token for private integrations, sent in the X-API-KEY
header. Fulfil also accepts the token via HTTP Basic authentication.
oauth2:
type: oauth2
description: OAuth 2.0 authorization code flow for public apps.
flows:
authorizationCode:
authorizationUrl: https://{merchant_id}.fulfil.io/oauth/authorize
tokenUrl: https://{merchant_id}.fulfil.io/oauth/token
scopes: {}
parameters:
ModelName:
name: model_name
in: path
required: true
description: >-
The Fulfil model name (for example sale.sale, product.product,
product.template, stock.move, party.party, stock.shipment.out,
purchase.purchase, production).
schema:
type: string
Id:
name: id
in: path
required: true
description: The integer id of the record.
schema:
type: integer
Filter:
name: filter
in: query
required: false
description: A Fulfil domain filter (JSON-encoded list of clauses).
schema:
type: string
Fields:
name: fields
in: query
required: false
description: Comma-separated list of fields to return.
schema:
type: string
Page:
name: page
in: query
required: false
description: Page number for pagination.
schema:
type: integer
default: 1
PerPage:
name: per_page
in: query
required: false
description: Number of records per page.
schema:
type: integer
default: 100
Order:
name: order
in: query
required: false
description: Sort order, for example "id DESC".
schema:
type: string
requestBodies:
Record:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Record'
RecordArray:
required: true
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Record'
responses:
Record:
description: A single record.
content:
application/json:
schema:
$ref: '#/components/schemas/Record'
RecordList:
description: A list of records.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Record'
Unauthorized:
description: Missing or invalid credentials.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
NotFound:
description: The requested record was not found.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
ValidationError:
description: The request payload failed validation.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
schemas:
Record:
type: object
description: >-
A Fulfil record. Fields vary by model; every persisted record has an
integer id. Additional model-specific fields are permitted.
properties:
id:
type: integer
additionalProperties: true
Domain:
type: array
description: >-
A Fulfil domain filter - a list of clauses such as
["name", "ilike", "iphone"] combined with logical operators.
items: {}
SearchReadRequest:
type: object
properties:
filter:
$ref: '#/components/schemas/Domain'
fields:
type: array
items:
type: string
order:
type: array
items:
type: array
items:
type: string
offset:
type: integer
default: 0
limit:
type: integer
default: 100
Error:
type: object
properties:
type:
type: string
message:
type: string
code:
type: integer