Documentation
Getting Started
Installation
Data Grid
Approvals
Administration
Account & Security
Integration & Staging
Architecture

API Keys

Overview

The REST API lets external applications read and write entity records programmatically over HTTP. Access is controlled with API keys — each key is tied to a service account (API user) that participates in the same role and permission system as regular users.

API Users admin view
API Users admin view(click to enlarge)

The API is designed for server-to-server use only. No CORS headers are set — browser-based calls from a different origin will be blocked. Always call the API from a backend service, script, or integration platform.


Enabling / Disabling the API

There is a global kill switch in Admin → General Settings → API tab.

  • On (default): all valid API key requests are processed normally
  • Off: every request using X-API-Key returns 503 Service Unavailable, regardless of key validity

Use this to temporarily suspend all external access without deleting any keys.


Managing API Users

Go to Admin → Access Management → API Users to create and manage API users.

Each API user is a service account — a dedicated identity for one external integration. API users are subject to the same roles and permissions as regular users. Assign only the roles the integration actually needs.

Create an API user:

  1. Go to Admin → Access Management → API Users and click New API user
  2. Enter a display name (required)
  3. Assign one or more roles — the API user inherits all permissions from those roles
  4. Optionally set an expiry date (leave blank for no expiry)
  5. Click Save

The API key is shown once immediately after creation. Copy it now — it cannot be retrieved again. Only a SHA-256 hash is stored in the database.

Edit an API user: Click any row to open the edit form. You can change the display name, role assignments, active status, or expiry date. The key is not affected.

Regenerate a key: Click Regenerate key on the API user row. The old key is immediately invalidated — any integration using it will receive 401 Unauthorized on the next request. The new key is shown once.

Delete an API user: Removes the service account. All existing calls using that key immediately return 401 Unauthorized.


Authenticating Requests

Send the API key in every request as the X-API-Key header:

X-API-Key: prm_<your-key>

The key format is prm_ followed by a random string. Never send the key as a query parameter or in the request body.


REST API Reference

All endpoints are under /api/v1/. Every response uses the same envelope format:

{
  "success": true,
  "data": { ... },
  "pagination": { "page": 1, "pageSize": 100, "total": 542, "totalPages": 6 }
}

Errors return:

{ "success": false, "error": { "code": "PERMISSION_DENIED", "message": "..." } }

Entities

MethodPathDescription
GET/api/v1/entitiesList all entities the API user can access
GET/api/v1/entities/:entityIdGet entity metadata and attribute definitions

Records

MethodPathDescription
GET/api/v1/entities/:entityId/recordsList records (paginated)
GET/api/v1/entities/:entityId/records/:idGet a single record
POST/api/v1/entities/:entityId/recordsCreate a record
PUT/api/v1/entities/:entityId/records/:idUpdate a record
DELETE/api/v1/entities/:entityId/records/:idDelete a record

Pagination parameters (on GET /records):

ParameterDefaultMaxDescription
page1Page number
pageSize1001000Records per page
searchFree-text search across Code and Name

Approval workflows and the API

Write operations through the REST API (POST, PUT, DELETE) write directly to the master data — they do not go through the approval workflow, even if the entity has Requires approval enabled.

This is intentional. API integrations are treated as trusted automated systems. If you give an integration write access via an API key, the assumption is that the data has already been validated upstream.

Audit log entries are still created for every API write, so all changes remain traceable.

Note: The staging pipeline also bypasses approval workflows for the same reason. If you need human review before data lands in production, implement that gate in the source system before it reaches Primentra.

Usage Tracking

Every API request is automatically counted per key. Primentra tracks both the total call count and a CRUD breakdown:

CounterIncremented on
TotalEvery authenticated API request
ReadGET requests (list/get records)
CreatePOST requests (new records)
UpdatePUT requests (modify records)
DeleteDELETE requests (remove records)

These counters are visible in:

  • Admin → API Users table — per-key totals with R/C/U/D badges
  • Dashboard → API Keys widget — aggregated across all keys
  • Dashboard → Key Metrics — total API calls KPI card

Tracking is fire-and-forget — it never slows down API responses.


Practical Examples

1. Discover your entities

First, list all entities the API user has access to. This gives you the entityId values you need for subsequent calls.

curl https://your-primentra-server/api/v1/entities \
  -H "X-API-Key: prm_Ab12XyZwQrstUv34"

Response:

{
  "success": true,
  "data": [
    { "id": 3, "name": "Customer", "modelId": 1, "modelName": "CRM" },
    { "id": 7, "name": "Product",  "modelId": 2, "modelName": "Products" }
  ]
}

2. Inspect an entity's attributes

Before creating or updating records, check what attributes the entity has and what data types they use.

curl https://your-primentra-server/api/v1/entities/3 \
  -H "X-API-Key: prm_Ab12XyZwQrstUv34"

Response:

{
  "success": true,
  "data": {
    "id": 3,
    "name": "Customer",
    "attributes": [
      { "id": 12, "name": "Region",   "dataType": "Domain",  "isRequired": true },
      { "id": 13, "name": "Tier",     "dataType": "Text",    "isRequired": false },
      { "id": 14, "name": "IsActive", "dataType": "Boolean", "isRequired": false }
    ]
  }
}

3. Read records

# First page, 50 records per page
curl "https://your-primentra-server/api/v1/entities/3/records?pageSize=50" \
  -H "X-API-Key: prm_Ab12XyZwQrstUv34"

