Skip to main content
Remark’s Product Import API allows you to import and synchronize your products directly from your systems — including physical goods with per-store inventory, classes and experiences with schedules, and any custom metadata your business needs.

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

Replaces your entire product set. Products not included in the import are archived (soft-deleted).
Only use REPLACE mode for complete product syncs. Any products missing from your file will be archived.
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

Authentication Options


Direct API: Upsert Products

For small to medium batches (up to ~1,000 products), use the upsertProducts 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
Archived vs Unpublished:
  • archived: true — Soft delete. Product is excluded from all search, recommendations, and AI.
  • publishedAt: null — Draft mode. Product exists but is not visible to customers.

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 the incoming/ directory:

Step 2: Trigger Processing

Call the processProductImport mutation to start the import job. Specify just the filename (the system automatically looks in your incoming/ directory):
Variables:
When using API key authentication, vendorId is optional. The vendor is automatically determined from your API key.

Example Request

Response

Save the jobId to check processing status and debug any issues.
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

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

**If externalId is not provided for a variant, the sku will be used as the external ID. This is convenient if your SKUs are already unique identifiers.

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 different externalId 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 with meta: and we’ll extract it automatically. Metafields work on both products and variants.
The 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:
Use whatever keys make sense for your data. The important thing is to be consistent across your products so Remark’s AI can learn what each field means.

Inventory by Location

If you sell products across multiple physical locations, use inventoryByLocation on each variant to tell us how many units are at each store.

Per-location inventory

Set inventoryByLocation to a JSON object mapping your location codes to available quantities. Locations not listed are assumed to have 0 stock:
The 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 in inventoryByLocation.

Create a Store Location

GraphQL Mutation

Example Request

Response

List Store Locations

GraphQL Query

Example Request

Response

Update a Store Location

GraphQL Mutation

Only include the fields you want to change. All fields except id are optional on update.

Example Request

Response

Delete a Store Location

GraphQL Mutation

Deleting a store location removes all associated inventory records for that location. Products that had inventory at the deleted location will be reindexed automatically.

Example Request

Response

Deactivate vs Delete

To temporarily stop syncing inventory to a location without losing data, update isActive to false instead of deleting:
Inactive locations are skipped during inventory sync but retain their configuration.

Examples

Full Product with Location Inventory

A physical product with metafields and per-store inventory:
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 code
  • meta:durationMinutes — class length in minutes
  • meta:instructor — instructor name (optional)
  • meta:skillLevel — e.g., "beginner", "intermediate", "all_levels"
  • inventory — seats remaining (use inventoryPolicy: "DENY" so classes can’t be overbooked)
The minimum required to create a new product:
Update total inventory and per-store breakdown:
Or update just the total without per-store detail:
Update pricing for a variant — just set price to the current selling price:
Archive a discontinued product:
Unarchive a product:
Unpublish a product (set to draft):
Publish immediately:
Schedule future publish:
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:
This creates two files:
  • ~/.ssh/remark_sftpPrivate key (keep secure, never share)
  • ~/.ssh/remark_sftp.pubPublic key (send to Remark)
Never share your private key. Only send the .pub file 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
We’ll confirm when registered and provide your SFTP username.

Connect to SFTP

Upload Files

Always upload to the incoming/ 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:
Files on SFTP are retained for 30 days, then automatically deleted.

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 the errors array:

Job Errors

When a job completes with errors, check errorDetails 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

  • Check that publishedAt is set (null = draft/unpublished)
  • Verify required fields: externalId, name, brandName, externalUrl
  • Query the job status and check for errors
  • 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
  • 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
  • Large files may take several minutes to process
  • Check job status periodically
  • Contact support if stuck for more than 30 minutes
  • 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)
  • Verify your location codes in inventoryByLocation match the storeCode values 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
  • Validate each line before upload
  • Run: cat file.jsonl | jq -c . > /dev/null to check for issues
  • Check for trailing commas, unescaped quotes, or multi-line objects

REST API Reference

Start Import

Request Body:
Response:

Check Job Status

Response:

GraphQL Schema Reference


Need Help?

Contact Support

Email support@remark.ai with your Vendor ID, job ID (if applicable), error messages, and sample data.