> ## Documentation Index
> Fetch the complete documentation index at: https://docs.genuka.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Getting Started with the Genuka API

## 1. Overview

The Genuka API is a multi-tenant REST API for building commerce, inventory, messaging, and accounting integrations on top of the Genuka platform. All responses are JSON.

**Production base URL**

```
https://api.genuka.com
```

There is one environment: production at `api.genuka.com`. There is no separate sandbox URL.

***

## 2. Versioning

The API uses date-based path versioning. Every resource endpoint is nested under the version prefix:

```
https://api.genuka.com/2023-11/<resource>
```

For example:

```
GET https://api.genuka.com/2023-11/admin/products
GET https://api.genuka.com/2023-11/admin/orders
```

The version `2023-11` is the current stable version. Breaking changes will be introduced under a new date prefix while the previous version remains accessible during a deprecation period.

***

## 3. Authentication

The API uses Laravel Passport personal access tokens (Bearer JWT). You must include a valid token on every authenticated request.

### Obtaining a token

Send a `POST` request to the login endpoint:

```http theme={null}
POST https://api.genuka.com/2023-11/auth/login
Content-Type: application/json
Accept: application/json

{
  "email": "user@example.com",
  "password": "your_password"
}
```

A successful response returns a `plainTextToken` (the token you store and reuse) along with the user's `companyId`:

```json theme={null}
{
  "plainTextToken": "1|abc123...",
  "companyId": "01hqydxwtxdj3kmzp3bz7jk73g",
  "company_code": "MYSTORE",
  "name": "Alice",
  "accessToken": { ... }
}
```

Store `plainTextToken` securely. Include it in every subsequent request as a Bearer token.

For full authentication details — registration, PIN login, password reset, multi-company users — see [02-authentication.md](./02-authentication.md).

***

## 4. Required Headers

Every authenticated request to the admin API must include all four headers below.

| Header | Required | Value | Notes |
| - | - | - | - |
| `Authorization` | Yes | `Bearer <plainTextToken>` | Token obtained from `/2023-11/auth/login` |
| `X-Company` | Yes | `<companyId>` | ULID of the tenant company. Omitting it returns HTTP 422. |
| `Accept` | Yes | `application/json` | Ensures JSON error responses instead of HTML. |
| `Content-Type` | Yes (write ops) | `application/json` | Required for `POST`, `PUT`, `PATCH` requests with a body. |

**Example authenticated request**

```http theme={null}
GET https://api.genuka.com/2023-11/admin/products
Authorization: Bearer 1|abc123...
X-Company: 01hqydxwtxdj3kmzp3bz7jk73g
Accept: application/json
```

**Optional shop header**

Many endpoints are additionally scoped to a shop (point of sale / storefront). Pass the shop ULID to restrict results to that shop:

| Header | Required | Value |
| - | - | - |
| `X-Shop` | No | ULID of the active shop |

***

## 5. Multi-Tenancy

The Genuka API is multi-tenant. Each company is an isolated tenant. All data — products, orders, customers, payments, etc. — is scoped to the company identified by `X-Company`.

**How it works**

* The `X-Company` header value is read by the `CheckCurrentCompany` middleware and injected as `companyId` into the request context.
* Eloquent global scopes (via `CompanyScope`) automatically append `WHERE company_id = ?` to every query for scoped models.
* The `owner` middleware (applied to the `/admin` route group) additionally verifies that the authenticated user belongs to that company. If the user's company does not match `X-Company`, the request is rejected with HTTP 401.

**Missing or wrong company**

| Situation | HTTP status | Response |
| - | - | - |
| `X-Company` header absent | 422 | `{"message":"Please provide a Company Id","error":"company_id_required","code":400}` |
| User does not own the company | 401 | `{"message":"You don't have the permission to manage this company..."}` |

***

## 6. Pagination

All paginated list endpoints return the same JSON envelope. The API does **not** use the default Laravel `data/links/meta` wrapper. It uses a custom `pagination` object:

```json theme={null}
{
  "data": [ { ... }, { ... } ],
  "pagination": {
    "total": 284,
    "count": 15,
    "per_page": 15,
    "current_page": 2,
    "total_pages": 19
  }
}
```

### Pagination query parameters

| Parameter | Type | Default | Maximum | Description |
| - | - | - | - | - |
| `page` | integer | `1` | — | Page number to retrieve |
| `per_page` | integer | `15` | `100` | Number of items per page |

**Example**

```http theme={null}
GET /2023-11/admin/products?page=3&per_page=25
```

Some endpoints also support `simple_paginate=1` which returns a cursor-based response without a total count (faster on very large tables).

***

## 7. Filtering, Sorting, and Search

