Executive Summary
The INNOVATECH GROUP API provides authenticated, read-only access to billing and profile data for INNOVATECH client accounts. Authentication uses Laravel Sanctum personal access tokens created via POST /api/v1/tokens. Each token can be scoped to one or more of four abilities: invoices:read, invoices:download, quotes:read, and profile:read. All protected endpoints are served under the /api/v1/ base path, require a Bearer token in the Authorization header, and are subject to rate limiting — exceeding the limit returns HTTP 429 with a Retry-After header indicating when requests may resume.
The INNOVATECH GROUP API gives developers programmatic access to billing data (invoices and quotes) and profile information for an authenticated INNOVATECH GROUP client account. It is designed for integrating billing data into internal dashboards, ERP systems, accounting software, or custom reporting tools.
The API is read-only — you can retrieve invoices, download invoice PDFs, list quotes, and read profile data. Token creation is the only write operation.
Prerequisites
Before you begin, make sure you have:
- An active INNOVATECH GROUP client account with a known email address and password
- A REST client for testing requests —
curl, Postman, Insomnia, or any HTTP library in your preferred programming language - Basic familiarity with HTTP methods, headers, and JSON response bodies
Base URL
All API endpoints are served under:
https://{portal-domain}/api/v1/
Replace {portal-domain} with the domain of your INNOVATECH GROUP client portal. All examples in this article use relative paths starting from /api/v1/.
Authentication
The INNOVATECH GROUP API uses Laravel Sanctum personal access tokens for authentication. Tokens are created by exchanging your client account credentials for a bearer token via the token creation endpoint.
Creating a Token
Endpoint: POST /api/v1/tokens
This endpoint is unauthenticated — you do not need an existing token to create one.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
email |
string (email) | Yes | Your INNOVATECH GROUP client account email address |
password |
string | Yes | Your client account password |
device_name |
string (max 255) | Yes | A descriptive label for the token (e.g. erp-integration, accounting-sync) |
abilities |
array of strings | No | The ability scopes to grant. If omitted, the token receives full access (["*"]) |
Example request:
curl -X POST https://{portal-domain}/api/v1/tokens \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"email": "client@example.com",
"password": "your-password",
"device_name": "erp-integration",
"abilities": ["invoices:read", "invoices:download"]
}'
Success response — 201 Created:
{
"token": "1|a1b2c3d4e5f6g7h8i9j0...",
"abilities": ["invoices:read", "invoices:download"]
}
The token field contains the plain-text token string. This is the only time the full token is returned — store it securely immediately.
The abilities field confirms which abilities were actually granted to the token.
Behaviour when abilities is omitted:
If you do not include the abilities field in your request, the token is granted full access:
{
"token": "2|k1l2m3n4o5p6q7r8s9t0...",
"abilities": ["*"]
}
A ["*"] token can access all protected endpoints.
Behaviour when unknown abilities are requested:
If you include ability strings that are not in the allowed set, they are silently removed. Only valid abilities are granted. If the intersection of your requested abilities and the allowed set is empty, the request returns a 422 Unprocessable Entity validation error.
Authentication failure — 422 Unprocessable Entity:
If the email and password combination is invalid, the response contains a validation error on the email field:
{
"message": "The provided credentials are incorrect.",
"errors": {
"email": ["The provided credentials are incorrect."]
}
}
Ability Scopes
Abilities control which endpoints a token can access. The INNOVATECH GROUP API supports four ability scopes:
| Ability | Description |
|---|---|
invoices:read |
Intended for invoice retrieval — list and view invoices |
invoices:download |
Download invoice PDF files |
quotes:read |
List and view quotes |
profile:read |
Retrieve the authenticated user's profile information |
When creating a token, request only the abilities your integration actually needs. This follows the principle of least privilege — if a token is compromised, the exposure is limited to the abilities it was granted.
Note: The invoice list and detail endpoints (
GET /api/v1/invoicesandGET /api/v1/invoices/{invoice}) accept any valid authenticated token and do not enforce a specific ability at the route level. Includinginvoices:readwhen creating your token is recommended as a scoping convention — it documents the token's intended purpose and future-proofs your integration if ability enforcement is tightened.
Using the Token
Include the token in the Authorization header of every request to a protected endpoint:
Authorization: Bearer 1|a1b2c3d4e5f6g7h8i9j0...
All protected endpoints require this header. Requests without a valid token receive a 401 Unauthenticated response.
Endpoint Reference — Invoices
List Invoices
GET /api/v1/invoices
Returns a paginated list of invoices belonging to the authenticated client account. Results are paginated at 15 items per page.
Example request:
curl -X GET https://{portal-domain}/api/v1/invoices \
-H "Authorization: Bearer {token}" \
-H "Accept: application/json"
Example response — 200 OK:
{
"data": [
{
"id": 1042,
"invoicenum": "INV-1042",
"status": "Paid",
"date": "2026-03-01",
"duedate": "2026-03-15",
"datepaid": "2026-03-10",
"subtotal": "1500.00",
"tax": "225.00",
"total": "1725.00",
"paymentmethod": "payfast",
"notes": ""
}
],
"links": {
"first": "https://{portal-domain}/api/v1/invoices?page=1",
"last": "https://{portal-domain}/api/v1/invoices?page=3",
"prev": null,
"next": "https://{portal-domain}/api/v1/invoices?page=2"
},
"meta": {
"current_page": 1,
"last_page": 3,
"per_page": 15,
"total": 42
}
}
To retrieve subsequent pages, append the page query parameter:
GET /api/v1/invoices?page=2
Retrieve a Single Invoice
GET /api/v1/invoices/{invoice}
Returns a single invoice by its ID, scoped to the authenticated client account. Returns 404 Not Found if the invoice does not exist or does not belong to the authenticated user.
Example request:
curl -X GET https://{portal-domain}/api/v1/invoices/1042 \
-H "Authorization: Bearer {token}" \
-H "Accept: application/json"
Example response — 200 OK:
{
"data": {
"id": 1042,
"invoicenum": "INV-1042",
"status": "Paid",
"date": "2026-03-01",
"duedate": "2026-03-15",
"datepaid": "2026-03-10",
"subtotal": "1500.00",
"tax": "225.00",
"total": "1725.00",
"paymentmethod": "payfast",
"notes": ""
}
}
Download an Invoice PDF
GET /api/v1/invoices/{invoice}/download
Required ability: invoices:download
Returns a JSON response containing the invoice ID and a URL from which the PDF can be downloaded.
Example request:
curl -X GET https://{portal-domain}/api/v1/invoices/1042/download \
-H "Authorization: Bearer {token}" \
-H "Accept: application/json"
Example response — 200 OK:
{
"data": {
"invoice_id": 1042,
"download_url": "https://{portal-domain}/client/invoices/1042/download"
}
}
Use the download_url to retrieve the actual PDF file. The URL requires the same authenticated session or token depending on your integration approach.
403 Forbidden is returned if the token does not include the invoices:download ability.
Endpoint Reference — Quotes
All quote endpoints require the quotes:read ability on the token.
List Quotes
GET /api/v1/quotes
Returns a paginated list of quotes belonging to the authenticated client account. Results are paginated at 15 items per page.
Example request:
curl -X GET https://{portal-domain}/api/v1/quotes \
-H "Authorization: Bearer {token}" \
-H "Accept: application/json"
Example response — 200 OK:
{
"data": [
{
"id": 87,
"subject": "Website Redesign Proposal",
"stage": "Delivered",
"datecreated": "2026-02-15",
"validuntil": "2026-03-15",
"subtotal": "25000.00",
"tax1": "3750.00",
"tax2": "0.00",
"total": "28750.00",
"customernotes": "Includes responsive design and SEO audit."
}
],
"links": {
"first": "https://{portal-domain}/api/v1/quotes?page=1",
"last": "https://{portal-domain}/api/v1/quotes?page=1",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"last_page": 1,
"per_page": 15,
"total": 5
}
}
403 Forbidden is returned if the token does not include the quotes:read ability.
Retrieve a Single Quote
GET /api/v1/quotes/{quote}
Required ability: quotes:read
Returns a single quote by its ID, scoped to the authenticated client account. Returns 404 Not Found if the quote does not exist or does not belong to the authenticated user.
Example request:
curl -X GET https://{portal-domain}/api/v1/quotes/87 \
-H "Authorization: Bearer {token}" \
-H "Accept: application/json"
Example response — 200 OK:
{
"data": {
"id": 87,
"subject": "Website Redesign Proposal",
"stage": "Delivered",
"datecreated": "2026-02-15",
"validuntil": "2026-03-15",
"subtotal": "25000.00",
"tax1": "3750.00",
"tax2": "0.00",
"total": "28750.00",
"customernotes": "Includes responsive design and SEO audit."
}
}
Endpoint Reference — Profile
Retrieve Profile
GET /api/v1/profile
Required ability: profile:read
Returns the profile information of the authenticated client account.
Example request:
curl -X GET https://{portal-domain}/api/v1/profile \
-H "Authorization: Bearer {token}" \
-H "Accept: application/json"
Example response — 200 OK:
{
"data": {
"id": 15,
"firstname": "Jane",
"lastname": "Mokoena",
"email": "jane@example.co.za",
"companyname": "Mokoena Trading (Pty) Ltd",
"address1": "45 Main Road",
"city": "Cape Town",
"state": "Western Cape",
"postcode": "8001",
"country": "ZA",
"phonenumber": "+27211234567"
}
}
403 Forbidden is returned if the token does not include the profile:read ability.
Rate Limiting
All protected endpoints are subject to rate limiting via the throttle:api middleware. The current limit is 60 requests per minute per authenticated user. Unauthenticated requests (such as token creation) are rate-limited per IP address at the same threshold.
When the limit is exceeded, the API returns:
429 Too Many Requests:
HTTP/1.1 429 Too Many Requests
Retry-After: 30
The Retry-After header indicates the number of seconds to wait before sending another request. This follows the convention defined in RFC 6585.
Best practices for handling rate limits:
- Implement exponential backoff when you receive a
429response — wait for at least the duration specified inRetry-Afterbefore retrying - Avoid polling endpoints at high frequency; cache results locally where possible
- If your integration processes invoices or quotes in bulk, introduce a short delay between requests to stay within the limit
Error Reference
| HTTP Status | Meaning | Common Cause |
|---|---|---|
401 Unauthenticated |
Missing or invalid token | The Authorization: Bearer {token} header is absent, malformed, or the token has been revoked |
403 Forbidden |
Insufficient ability or resource ownership | The token does not have the required ability scope, or the requested resource belongs to a different client account |
404 Not Found |
Resource does not exist | The invoice or quote ID does not exist or does not belong to the authenticated user |
422 Unprocessable Entity |
Validation error | Invalid credentials on token creation, or no valid abilities in the request |
429 Too Many Requests |
Rate limit exceeded | More than 60 requests were sent within the current one-minute window |
500 Internal Server Error |
Server error | An unexpected error occurred — contact INNOVATECH GROUP support if the issue persists |
Best Practices
Token Management
- One token per integration: Create a separate token for each system that connects to the API. This makes it easy to revoke access for a specific integration without affecting others.
- Descriptive device names: Use the
device_namefield to clearly identify the purpose of each token (e.g.xero-invoice-sync,internal-dashboard,monthly-report-script). This helps you track which tokens are in use and what they are for. - Least-privilege abilities: Request only the abilities your integration needs. An accounting sync that only reads invoices should not have
profile:readorquotes:readaccess.
Token Security
- Treat tokens as secrets: A Sanctum token grants the same access as the client credentials used to create it (within the granted abilities). Store tokens in environment variables or a secrets manager — never in source code, configuration files committed to version control, or client-side code.
- Do not log tokens: Ensure your application does not write bearer tokens to log files, error reports, or monitoring dashboards.
- Rotate tokens periodically: Create a new token, update your integration to use the new token, and then revoke the old one. The frequency depends on your security requirements — quarterly rotation is a reasonable starting point for most integrations.
Response Handling
- Always check HTTP status codes before parsing the response body. A
200 OKconfirms the request succeeded; any other status indicates an error condition described in the Error Reference section above. - Handle pagination: List endpoints return paginated results. Check the
meta.last_pagevalue and iterate through pages as needed to retrieve the full dataset. - Cache where appropriate: If your integration reads the same data repeatedly (e.g. an invoice list that updates daily), cache the results locally and refresh on a schedule rather than querying the API on every request.
Getting Help
If you encounter an issue that is not covered by the error reference above, or if you believe the API is not behaving as documented, contact the INNOVATECH GROUP support team by submitting a ticket through the client portal. Include the HTTP method, endpoint path, request headers (with the token redacted), and the full response body in your support request.