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
/api/tables/
Get a list of KPI tables you can access.
Table details
/api/tables/{id}/
Get detailed information about a specific table.
Table data (recommended)
/api/tables/{id}/data/
Get flattened data from a table for Power BI, Excel, and reporting tools.
Pending approvals
/api/tables/{id}/pending/
Get data pending approval for a table.
Available filters
search- Search by name or descriptionordering- Sort results (e.g., -created_at)page- Pagination
Example
/api/tables/?search=sales&ordering=-created_at
Data rows
List all rows
/api/rows/
Get all data rows with nested cell structure.
Row details
/api/rows/{id}/
Get detailed information about a specific row.
Available filters
ordering- Sort results bycreated_atorapproved_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
/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
/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
/api/rows/{id}/approve/
curl -X POST \
https://matso.nl/api/rows/1/approve/ \
-H "Authorization: Token your_token_here"
Reject a row
/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
- Get Data → Web
- URL:
/api/tables/1/data/ - Add Authorization header
- Connect and transform
Excel
- Data → Get Data → From Web
- Enter API URL
- Configure authentication
- Load and refresh
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.