Direct API
Send product data via GraphQL mutation. Best for small batches up to ~1,000 products.
SFTP Upload
Upload JSONL files to SFTP, then trigger processing. Best for large product sets.
Custom Data
Attach any metadata to products and variants with the
meta: prefix.Inventory by Location
Track per-location inventory across your physical stores and warehouses.
Import Modes
- REPLACE Mode
- UPDATE Mode
Replaces your entire product set. Products not included in the import are archived (soft-deleted).When to use:
- Initial product import
- Daily overnight product refresh
- When your export represents the complete source of truth
Before You Begin
You’ll need the following credentials from your Remark Dashboard before getting started.
Quick Start
1
Choose your method
Direct API for small batches (≤1,000 products), SFTP for large product sets.
2
Format your data
JSON objects for API, JSONL files for SFTP.
3
Send or upload
Call the GraphQL mutation or upload to SFTP and trigger processing.
4
Verify import
Check job status to confirm successful processing.
API Endpoint & Authentication
- GraphQL API
- REST API
Authentication Options
- API Key (Recommended for automation)
- Bearer Token (User auth)
Use an integration API key for server-to-server integrations:API keys start with
rmrk_ and are scoped to a specific vendor with specific permissions.Direct API: Upsert Products
For small to medium batches (up to ~1,000 products), use theupsertProducts mutation.
The direct API always operates in UPDATE mode. Products not included are left unchanged. To archive products, set
archived: true. To unpublish, set publishedAt: null.GraphQL Mutation
Example Request
Response
Archive & Publish Controls
The direct API supports archiving and publishing controls on each product:
Example: Archive and unpublish products
SFTP File Processing
For large product sets (thousands of products), upload a JSONL file to SFTP, then trigger processing via GraphQL.Step 1: Upload File to SFTP
Upload your file to theincoming/ directory:
Step 2: Trigger Processing
Call theprocessProductImport mutation to start the import job. Specify just the filename (the system automatically looks in your incoming/ directory):
Example Request
Response
Imports are queued per-vendor — if you trigger an import while another is already running, it joins the queue (up to 20 pending jobs) and starts automatically when the current job finishes. You don’t need to wait for one import to complete before triggering the next.
Check Job Status
Query job status to track processing progress:Example Request
Job Statuses
Example Response
Partial failures don’t stop processing. Valid products are imported; errors are logged and returned in
errorDetails.Data Format
- Direct API (JSON)
- SFTP Files (JSONL)
For the
upsertProducts mutation, send an array of product objects:Product Schema
Product Fields
*Required for new products and REPLACE mode. For UPDATE mode on existing products, only
externalId and the fields you’re changing are needed.Variant Fields
Price Fields
Just set
price to whatever the customer pays today. If you want to show a strikethrough original price, include compareAtPrice — but it’s not required.
Image Fields
Images are deduplicated by URL — if the same URL appears multiple times (even with differentexternalId values), only the first occurrence is kept. Variant imageExternalId references are remapped automatically.
Metafields
Metafields let you attach custom data to products and variants — things like class schedules, store availability, materials, care instructions, or anything else specific to your business.How it works
Prefix any key withmeta: and we’ll extract it automatically. Metafields work on both products and variants.
meta: prefix is stripped when stored — meta:skillLevel becomes a metafield with key skillLevel. Any key without the meta: prefix that isn’t a standard field is ignored.
Value types
Values are stored as strings internally. You can pass any JSON-compatible type and we’ll coerce it:Inventory by Location
If you sell products across multiple physical locations, useinventoryByLocation on each variant to tell us how many units are at each store.
Per-location inventory
SetinventoryByLocation to a JSON object mapping your location codes to available quantities. Locations not listed are assumed to have 0 stock:
inventory field is the total across all locations. inventoryByLocation breaks that total down by store.
Store codes are strings you define — store numbers (
"store_1"), location codes ("CARMEL_IN"), or Shopify location IDs ("loc_12345"). Just be consistent. A store not included in the object means 0 units there.Store locations
Store codes are resolved against your vendor’s store location records, which include the store name and address. You can manage store locations via the GraphQL API — see Store Location API below.Store Location API
Manage your physical store and warehouse locations via GraphQL. Store locations must be created before they can be referenced ininventoryByLocation.
Create a Store Location
GraphQL Mutation
Example Request
Response
List Store Locations
GraphQL Query
Example Request
Response
Update a Store Location
GraphQL Mutation
id are optional on update.
Example Request
Response
Delete a Store Location
GraphQL Mutation
Example Request
Response
Deactivate vs Delete
To temporarily stop syncing inventory to a location without losing data, updateisActive to false instead of deleting:
Examples
Full Product with Location Inventory
Full Product with Location Inventory
A physical product with metafields and per-store inventory:
Experience / Class
Experience / Class
Products that are classes or scheduled experiences. Each variant is a specific session with its own date, time, and location:Key conventions for classes:
meta:datetime— ISO 8601 in UTC (e.g.,"2026-03-15T15:00:00Z")meta:location— your store/location codemeta:durationMinutes— class length in minutesmeta:instructor— instructor name (optional)meta:skillLevel— e.g.,"beginner","intermediate","all_levels"inventory— seats remaining (useinventoryPolicy: "DENY"so classes can’t be overbooked)
Minimal New Product
Minimal New Product
The minimum required to create a new product:
Inventory & Location Update (UPDATE)
Inventory & Location Update (UPDATE)
Update total inventory and per-store breakdown:Or update just the total without per-store detail:
Price Update (UPDATE)
Price Update (UPDATE)
Update pricing for a variant — just set
price to the current selling price:Archive Product (UPDATE)
Archive Product (UPDATE)
Archive a discontinued product:Unarchive a product:
Unpublish / Publish (UPDATE)
Unpublish / Publish (UPDATE)
Unpublish a product (set to draft):Publish immediately:Schedule future publish:
Product with Fitment Targets
Product with Fitment Targets
A product linked to specific vehicle fitment targets. Fitment targets must be created via the Fitment API before referencing them in imports.
Fitment target external IDs that don’t match existing targets are ignored with a warning — they won’t cause the import to fail.
SFTP Setup
Generate SSH Keys
Create an ED25519 key pair for secure authentication:~/.ssh/remark_sftp— Private key (keep secure, never share)~/.ssh/remark_sftp.pub— Public key (send to Remark)
Register Your Key
Email your public key to support@remark.ai with:- Your company name
- Your Remark Vendor ID
- Whether this is for staging or production
Connect to SFTP
Upload Files
Always upload to theincoming/ directory:
When triggering the import via API, you only need to specify the filename (e.g.,
products-full-2024-01-15.jsonl). The system automatically looks in your incoming/ directory.File Naming Conventions
Include date/time and mode for easier debugging:Compression
Gzip compression is supported for faster uploads:Recommended Update Cadence
Use REPLACE mode sparingly — once daily is typically sufficient. For all other updates, use UPDATE mode to avoid accidentally archiving products.
Error Handling
GraphQL Errors
Authentication or permission errors return in theerrors array:
Job Errors
When a job completes with errors, checkerrorDetails for specifics:
Best Practices
Use UPDATE for frequent updates
Reserve REPLACE mode for daily syncs. Use UPDATE for inventory, prices, and incremental changes.
Keep external IDs stable
Never change a product’s
externalId. Changing it creates a duplicate product.Use consistent meta: keys
Pick a naming convention for your metafield keys and stick with it. Remark’s AI uses these to understand your products.
Keep location codes stable
Use the same location codes in
inventoryByLocation that match your configured store locations.Validate JSONL locally
Validate each line is valid JSON before uploading. Use
jq or similar tools.Monitor job status
Always check job status after imports to catch and address errors quickly.
Troubleshooting
Products not appearing after import
Products not appearing after import
- Check that
publishedAtis set (null = draft/unpublished) - Verify required fields:
externalId,name,brandName,externalUrl - Query the job status and check for errors
Products unexpectedly archived
Products unexpectedly archived
- This happens with REPLACE mode when products are missing from your file
- Ensure your export includes all active products
- Use UPDATE mode for partial updates
SFTP connection refused
SFTP connection refused
- Verify firewall allows outbound connections to port 22
- Check you’re using the correct private key:
-i ~/.ssh/remark_sftp - Ensure key permissions:
chmod 600 ~/.ssh/remark_sftp - Confirm your public key was registered by Remark
Job stuck in 'processing'
Job stuck in 'processing'
- Large files may take several minutes to process
- Check job status periodically
- Contact support if stuck for more than 30 minutes
Metafields not appearing on products
Metafields not appearing on products
- Ensure keys are prefixed with
meta:(e.g.,"meta:material", not"material") - Keys without the
meta:prefix that aren’t standard fields are silently ignored - Check that values aren’t empty strings (empty strings are stored as
null)
Location inventory not updating
Location inventory not updating
- Verify your location codes in
inventoryByLocationmatch thestoreCodevalues on your store locations - Location codes are case-sensitive —
"STORE_1"and"store_1"are different - Query
storeLocations(vendorId)to confirm your locations exist and are active
Malformed JSON errors
Malformed JSON errors
- Validate each line before upload
- Run:
cat file.jsonl | jq -c . > /dev/nullto check for issues - Check for trailing commas, unescaped quotes, or multi-line objects
REST API Reference
GraphQL Schema Reference
View full GraphQL schema
View full GraphQL schema
Need Help?
Contact Support
Email support@remark.ai with your Vendor ID, job ID (if applicable), error messages, and sample data.