Developer guides
API overview
The current v1 endpoint map, catalog and account schemas, request examples, and error conventions.
API overview
Explore every operation
Choose an operation to see its request, response shape, access rules and implementation source.
Platform
Catalog
Account
/auth/tokensCreate a named API tokenDELETE/auth/tokensRevoke the current bearer tokenGET/meRead the current accountPATCH/meUpdate the account nameGET/addressesList saved customer addressesPOST/addressesSave a customer addressPATCH/addresses/{address}Replace the saved address fieldsDELETE/addresses/{address}Delete an owned addressCart and checkout
Orders and payments
Financial records
Support
On this page
Base URL and contract
The customer/storefront API lives at https://<your-shop>/api/v1. This documentation page is /development/api; it is not an API proxy. Use HTTPS and send Accept: application/json. For JSON writes also send Content-Type: application/json.
The route source is routes/api.php. The code also registers GET /api/v1/openapi.json, returning OpenAPI 3.0.3 from OpenApiDocument. That document describes operations, security, and selected request fields. It does not contain complete resource schemas and it is not proof that a Module is enabled on an installation. v1 is explicitly an early interface, not a frozen public contract.
curl 'https://shop.example.test/api/v1/openapi.json' \
--header 'Accept: application/json'
All example domains, names, IDs, tokens, and product slugs below are illustrative. Substitute values returned by your installation. Examples show response shapes, not captured production responses.
Endpoint collection
Paths in these tables are relative to /api/v1. Each method is grouped so you can scan the page like an API client collection. “Public” means the route does not require auth:sanctum; installation and other global middleware still apply. Ability names apply to bearer tokens. See authentication for sessions and token creation.
GET · Read
| Method | Path | Access | Request / response |
|---|---|---|---|
GET |
/openapi.json |
Public | OpenAPI document |
GET |
/products |
Public | q, category, page; paginated products |
GET |
/products/{slug} |
Public | Product by slug |
GET |
/categories |
Public | Category collection |
GET |
/categories/{slug} |
Public | Category and paginated products |
GET |
/search |
Public | q; up to eight product suggestions |
GET |
/me |
account.read |
Account resource |
GET |
/addresses |
addresses.read |
Address collection |
GET |
/cart |
Public cart context | Cart and X-Cart-Token response header |
GET |
/checkout/requirements |
Public cart context | Requirements and available payment methods |
GET |
/orders |
orders.read |
Paginated orders |
GET |
/orders/{order} |
orders.read |
Owned order |
GET |
/invoices |
invoices.read |
Paginated invoices |
GET |
/invoices/{invoice} |
invoices.read |
Owned invoice |
GET |
/credit-notes |
credit_notes.read |
Paginated credit notes |
GET |
/credit-notes/{creditNote} |
credit_notes.read |
Owned credit note |
GET |
/support-tickets |
tickets.read |
Paginated support tickets |
GET |
/support-tickets/{ticket} |
tickets.read |
Owned ticket summary |
POST · Create or trigger
| Method | Path | Access | Request / response |
|---|---|---|---|
POST |
/auth/tokens |
Email and password | Named token; explicit abilities recommended |
POST |
/addresses |
addresses.create |
Complete address; address resource |
POST |
/cart |
Public cart context | product_id, optional quantity and selections |
POST |
/checkout |
orders.create |
Billing and checkout input; order resource |
POST |
/orders/{order}/pay |
orders.pay |
Gateway and redirect URLs; payment attempt |
PATCH · Update
| Method | Path | Access | Request / response |
|---|---|---|---|
PATCH |
/me |
account.update |
Required name; account resource |
PATCH |
/addresses/{address} |
addresses.update |
Complete address; address resource |
PATCH |
/cart/{lineKey} |
Public cart context | Quantity from 0 to 99; updated cart |
DELETE · Revoke or remove
| Method | Path | Access | Request / response |
|---|---|---|---|
DELETE |
/auth/tokens |
tokens.revoke |
Revoke current token; {"ok":true} |
DELETE |
/addresses/{address} |
addresses.delete |
{"ok":true} |
DELETE |
/cart/{lineKey} |
Public cart context | Updated cart |
The collection above reflects the current Core route source. Core has no public v1 routes to create products, change invoice status, issue refunds, create support tickets, administer packages, or expose a PUT operation. For cart, checkout, and payment request schemas use Cart and checkout API. Financial, ticket, and optional Module response fields are defined in Commerce API resources.
Catalog queries
GET /products selects active products, sorts by name, and paginates at 24 items. The q filter searches name or description only when its trimmed length is at least two characters. category accepts a category slug or ID. There is no controller option for page size or arbitrary sorting.
GET /products/{slug} selects an active product by slug, not numeric ID. Missing or inactive products return 404.
GET /search?q=... returns at most eight products, not a paginator. A query shorter than two trimmed characters returns an empty collection. The suggestion service also constrains products to available capabilities. Do not assume that every catalog action uses an identical query internally.
curl --get 'https://shop.example.test/api/v1/products' \
--header 'Accept: application/json' \
--data-urlencode 'q=lamp' \
--data-urlencode 'category=lighting' \
--data-urlencode 'page=1'
curl 'https://shop.example.test/api/v1/products/reading-lamp' \
--header 'Accept: application/json'
curl --get 'https://shop.example.test/api/v1/search' \
--header 'Accept: application/json' --data-urlencode 'q=lamp'
Product schema
A single product is wrapped in data. The list returns data as an array, plus Laravel pagination links and meta.
| Field | Shape |
|---|---|
id, name, slug |
Integer ID and strings |
subtitle, sku, description |
Product text; nullable where not configured |
price |
{amount: integer, currency: string, formatted: string} |
category |
null or {id, name, slug} |
images |
Array of public image URL strings |
capabilities |
Array of capability keys |
options |
Active options: {key, label, type, required, choices} |
options[].choices |
Active choices: {value, label} |
Amounts are integer minor units. Use the amount and currency for calculations, not the formatted display string. options is a selection contract, not a client-authoritative price quote.
Category schema
GET /categories returns data: Category[]. A Category contains id, name, slug, nullable image, integer products_count, and children. Each loaded child contains id, name, slug, and products_count; this is not an arbitrary-depth nested Category serializer.
GET /categories/{slug}?page=1 returns a different envelope:
{
"data": {
"category": {
"id": 7,
"name": "Lighting",
"slug": "lighting",
"image": null,
"products_count": 0,
"children": []
},
"products": []
},
"meta": {"current_page": 1, "last_page": 1, "total": 0}
}
The category detail action selects an active category and paginates its active products at 24 items. It returns only the shown pagination metadata, not the full Laravel resource links envelope.
Sources: CatalogController, ProductResource, CategoryResource, and SuggestStorefrontProducts.
Current account
GET /me and PATCH /me return:
{
"data": {
"id": 12,
"name": "Alex Example",
"email": "[email protected]",
"email_verified": true
}
}
PATCH accepts only the required name string, maximum 255 characters. It does not expose email changes, password changes, roles, or arbitrary customer properties.
curl --request PATCH 'https://shop.example.test/api/v1/me' \
--header "Authorization: Bearer $AGOVENA_TOKEN" \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{"name":"Alex Example"}'
Address requests and responses
POST and PATCH share the same validation. PATCH is not a partial address patch: it still requires name, line1, city, postal_code, and country.
| Field | Rule |
|---|---|
name, line1 |
Required string, maximum 255 |
city |
Required string, maximum 120 |
postal_code |
Required string, maximum 20 |
country |
Required string, exactly two characters |
label |
Nullable string, maximum 80 |
company, line2 |
Nullable string, maximum 255 |
region |
Nullable string, maximum 120 |
phone |
Nullable string, maximum 40 |
is_default_billing, is_default_shipping |
Optional boolean |
properties |
Nullable array with the supported address aliases |
The supported properties keys are phone, company_name, address, address2, city, state, zip, and country. Their lengths match the equivalent top-level fields; properties.country is also checked against CheckoutCountries::codes(). If provided, these aliases take precedence when building the saved address. Required top-level fields remain required.
curl --request POST 'https://shop.example.test/api/v1/addresses' \
--header "Authorization: Bearer $AGOVENA_TOKEN" \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{"label":"Office","name":"Alex Example","line1":"Example Street 1","city":"Brussels","postal_code":"1000","country":"BE","is_default_billing":true}'
An Address resource contains id, label, the address fields above, both default flags, and a properties object with those aliases. GET /addresses orders saved addresses by descending ID. If none exist, it may return an address reconstructed from customer properties, with id: null, default billing true, and default shipping false. Such an entry is not a stored address ID that can be patched or deleted.
Foreign address IDs return 404. Setting a saved address as a default clears that default flag on the customer's other saved addresses. POST currently returns a refreshed resource rather than explicitly setting an HTTP creation status; do not use the descriptive OpenAPI 201 entry as the sole runtime assertion.
Sources: AccountController, AddressResource, and SaveCustomerAddress.
Error envelope
Expected API errors use top-level message and code, with optional field errors:
{
"message": "<localized validation message>",
"code": "validation_error",
"errors": {"name": ["<localized field error>"]}
}
| HTTP status | Typical code | Client action |
|---|---|---|
| 401 | unauthenticated |
Supply valid authentication |
| 403 | unauthorized, insufficient_scope, ip_not_allowed |
Check ownership context, scope, and IP policy |
| 404 | not_found |
Check identifier, visibility, ownership, or enabled Module |
| 409 | invalid_state |
Reload state before trying again |
| 422 | validation_error, invalid_state, payment_failed |
Correct input or resolve the domain condition |
| 429 | rate_limited |
Back off rather than immediately replaying writes |
errors is omitted when empty. Do not parse localized messages as stable identifiers. Exception type affects the code: validation exceptions map to validation_error, while a generic HTTP 422 maps to invalid_state. Unexpected server failures are not a guaranteed business-error schema.
Sources: ApiError, exception rendering, and StorefrontApiTest.