NutraSoft food manufacturing ERP software
Esc
  • GuideGetting startedThree steps to your first call and your first order, then the two shapes every response takes.
  • GuideAuthentication and keysBearer keys, test and live environments, rotation without an outage, and what each scope grants.
  • GuideTest modeA second NutraSoft account of your own, seeded with realistic data, that you can safely break.
  • PageErrorsEvery error code the API returns, what it means, what to do about it and whether to retry.
  • PageConventionsThe rules every operation follows: money, time, paging, sorting, nulls, PATCH and identifiers.
Menu

Getting started

Three steps to your first call and your first order, then the two shapes every response takes.
Updated

In short

  • Send the key as Authorization: Bearer <key> to one base URL. The key's prefix, not the URL, decides live or test.
  • Make GET /organization your first call: it needs no scope and names the key's account and environment.
  • Lists return data, pagination and order; a single object returns data. Never a bare array.
  • Branch on the error code, never on message, and keep the request_id for support.

The NutraSoft API is a REST API over your ERP data: the catalog, lots and warehouses, production runs, sales and purchasing. It speaks JSON, it is authenticated with an API key, and every response has the same envelope. Three steps get you from nothing to an order in your test account.

Before you start

  • The API is included in the paid Enterprise plan. Free trials cannot create API keys or a test account.
  • Keys are managed in NutraSoft under Settings, Developer, by the license administrator or a user they have given Developer access.

Your first call in three steps

  1. Create a test key

    In Settings, Developer, create your test account on the Sandbox card, then a test key. It is shown once, so store it before you close the dialog.

  2. Call GET /organization

    Put the key in NUTRASOFT_API_KEY and run this. It needs no scope. Expect environment to be test.

    GET /organization
    curl "https://public-api.nutrasoft.ca/v1/organization" \
      -H "Authorization: Bearer $NUTRASOFT_API_KEY"
    Response200
    {
      "data": {
        "api_key_id": 123,
        "company": "Example Foods Inc.",
        "currency": "CAD",
        "environment": "test",
        "key_prefix": "nsk_test_9fA",
        "license_id": 123,
        "rate_limit_per_min": 300,
        "scopes": [
          "catalog:read",
          "sales:read",
          "sales:write"
        ]
      }
    }
  3. Create a sales order

    Take a customer id from GET /customers and a product id from GET /products, then post the order. It stays in your test account.

    POST /sales-orders
    curl -X POST "https://public-api.nutrasoft.ca/v1/sales-orders" \
      -H "Authorization: Bearer $NUTRASOFT_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
      "customer_id": 123,
      "lines": [
        {
          "order_qty": 12.5,
          "product_id": 123
        }
      ]
    }'
    Response201
    {
      "data": {
        "id": 123,
        "billing_address": {
          "city": "Montreal",
          "line1": "123 Example Street",
          "line2": "Suite 200",
          "postal_code": "H4S 2C1",
          "state": "QC"
        },
        "company": "Example Foods Inc.",
        "created_at": "2026-09-10T14:30:00Z",
        "customer_id": 123,
        "customer_purchase_order_no": "PO-10042",
        "expected_date": "2026-09-10",
        "lines": [
          {
            "id": 123,
            "cancelled_qty_base": 12.5,
            "discount_rate": 12.5,
            "notes": "string",
            "order_qty": 12.5,
            "order_qty_base": 12.5,
            "price": 12.5,
            "product_code": "SAMPLE-01",
            "product_id": 123,
            "product_name": "Sample name",
            "shipped_qty_base": 12.5,
            "tax1": false,
            "tax2": false,
            "uom_type": "INVENTORY_UNIT"
          }
        ],
        "order_date": "2026-09-10",
        "sales_order_no": 10,
        "ship_date": "2026-09-10",
        "shipping_address": {
          "city": "Montreal",
          "line1": "123 Example Street",
          "line2": "Suite 200",
          "postal_code": "H4S 2C1",
          "state": "QC"
        },
        "status": "OPEN",
        "subtotal": 12.5,
        "tax1": false,
        "tax1_rate": 12.5,
        "tax2": false,
        "tax2_rate": 12.5,
        "tracking_number": "string",
        "updated_at": "2026-09-10T14:30:00Z"
      }
    }

Keys and environments

PrefixEnvironmentReads and writes
nsk_live_LiveYour real business data
nsk_test_TestYour test account, a separate copy you can safely break

There is one base URL for both environments. The key decides which one you are in, so a test suite cannot be pointed at live data by editing a URL. Check environment in your test setup: it is the cheapest guard against a suite that has quietly been pointed at production. See Test mode.

Read the envelope

A list comes back as data, pagination and order. A single object comes back as {"data": {...}}. Nothing is ever a bare array, so fields can be added without breaking you.

json
{
  "data": [{ "id": 123 }],
  "pagination": { "limit": 50, "offset": 0, "total": 412, "has_more": true },
  "order": "name:asc,id:asc"
}

Use has_more to decide whether to fetch the next page. Conventions covers paging, sorting and null values.

Handle errors

Every error has the same shape. Branch on code, never on message: codes are added over time but never renamed or repurposed, while messages are written for people and may change.

json
{
  "error": {
    "code": "INVALID_API_KEY",
    "message": "The API key is missing, malformed, revoked or not valid for this environment.",
    "details": null,
    "request_id": "req_4f1c8a90b7e24d3f9c0a5b6e7d8f9012"
  }
}
What you seeWhat to check
401 UNAUTHENTICATEDNo Authorization header, or it is not Bearer <key>.
401 INVALID_API_KEYA wrong or revoked key, or one whose nsk_live_ or nsk_test_ prefix does not match its account.
403 NOT_ENTITLEDThe account is not on a paid Enterprise plan, or its plan has lapsed.
403 INSUFFICIENT_SCOPEThe key does not carry the scope the operation needs.
429 RATE_LIMITEDSlow down. Retry-After says how many seconds to wait.

Keep the request_id. It is in every error body and in the X-Request-Id header of every response, and it is what lets support find your exact request.

Next steps

Once you are reading data, look at Webhooks: rather than polling for changes, register a URL and we will POST to it within a minute of one.