Skip to content

Supplier Portal Access

This guide shows how to give a supplier read-only visibility into their own purchase orders - and nothing else. It uses a dedicated role with a Procurement / View privilege and a granular filter that restricts list results to a single supplier number.

When the supplier logs in and calls GET /api/purchase-order, they see only POs where Supplier Number = SUP-007. No other data is exposed.


How Granular Filters Work

Every role can carry a Granular Privileges object alongside its standard privilege list:

{
  "Granular Privileges": {
    "include": {
      "Supplier Number": [[42, "SUP-007"]]
    },
    "exclude": {}
  }
}
Key Description
include Only records matching these values are returned
exclude Records matching these values are hidden

Each entry is a list of [id, display_text] pairs, where id is the internal database ID of the referenced record (e.g. the supplier's integer id from the Supplier Master, not the Supplier Number string).


Prerequisites

Requirement Detail
Auth Active session + CSRF token
Privilege - manage roles Users / Edit

Step 1 - Look Up the Supplier's Internal ID

The granular filter requires the supplier's integer id from the Supplier Master. Fetch it by filtering on the supplier number:

curl -b cookies.txt \
  "https://acme.knosc.com/api/supplier-master?filter[Supplier+Number]=SUP-007"

Response:

{
  "data": {
    "rows": [
      {
        "id": 42,
        "Supplier Number": "SUP-007",
        "Supplier Name": "Acme Supplies Ltd."
      }
    ]
  }
}

Note the id - 42 in this example. This is the value used inside the granular filter, not the "SUP-007" string.


Step 2 - Create the Supplier Role

Create a role with:

  • Procurement / View - read-only access to purchase orders
  • A granular include filter on Supplier Number set to the supplier's internal ID
CSRF=$(grep csrf_access_token cookies.txt | awk '{print $NF}')

curl -b cookies.txt -X POST \
  "https://acme.knosc.com/api/role" \
  -H "Content-Type: application/json" \
  -H "X-XSRF-TOKEN: $CSRF" \
  -d '{
    "Role Name": "Supplier Portal - Acme Supplies",
    "Role Description": "Read-only procurement access restricted to Acme Supplies orders",
    "Privileges": [
      {
        "Privilege Type": "Procurement",
        "Privilege Description": "Read-only purchase order access",
        "Privilege Access": "View"
      }
    ],
    "Granular Privileges": {
      "include": {
        "Supplier Number": [[42, "SUP-007"]]
      },
      "exclude": {}
    }
  }'

Response:

{
  "message": "Role created successfully.",
  "id": 12
}

Note the role id - you need it for the next step.


Step 3 - Verify the Role

Fetch the role to confirm the privilege and granular filter were saved correctly:

curl -b cookies.txt "https://acme.knosc.com/api/role/12"

Response:

{
  "data": {
    "id": 12,
    "Role Name": "Supplier Portal - Acme Supplies",
    "Role Description": "Read-only procurement access restricted to Acme Supplies orders",
    "Privileges": [
      {
        "id": 55,
        "Privilege Type": "Procurement",
        "Privilege Description": "Read-only purchase order access",
        "Privilege Access": "View"
      }
    ],
    "Granular Filters": [
      {
        "category": "Supplier Number",
        "id": 42,
        "text": "SUP-007",
        "type": "autocomplete",
        "exclude": false
      }
    ]
  }
}

"exclude": false confirms this is an include filter - only SUP-007 records are visible.


Step 4 - Create a User Account for the Supplier

Assign the new role when creating the user:

curl -b cookies.txt -X POST \
  "https://acme.knosc.com/api/user" \
  -H "Content-Type: application/json" \
  -H "X-XSRF-TOKEN: $CSRF" \
  -d '{
    "User Fullname": "James Obi (Acme Supplies)",
    "Username": "james.obi@acmesupplies.com",
    "User Email": "james.obi@acmesupplies.com",
    "Role Id": 12,
    "password": "TempPass2024!"
  }'

Response:

{
  "message": "User created successfully.",
  "id": 91
}

Step 5 - Test the Filtered View

Log in as the supplier user and call the purchase order list endpoint:

# Login as the supplier user
curl -c supplier-cookies.txt -X POST \
  "https://acme.knosc.com/api/authenticate" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "james.obi@acmesupplies.com",
    "password": "TempPass2024!"
  }'

# Fetch purchase orders
curl -b supplier-cookies.txt "https://acme.knosc.com/api/purchase-order"

The response contains only POs where Supplier Number = SUP-007. Attempting to request another supplier's PO directly by ID returns 403 User.NotPrivileged.


