API Documentation
Overview
The FractionERP REST API provides access to your data for external integrations. All API requests require authentication using a Bearer token.
Base URL
https://fraction.app/api/v1
Rate Limiting
API requests are limited to 2,000 requests per hour per user. Rate limit information is included in response headers:
| Header | Description |
|---|---|
X-RateLimit-Limit |
Maximum requests per hour |
X-RateLimit-Remaining |
Remaining requests in current window |
X-RateLimit-Reset |
Unix timestamp when the limit resets |
Authentication
Authenticate using your existing FractionERP email and password to obtain an access token.
1. Login to Get Token
POST https://fraction.app/api/v1/auth/login
Content-Type: application/json
{
"email": "your.email@company.com",
"password": "your_password"
}
Response:
{
"success": true,
"data": {
"token_type": "Bearer",
"access_token": "eyJ0eXAiOiJKV1Q...",
"expires_at": "2026-07-13T18:00:00+00:00",
"user": { "id": 1, "name": "John Doe", "email": "john@company.com" },
"tenant": { "id": 1, "company": "Company Ltd", "plan": "pro" }
}
}
2. Use Token in Requests
Include the token in the Authorization header for all API requests:
GET https://fraction.app/api/v1/parts
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/json
Authentication Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/auth/login |
Login and get access token |
| POST | /api/v1/auth/logout |
Revoke current token |
| GET | /api/v1/auth/me |
Get current user info |
| POST | /api/v1/auth/refresh |
Get a new token (revokes old) |
Data Endpoints
All data endpoints require authentication. List endpoints return paginated results.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/customers |
List all customers |
| GET | /api/v1/customers/{id} |
Get single customer with contacts |
| GET | /api/v1/customers/{id}/contacts |
List contacts for a customer |
| POST | /api/v1/customers/{id}/contacts |
Create a new contact for a customer |
| GET | /api/v1/parts |
List all parts |
| GET | /api/v1/parts/{id} |
Get single part |
| POST | /api/v1/parts |
Create a new part (manufactured by default) |
| GET | /api/v1/parts/{id}/price-lists |
Get customer price lists for a part |
| PUT | /api/v1/parts/{id}/price-lists |
Create or replace customer price breaks for a part |
| GET | /api/v1/contracts |
List all contracts/sales orders |
| GET | /api/v1/contracts/{id} |
Get single contract |
| POST | /api/v1/contracts |
Create a new enquiry |
| GET | /api/v1/boms |
List all bills of materials |
| GET | /api/v1/boms/{id} |
Get single BOM |
| GET | /api/v1/work-orders |
List all work orders |
| GET | /api/v1/work-orders/{id} |
Get single work order |
| GET | /api/v1/purchase-orders |
List all purchase orders |
| GET | /api/v1/purchase-orders/{id} |
Get single purchase order |
| GET | /api/v1/shipments |
List all shipments |
| GET | /api/v1/shipments/{id} |
Get single shipment |
| GET | /api/v1/invoices |
List all invoices |
| GET | /api/v1/invoices/{id} |
Get single invoice |
Query Parameters
All list endpoints support the following query parameters:
| Parameter | Description | Example |
|---|---|---|
page |
Page number (1-based) | ?page=2 |
per_page |
Items per page (max 100, default 25) | ?per_page=50 |
search |
Search term | ?search=widget |
sort |
Sort field | ?sort=created_at |
order |
Sort direction (asc/desc) | ?order=desc |
Response Format
All API responses follow this structure:
Success Response
{
"success": true,
"data": [ ... ],
"meta": {
"pagination": {
"total": 100,
"per_page": 25,
"current_page": 1,
"last_page": 4,
"from": 1,
"to": 25,
"has_more": true
}
},
"errors": null
}
Error Response
{
"success": false,
"data": null,
"meta": null,
"errors": [
{
"code": "UNAUTHORIZED",
"message": "Invalid credentials."
}
]
}
Write Endpoints
Create Customer Contact
Add a new contact to an existing customer.
POST /api/v1/customers/{id}/contacts
Content-Type: application/json
{
"name": "John Smith",
"email": "john@example.com",
"phone": "+44 123 456 7890",
"job_title": "Purchasing Manager"
}
| Field | Required | Description |
|---|---|---|
name |
Yes | Contact name |
email |
No | Email address |
phone |
No | Phone number |
job_title |
No | Job title |
Create Enquiry
Create a new enquiry for a customer. A quote number (Q-number) is auto-generated. You can pass either customer_id or customer_name to identify the customer.
POST /api/v1/contracts
Content-Type: application/json
{
"customer_name": "Acme Corp",
"customer_contact_id": 42,
"customer_ref": "PO-12345",
"notes": "Urgent order"
}
| Field | Required | Description |
|---|---|---|
customer_name |
Yes (or customer_id) |
Customer name |
customer_contact_id |
No | Contact ID |
customer_ref |
No | Customer reference |
notes |
No | Notes |
Response (201):
{
"success": true,
"data": {
"id": 653,
"quote_number": "Q1235",
"status": "Enquiry",
"customer": { "id": 10, "name": "Acme Corp" },
"customer_ref": "PO-12345",
"currency": "GBP",
"contract_date": "2026-03-25"
}
}
Create Part
Create a new part. Parts default to Manufactured type. Duplicate part numbers are rejected with a 409 error.
POST /api/v1/parts
Content-Type: application/json
{
"part_number": "WIDGET-001",
"description": "Custom widget",
"revision": "A",
"uom": "EA",
"material": "Stainless Steel",
"category": "Machined Parts"
}
| Field | Required | Description |
|---|---|---|
part_number |
Yes | Must be unique |
description |
No | Part description |
revision |
No | Revision letter |
uom |
No | Unit of measure (default: EA) |
material |
No | Material type |
category |
No | Part category |
Duplicate Response (409):
{
"success": false,
"errors": [{ "code": "DUPLICATE", "message": "A part with this part number already exists." }]
}
Get Customer Price Lists for a Part
Returns all customer price lists associated with a part. Optionally filter by customer_id.
GET /api/v1/parts/{id}/price-lists
GET /api/v1/parts/{id}/price-lists?customer_id=52
Response:
{
"success": true,
"data": [
{
"part_customer_id": 1,
"customer_id": 52,
"customer_name": "Acme Corp",
"customer_part_number": "ACM-100",
"selling_price": "10.50000",
"price_breaks": [
{ "id": 1, "qty": 1, "price": 12.50 },
{ "id": 2, "qty": 100, "price": 10.00 },
{ "id": 3, "qty": 500, "price": 8.50 }
]
}
]
}
Update Customer Price List
Create or replace customer price breaks for a part. If the customer is not yet linked to the part, the link is created automatically. Existing price breaks are replaced entirely. Only base currency prices are supported.
PUT /api/v1/parts/{id}/price-lists
Content-Type: application/json
{
"customer_id": 52,
"customer_part_number": "ACM-100",
"selling_price": 10.50,
"price_breaks": [
{ "qty": 1, "price": 12.50 },
{ "qty": 100, "price": 10.00 },
{ "qty": 500, "price": 8.50 }
]
}
| Field | Required | Description |
|---|---|---|
customer_id |
Yes | Customer ID |
customer_part_number |
No | Customer’s part number |
selling_price |
No | Base selling price |
price_breaks |
Yes | Array of qty/price pairs (min 1) |
Response returns the updated price list in the same format as the GET endpoint.
Error Codes
| HTTP Status | Error Code | Description |
|---|---|---|
| 401 | UNAUTHORIZED |
Missing or invalid authentication token |
| 401 | INVALID_CREDENTIALS |
Wrong email or password |
| 403 | FORBIDDEN |
Access denied |
| 403 | PLAN_RESTRICTED |
API access requires Pro or Moulding plan |
| 403 | ACCOUNT_INACTIVE |
Tenant account is inactive |
| 404 | NOT_FOUND |
Resource not found |
| 409 | DUPLICATE |
Resource already exists (e.g. duplicate part number) |
| 422 | VALIDATION_ERROR |
Invalid input data (see errors array for field-level details) |
| 429 | RATE_LIMIT_EXCEEDED |
Too many requests, please wait |
| 500 | SERVER_ERROR |
Internal server error |