The API uses [Spatie Laravel Query Builder](https://spatie.be/docs/laravel-query-builder) conventions. Parameters are namespaced with brackets.

### Filtering

```
filter[<field>]=<value>
```

Examples:

```
GET /2023-11/admin/products?filter[published]=true
GET /2023-11/admin/products?filter[type]=physical
GET /2023-11/admin/products?filter[search]=blue+shirt
GET /2023-11/admin/customers?filter[search]=alice
```

Multiple filters can be combined. Refer to each resource's documentation for its allowed filter names.

### Sorting

```
sort=<field>           # ascending
sort=-<field>          # descending (prefix with -)
```

Examples:

```
GET /2023-11/admin/products?sort=title
GET /2023-11/admin/products?sort=-created_at
GET /2023-11/admin/orders?sort=-created_at
```

### Including relationships

```
include=<relation>,<relation>
```

Example:

```
GET /2023-11/admin/products?include=variants,tags,collections
```

Only whitelisted includes are accepted. Requesting an unknown include returns a 400 error.

### Field selection

```
fields=id,title,handle,min_price
```

Restricts the response to the listed fields, reducing payload size.

### Date range filtering

Many index endpoints accept `startDate` and `endDate` parameters (ISO 8601 format):

```
GET /2023-11/admin/products?startDate=2024-01-01&endDate=2024-12-31
```

***

## 8. Error Handling

The API returns a consistent JSON error envelope for all error responses.

### Error envelope shape

```json theme={null}
{
  "message": "Human-readable description of the error",
  "error": "machine_readable_error_code",
  "code": 422
}
```

For validation errors (HTTP 422), Laravel includes an additional `errors` object with per-field messages:

```json theme={null}
{
  "message": "The given data was invalid.",
  "errors": {
    "email": ["The email field is required."],
    "password": ["The password must be at least 8 characters."]
  }
}
```

### HTTP status codes

| Status | Meaning | Example `error` value |
| - | - | - |
| `200 OK` | Success | — |
| `201 Created` | Resource created | — |
| `400 Bad Request` | Malformed request or missing company ID | `company_id_required` |
| `401 Unauthorized` | Invalid token or wrong company | `unauthenticated` / custom message |
| `403 Forbidden` | Valid token but insufficient scope/permission | `invalid_scope` |
| `404 Not Found` | Resource does not exist | `model_not_found` |
| `422 Unprocessable Entity` | Validation failed | — (see `errors` object) |
| `429 Too Many Requests` | Rate limit exceeded | `too_many_requests` |
| `500 Internal Server Error` | Unexpected server error | `internal_server_error` |

**401 example**

```json theme={null}
{
  "message": "You don't have the permission to manage this company. Company ID: 01hqydxwtxdj3kmzp3bz7jk73g",
  "error": "unauthenticated",
  "code": 401
}
```

**403 example** (invalid OAuth scope)

```json theme={null}
{
  "message": "You don't have access to this.",
  "error": "invalid_scope",
  "code": 403
}
```

**422 example** (missing company header)

```json theme={null}
{
  "message": "Please provide a Company Id",
  "error": "company_id_required",
  "code": 400
}
```

**500 example**

```json theme={null}
{
  "message": "An error occurred: ...",
  "error": "internal_server_error",
  "code": 500
}
```

***

## 9. Data Formats

### Resource IDs — ULIDs

All resource identifiers are [ULIDs](https://github.com/ulid/spec) — 26-character alphanumeric strings that are lexicographically sortable by creation time.

```
01hqydxwtxdj3kmzp3bz7jk73g
```

ULIDs are case-insensitive but are stored and returned in lowercase.

### Timestamps

All timestamps are returned in **ISO 8601** format, UTC:

```
"created_at": "2024-03-15T10:23:45.000000Z"
"updated_at": "2024-03-15T11:00:00.000000Z"
```

### Money / Prices

Monetary amounts (prices, order totals, payment amounts) are stored and returned as **decimal numbers** (floating point, two decimal places). There is no integer-cent encoding.

```json theme={null}
{
  "price": 12500.00,
  "compare_at_price": 15000.00,
  "amount": 37500.50
}
```

Currency is not embedded on individual resources. The company's configured currency is available on the company object:

```json theme={null}
{
  "currency_code": "XAF",
  "currency_name": "CFA Franc BEAC"
}
```

***

## 10. Rate Limiting

The `api` middleware group applies throttling to all `/2023-11` routes via Laravel's rate limiter.

| Limiter | Limit | Key |
| - | - | - |
| `api` (default) | **6 000 requests per minute** | Authenticated user ID, or IP address for unauthenticated requests |
| `sync` | 60 requests/minute + 500 requests/hour | IP address |
| `chat` (public webchat) | 30 requests/minute | Bearer token, or IP address |

When the limit is exceeded, the server responds with **HTTP 429**. Laravel includes standard rate-limit headers in responses:

```
X-RateLimit-Limit: 6000
X-RateLimit-Remaining: 5874
Retry-After: 12
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.