What the Supplier Can and Cannot Do

Action Allowed
GET /api/purchase-order (their POs only) Yes
GET /api/purchase-order/{id} (their POs only) Yes
GET /api/purchase-order/{id} (another supplier's PO) No - 403
POST /api/purchase-order No - View privilege only
PUT /api/purchase-order/{id} No - View privilege only
DELETE /api/purchase-order/{id} No - View privilege only
Any other module (Sales, Inventory, Users, etc.) No - no privilege granted

Supporting Multiple Suppliers on One Role

To create a shared role for a group of suppliers (e.g. a 3PL that manages multiple), add multiple entries to the include array:

"Granular Privileges": {
  "include": {
    "Supplier Number": [
      [42, "SUP-007"],
      [61, "SUP-019"],
      [78, "SUP-034"]
    ]
  },
  "exclude": {}
}

All three suppliers' POs will be visible to users assigned this role.


Updating the Filter - Adding a New Supplier

Use PUT /api/role/{id} and include the full updated Granular Privileges object. The Privileges array must also be included in full - any privilege omitted will be removed.

First fetch the current role to avoid losing existing data:

curl -b cookies.txt "https://acme.knosc.com/api/role/12"

Then update with the new supplier added:

curl -b cookies.txt -X PUT \
  "https://acme.knosc.com/api/role/12" \
  -H "Content-Type: application/json" \
  -H "X-XSRF-TOKEN: $CSRF" \
  -d '{
    "id": 12,
    "Role Name": "Supplier Portal - Acme Supplies",
    "Role Description": "Read-only procurement access restricted to Acme Supplies orders",
    "Privileges": [
      {
        "id": 55,
        "Privilege Type": "Procurement",
        "Privilege Description": "Read-only purchase order access",
        "Privilege Access": "View"
      }
    ],
    "Granular Privileges": {
      "include": {
        "Supplier Number": [
          [42, "SUP-007"],
          [61, "SUP-019"]
        ]
      },
      "exclude": {}
    }
  }'

Important: PUT /api/role/{id} replaces the full privilege and granular filter configuration. Always read the role first and include all existing privileges in the body to avoid unintentional removal.


Python - Full Setup Script

BASE_URL = "https://acme.knosc.com/api"

def setup_supplier_role(client, supplier_number: str) -> dict:
    """
    Create a supplier portal role restricted to a single supplier.
    Returns {"role_id": int, "supplier_id": int, "supplier_name": str}
    """
    # 1. Resolve supplier internal ID
    result = client.get(f"/supplier-master?filter[Supplier Number]={supplier_number}")
    rows = result["data"]["rows"]
    if not rows:
        raise ValueError(f"Supplier not found: {supplier_number}")
    supplier = rows[0]
    supplier_id = supplier["id"]
    supplier_name = supplier["Supplier Name"]

    # 2. Create role with granular filter
    role_body = {
        "Role Name": f"Supplier Portal - {supplier_name}",
        "Role Description": f"Read-only procurement access restricted to {supplier_name} orders",
        "Privileges": [
            {
                "Privilege Type": "Procurement",
                "Privilege Description": "Read-only purchase order access",
                "Privilege Access": "View",
            }
        ],
        "Granular Privileges": {
            "include": {
                "Supplier Number": [[supplier_id, supplier_number]]
            },
            "exclude": {},
        },
    }
    role_result = client.post("/role", role_body)
    role_id = role_result["id"]
    print(f"Role created: id={role_id} for {supplier_name} ({supplier_number})")

    return {"role_id": role_id, "supplier_id": supplier_id, "supplier_name": supplier_name}


def provision_supplier_user(client, fullname: str, email: str, role_id: int, temp_password: str) -> int:
    """Create a user account for a supplier contact."""
    result = client.post("/user", {
        "User Fullname": fullname,
        "Username": email,
        "User Email": email,
        "Role Id": role_id,
        "password": temp_password,
    })
    user_id = result["id"]
    print(f"User created: {email} (id={user_id})")
    return user_id


# Example usage
role = setup_supplier_role(client, "SUP-007")
provision_supplier_user(client, "James Obi", "james.obi@acmesupplies.com", role["role_id"], "TempPass2024!")

For clarity when managing many suppliers, use a consistent naming pattern:

Pattern Example
Supplier Portal - {Supplier Name} Supplier Portal - Acme Supplies
One role per supplier Easier to revoke access for a specific supplier without affecting others
One shared role per supplier group Suitable when a 3PL or agent manages multiple supplier accounts