# Search by name or code
curl "https://your-primentra-server/api/v1/entities/3/records?search=Acme" \
  -H "X-API-Key: prm_Ab12XyZwQrstUv34"

Records are returned as flat objects. Domain attributes include a _FieldName_display property with the human-readable label:

{
  "id": 101,
  "code": "CUST-001",
  "name": "Acme Corp",
  "Region": 5,
  "_Region_display": "{EU} Europe",
  "Tier": "Gold",
  "IsActive": 1
}

4. Create a record

The request body requires code or name (or both). Pass additional attribute values in the values array, each with an attributeId and the appropriate value field.

curl -X POST https://your-primentra-server/api/v1/entities/3/records \
  -H "X-API-Key: prm_Ab12XyZwQrstUv34" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "CUST-050",
    "name": "New Customer Ltd",
    "values": [
      { "attributeId": 12, "intValue": 5 },
      { "attributeId": 13, "textValue": "Silver" },
      { "attributeId": 14, "intValue": 1 }
    ]
  }'

Response:

{ "success": true, "data": { "id": 102 } }

5. Update a record

Same body shape as create. Only fields present in values are updated — omitted attributes are left unchanged.

curl -X PUT https://your-primentra-server/api/v1/entities/3/records/102 \
  -H "X-API-Key: prm_Ab12XyZwQrstUv34" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "CUST-050",
    "name": "New Customer Ltd",
    "values": [
      { "attributeId": 13, "textValue": "Gold" }
    ]
  }'

6. Delete a record

curl -X DELETE https://your-primentra-server/api/v1/entities/3/records/102 \
  -H "X-API-Key: prm_Ab12XyZwQrstUv34"

Values array — field names by data type

When creating or updating records, use the correct field name in each values entry:

Attribute data typeField in valuesExample
TexttextValue"textValue": "Gold"
IntintValue"intValue": 42
DecimaldecimalValue"decimalValue": 9.99
BooleanintValue"intValue": 1 (1 = true, 0 = false)
DateTimedateTimeValue"dateTimeValue": "2026-01-15T00:00:00"
DomainintValue"intValue": 5 (foreign key ID of the domain record)

Python example

import requests

BASE = "https://your-primentra-server/api/v1"
HEADERS = {"X-API-Key": "prm_Ab12XyZwQrstUv34"}

# List entities
entities = requests.get(f"{BASE}/entities", headers=HEADERS).json()

# Read all records (paginated)
page, records = 1, []
while True:
    r = requests.get(f"{BASE}/entities/3/records", headers=HEADERS,
                     params={"page": page, "pageSize": 500}).json()
    records.extend(r["data"])
    if page >= r["pagination"]["totalPages"]:
        break
    page += 1

# Create a record
new = requests.post(f"{BASE}/entities/3/records", headers=HEADERS, json={
    "code": "CUST-099",
    "name": "Example Corp",
    "values": [{"attributeId": 13, "textValue": "Bronze"}]
}).json()
print("Created ID:", new["data"]["id"])

JavaScript / Node.js example

const BASE = 'https://your-primentra-server/api/v1';
const HEADERS = { 'X-API-Key': 'prm_Ab12XyZwQrstUv34', 'Content-Type': 'application/json' };

// Read records
const list = await fetch(`${BASE}/entities/3/records?pageSize=100`, { headers: HEADERS });
const { data, pagination } = await list.json();

// Create a record
const res = await fetch(`${BASE}/entities/3/records`, {
  method: 'POST',
  headers: HEADERS,
  body: JSON.stringify({
    code: 'CUST-100',
    name: 'Acme New',
    values: [{ attributeId: 13, textValue: 'Gold' }],
  }),
});
const { data: { id } } = await res.json();

Error Codes

CodeHTTPMeaning
API_DISABLED503Global API kill switch is off
INVALID_KEY401Key not found or invalid
KEY_DISABLED401Key or linked user is inactive
KEY_EXPIRED401Key is past its expiry date
PERMISSION_DENIED403Role lacks required permission for the operation
NOT_FOUND404Entity or record does not exist
VALIDATION_ERROR400Missing required field or invalid input
INTERNAL_ERROR500Unexpected server error

─── Technical ───

Database tables: `ApiKeys`, `Users` (UserType = 'api')

ColumnTableDescription
KeyHashApiKeysSHA-256 hash of the raw key — raw key is never stored
KeyPrefixApiKeysFirst 12 characters of the raw key, shown in the UI for identification
CallCountApiKeysIncremented on every successful authenticated request
LastUsedAtApiKeysTimestamp of the most recent authenticated request
ExpiresAtApiKeysNULL = never expires
IsDeletedApiKeysSoft-delete — row is retained for audit, key is rejected on auth
UserTypeUsers'api' for service accounts, 'standard' for human users

Authentication flow:

  1. Client sends X-API-Key: prm_<key> in the request header
  2. Server computes SHA-256 of the key and looks up the hash in ApiKeys
  3. If found and valid (not deleted, not inactive, not expired, API not globally disabled): request proceeds as the linked API user
  4. On success: CallCount is incremented and LastUsedAt is updated
  5. Role-based permission checks run identically to regular user requests

Kill switch caching: The global API enabled/disabled setting is cached in memory for up to 30 seconds to reduce database load. After toggling the kill switch, allow up to 30 seconds before the change takes effect on all requests.

Ready to get started?

Start managing your master data with Primentra today.

View Pricing