API reference v1.0

Connect your applications to Normly's KPI data platform.

Quick start

1. Get your API token

API access is enabled per company, on request. Ask Matso to enable it for your company; you then get a personal API token. Until then every call answers 403 with "API access is not enabled for your company."

2. Base URL
https://matso.nl/api/
3. Authentication

Include your token in the Authorization header:

Authorization: Token your_token_here
4. Test connection
curl https://matso.nl/api/tables/ \
  -H "Authorization: Token your_token_here"

Main endpoints

KPI tables

List all tables
GET /api/tables/

Get a list of KPI tables you can access.

Table details
GET /api/tables/{id}/

Get detailed information about a specific table.

Table data (recommended)
GET /api/tables/{id}/data/

Get flattened data from a table for Power BI, Excel, and reporting tools.

Pending approvals
GET /api/tables/{id}/pending/

Get data pending approval for a table.

Available filters
  • search - Search by name or description
  • ordering - Sort results (e.g., -created_at)
  • page - Pagination
Example
/api/tables/?search=sales&ordering=-created_at

Data rows

List all rows
GET /api/rows/

Get all data rows with nested cell structure.

Row details
GET /api/rows/{id}/

Get detailed information about a specific row.

Available filters
  • ordering - Sort results by created_at or approved_at (e.g., -approved_at)
  • page, page_size - Pagination

For the approved rows of one table, use /api/tables/{id}/data/.

Example
/api/rows/?ordering=-approved_at
Create a row
POST /api/rows/

Send the table ID and the row's values, keyed by column name. The values are checked with the same rules as the data entry form (required, minimum and maximum, allowed values, unique, ...). You need create rights on the table. The row is a draft, or approved straight away when the table lets you approve your own rows. Values cannot be changed through the API afterwards.

curl -X POST \
  https://matso.nl/api/rows/ \
  -H "Authorization: Token your_token_here" \
  -H "Content-Type: application/json" \
  -d '{"table": 1, "values": {"Revenue": 1200, "Region": "North"}}'

Answers 201 Created with the row and its cells. A value that breaks only a warning rule still creates the row and is marked "Needs review"; rule_warnings lists it:

{
  "rule_warnings": [
    {"column": "Revenue", "rule": "Ensure this value is less than or equal to 10000.00."}
  ]
}

Values that break a rule answer 400 Bad Request with the errors per column; no row is created:

{
  "values": {
    "Revenue": ["Ensure this value is greater than or equal to 0.00."],
    "Profit": ["Unknown column."]
  }
}
Delete a row
DELETE /api/rows/{id}/

You need create rights on the table, or be the row's creator. The API answers 204 No Content and deactivates the row: it stays in the history, nothing is erased. When the table has the four-eyes principle on, deleting an approved row answers 202 Accepted instead: the deletion waits until another approver of the table confirms it in the app.

curl -X DELETE   https://matso.nl/api/rows/1/   -H "Authorization: Token your_token_here"

Approving & rejecting data

Move a pending row through the approval workflow. These actions require approver rights on the table. When the table has the four-eyes principle on, you cannot approve a row you submitted yourself. With it off, rows you create through the API are approved straight away if you may also approve in that table.

Approve a row
POST /api/rows/{id}/approve/
curl -X POST \
  https://matso.nl/api/rows/1/approve/ \
  -H "Authorization: Token your_token_here"
Reject a row
POST /api/rows/{id}/reject/
curl -X POST \
  https://matso.nl/api/rows/1/reject/ \
  -H "Authorization: Token your_token_here" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Numbers do not match source"}'

A reason is optional; it is stored with the row and shown to the editor.

Response examples

Table Data Response
GET /api/tables/1/data/
[
  {
    "id": 1,
    "table_name": "Sales Data",
    "status": "approved",
    "created_at": "2025-01-28T10:30:00Z",
    "created_by_email": "[email protected]",
    "Employee Name": "John Doe",
    "Monthly Sales": "50000",
    "Target Met": "true",
    "Region": "North"
  },
  {
    "id": 2,
    "table_name": "Sales Data", 
    "status": "approved",
    "created_at": "2025-01-28T11:15:00Z",
    "created_by_email": "[email protected]",
    "Employee Name": "Jane Smith",
    "Monthly Sales": "75000",
    "Target Met": "true",
    "Region": "South"
  }
]
Tables List Response
GET /api/tables/
{
  "count": 2,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": 1,
      "name": "Sales Data",
      "description": "Monthly sales figures",
      "version_name": "Q1 2025",
      "version_number": 1,
      "created_at": "2025-01-01T00:00:00Z",
      "company_name": "Your Company",
      "columns": [
        {
          "id": 1,
          "name": "Employee Name",
          "data_type": "text",
          "required": true
        },
        {
          "id": 2,
          "name": "Monthly Sales",
          "data_type": "number",
          "required": true
        }
      ]
    }
  ]
}

Integration Examples

Power BI
  1. Get Data → Web
  2. URL: /api/tables/1/data/
  3. Add Authorization header
  4. Connect and transform
Detailed Guide
Excel
  1. Data → Get Data → From Web
  2. Enter API URL
  3. Configure authentication
  4. Load and refresh
Use the /data/ endpoint for best results
Python
import requests

headers = {
    'Authorization': 'Token your_token'
}

response = requests.get(
    'https://matso.nl/api/tables/1/data/',
    headers=headers
)

data = response.json()
cURL
# List the tables you can access
curl https://matso.nl/api/tables/ \
  -H "Authorization: Token your_token"

# Pull flattened data from one table
curl https://matso.nl/api/tables/1/data/ \
  -H "Authorization: Token your_token"
JavaScript
const res = await fetch(
  "https://matso.nl/api/tables/1/data/",
  { headers: { Authorization: "Token your_token" } }
);

const data = await res.json();
console.log(data);

Authentication

Token Authentication

Every API call authenticates with a personal API token in the Authorization header.

The API does not accept usernames and passwords: a request with a password is refused.

Authorization: Token abc123def456
Rate limits

Each token may make up to 300 requests per minute; unauthenticated callers up to 30 per IP. Above that the API answers 429 Too Many Requests with a Retry-After header.

Error Handling

Status Code Meaning
200 Success - Request completed
400 Bad Request - Check parameters
401 Unauthorized - Check authentication
403 Forbidden - No permission
404 Not Found - Resource doesn't exist
Common Issues
  • 401 Unauthorized: Check your token format and validity
  • 403 Forbidden: API access is not enabled for your company yet, or you don't have access to this table
  • 404 Not Found: Check the table/row ID exists
  • 429 Too Many Requests: Over the rate limit; wait for the seconds in Retry-After
Need Help?

Contact Matso for:

  • Enabling API access for your company
  • Permission issues
  • Integration support
Rate Limits & Best Practices

  • Use the /data/ endpoint for reporting tools
  • Cache responses when possible
  • Filter data at the API level (not client-side)
  • Use pagination for large datasets
  • Implement exponential backoff for retries
  • Monitor your API usage
  • Keep tokens secure and rotate regularly
  • Test with small datasets first

Need Support?

Our team is here to help you integrate with the Normly API successfully.