openapi: 3.1.0
info:
version: 1.2.2-oas3.1
title: Ankorstore Stock Tracking and Logistics API
summary: API specification for the Ankorstore Stock Tracking and Logistics system
description: Ankorstore Stock Tracking and Logistics (ASTRAL) API specification
contact:
name: Ankorstore
url: https://www.ankorstore.com
email: api@ankorstore.com
license:
url: https://creativecommons.org/publicdomain/zero/1.0/
name: CC0 1.0 Universal
servers:
- url: http://www.ankorlocal.com:8000
description: Local Development Server
- url: https://www.preprod.ankorstore.com
description: Staging Environment
- url: https://www.ankorstore.com
description: Prod Environment
paths:
/api/astral/v1/stock/state:
get:
tags:
- State
summary: Stock quantities by location and status
description: |
Return the current quantity for a given product variant across locations, statuses and potentially lots.
One stock state entity is returned per product variant, so the identifier of each entity will correspond with
the product variant identifier for which quantities are returned.
operationId: list-state
parameters:
- $ref: '#/components/parameters/acceptInHeader'
- $ref: '#/components/parameters/xAksBrandUuidHeader'
- $ref: '#/components/parameters/xAksRetailerUuidHeader'
- $ref: '#/components/parameters/productVariantIdFilter'
- $ref: '#/components/parameters/statusFilter'
- $ref: '#/components/parameters/locationTypeFilter'
- $ref: '#/components/parameters/locationIdFilter'
- $ref: '#/components/parameters/productTypeFilter'
- $ref: '#/components/parameters/pageLimit'
- $ref: '#/components/parameters/pageBefore'
- $ref: '#/components/parameters/pageAfter'
- $ref: '#/components/parameters/stateAt'
responses:
default: {$ref: "#/components/responses/ErrorResponse"}
"200": {$ref: "#/components/responses/StockStateCollectionResponse"}
patch:
tags:
- State
summary: Update stock state
description: |
Override the stock state for one or more product variants.
* Only brand locations and available or onHand statuses are available for update.
* You may only specify either onHand or available for the same product variant and location, not both.
* Please include only absolute quantities.
operationId: patch-state
parameters:
- $ref: '#/components/parameters/acceptInHeader'
- $ref: '#/components/parameters/xAksBrandUuidHeader'
- $ref: '#/components/parameters/xAksRetailerUuidHeader'
requestBody: {$ref: '#/components/requestBodies/StockStateRequest'}
responses:
default: {$ref: "#/components/responses/ErrorResponse"}
"204": {$ref: "#/components/responses/NoContent"}
/api/astral/v1/stock/postings:
get:
tags:
- Movements
summary: List postings
description: |
Returns movement history for a set of product variants and locations, based on the provided filters.
To receive information on which specific lots were involved in the movement, include the `lot` resource. You
may also include the transaction resource to get more information on what caused the posting.
operationId: list-postings
parameters:
- $ref: '#/components/parameters/acceptInHeader'
- $ref: '#/components/parameters/xAksBrandUuidHeader'
- $ref: '#/components/parameters/xAksRetailerUuidHeader'
- $ref: '#/components/parameters/productVariantIdFilter'
- $ref: '#/components/parameters/locationTypeFilter'
- $ref: '#/components/parameters/locationIdFilter'
- $ref: '#/components/parameters/locationStatusFilter'
- $ref: '#/components/parameters/pageLimit'
- $ref: '#/components/parameters/pageBeforeUuidTimestamp'
- $ref: '#/components/parameters/pageAfterUuidTimestamp'
- name: sort
in: query
description: Specify what to sort by
required: false
allowEmptyValue: false
schema:
type: string
enum:
- transaction.createdAt
- '-transaction.createdAt'
- name: filter[movementType][in][]
in: query
description: Include only specific movement types
required: false
schema:
type: array
items: {$ref: '#/components/schemas/MovementType'}
- name: filter[movementType][notIn][]
in: query
description: Exclude specific movement types
required: false
schema:
type: array
items: {$ref: '#/components/schemas/MovementType'}
- name: include
in: query
description: >-
A comma-separated list of resources to include. Options are transaction, lot, state. The states that will be included represent the quantities at the start of the set of postings. Eg. when sorted ascending, the state will represent the quantities at cursor `meta.page.from`. When sorting descending, `meta.page.to`
schema:
type: string
responses:
default: {$ref: "#/components/responses/ErrorResponse"}
"200": {$ref: "#/components/responses/PostingCollectionResponse"}
/api/astral/v1/stock/transactions:
get:
tags:
- Movements
summary: List transactions
description: |
Returns transaction history for a set of product variants and locations, based on the provided filters.
Filters include relatedId, which is typically filled with the masterOrderUuid, fulfillmentOrderUuid or in case
of reservations, a user-specified identifier. You may also include the posting resource to get the individual
postings contained within the transaction(s).
operationId: list-transactions
parameters:
- $ref: '#/components/parameters/acceptInHeader'
- $ref: '#/components/parameters/xAksBrandUuidHeader'
- $ref: '#/components/parameters/xAksRetailerUuidHeader'
- $ref: '#/components/parameters/productVariantIdFilter'
- $ref: '#/components/parameters/locationTypeFilter'
- $ref: '#/components/parameters/locationIdFilter'
- $ref: '#/components/parameters/locationStatusFilter'
- $ref: '#/components/parameters/pageLimit'
- $ref: '#/components/parameters/pageBeforeUuidTimestamp'
- $ref: '#/components/parameters/pageAfterUuidTimestamp'
- name: filter[relatedIds][]
in: query
description: A set of related identifiers to fetch transactions for
required: false
schema:
type: array
minItems: 1
items:
type: string
- name: sort
in: query
description: Specify what to sort by
required: false
allowEmptyValue: false
schema:
type: string
enum:
- createdAt
- '-createdAt'
- name: include
in: query
description: 'A comma-separated list of resources to include. Options are: posting'
schema:
type: string
responses:
default: {$ref: "#/components/responses/ErrorResponse"}
"200": {$ref: "#/components/responses/TransactionCollectionResponse"}
/api/astral/v1/stock/locations:
get:
tags:
- Locations
summary: List locations
description: Return the list of available locations
operationId: list-locations
parameters:
- $ref: '#/components/parameters/acceptInHeader'
- $ref: '#/components/parameters/xAksBrandUuidHeader'
- $ref: '#/components/parameters/xAksRetailerUuidHeader'
- $ref: '#/components/parameters/locationTypeFilter'
- $ref: '#/components/parameters/externalIdFilter'
- $ref: '#/components/parameters/pageLimit'
- $ref: '#/components/parameters/pageBefore'
- $ref: '#/components/parameters/pageAfter'
responses:
default: {$ref: "#/components/responses/ErrorResponse"}
"200": {$ref: "#/components/responses/LocationCollectionResponse"}
tags:
- name: How to work with the Ankorstore Stock Tracking and Logistics API
description: "ℹ️ This section provides an overview of the general concepts and conventions used throughout the API.\n\n### API Specification\nThis API follows the [Ankorstore API Specification](https://api-spec.ankorstore.com) which is based on the [JSON:API](https://jsonapi.org/) specification.\n\n### Headers\nWhen making any request, the header `Accept: application/vnd.api+json` is required.\nWhen sending JSON within the body of a request the header `Content-Type: application/vnd.api+json` is also required.\n\n\U0001F517 [Learn more about JSON:API media types](https://jsonapi.org/format/#jsonapi-media-type)\n\n### Resource Data\nIn every response, the actual resource data can be found in the root level object `data`, this will either be an array\nor an object, depending on if its list based or not.\n\n\U0001F517 [Learn more about JSON:API document structure](https://jsonapi.org/format/#document-structure)\n\n### Includes\nResources may have the ability to include data from other relations in the same request.\nFor example, when fetching a single posting you may include the related transaction and lots\n\n`?include=transaction,lots`\n\n<div class=\"info\">\n\n### Pagination\nAnkorstore uses cursor based pagination. In the root level object for pagination supported endpoints\nyou will find a `meta` object containing pagination information:\n\n```yaml json_schema\n type: object\n description: Meta\n properties:\n meta:\n type: object\n description: Meta with Pagination Details\n properties:\n page:\n description: Cursor pagination details\n type: object\n properties:\n from:\n type: string\n to:\n type: string\n hasMore:\n type: boolean\n perPage:\n type: integer\n```\n\nAs an example:\n\n```json\n{\n \"meta\": {\n \"page\": {\n \"from\": \"63e9bacd-0288-4cf1-81ab-b34270c7f68a\",\n \"to\": \"747a1dcd-decc-4f8d-a9a9-61ff4d33d92e\",\n \"hasMore\": true,\n \"perPage\": 15\n }\n }\n}\n```\n\nYou may manually construct the pagination query parameters yourself using `page[limit]` with `page[before]`\nor `page[after]` depending on which direction you wish to iterate over.\n\n<div class=\"info\">\n\n#### Effective pagination\n\nWe recommend using the provided helper pagination links below instead of manually constructing the URLs yourself.\n\n</div>\n\nThe response will also provide pagination links, so you do not need to construct these yourself:\n\n```yaml json_schema\n type: object\n description: Links\n properties:\n links:\n description: Pagination navigation links. If a link does not exist then you cannot paginate any further in that direction.\n type: object\n properties:\n first:\n type: string\n format: url\n next:\n type: string\n format: url\n prev:\n type: string\n format: url\n```\n\nAs an example:\n\n```json\n{\n \"links\": {\n \"first\": \"https://www.ankorstore.com/api/astral/v1/postings?page%5Blimit%5D=15\",\n \"next\": \"https://www.ankorstore.com/api/astral/v1/postings?page%5Bafter%5D=1189606a-139e-4b4e-917c-b0c992498bad&page%5Blimit%5D=15\",\n \"prev\": \"https://www.ankorstore.com/api/astral/v1/postings?page%5Bbefore%5D=e04e17bb-9e70-4ecd-a8f5-4417d45b872c&page%5Blimit%5D=15\"\n }\n}\n```\n\n\U0001F517 [Learn more about JSON:API pagination](https://jsonapi.org/format/#fetching-pagination)\n\n### Filters\n\nAn endpoint that supports filters requires each listed filter to be wrapped in a filter object.\nE.g. you may wish to only retrieve stock quantities for a given status:\n\n`?filter[status][]=available`\n\nYou may chain filters as well:\n\n`?filter[status][]=available&filter[locationIds][]=1189606a-572e-4b4e-88da-b0c992498bad`\n\n<div class=\"info\">\n\n#### What filters can be used?\n\nThe supported filters will be listed on each endpoint in the API documentation.\n\n</div>\n\n\U0001F517 [Learn more about JSON:API filters](https://jsonapi.org/format/#fetching-filtering)"
- name: Authentication
description: |+
ℹ️ The Ankorstore API uses OAuth2 authentication, in order to get a `client_id` and a `client_secret` please,
login at https://www.ankorstore.com/ and generate a new application
in the [integrations](https://ankorstore.com/account/integrations) area of your account.
To generate a token pass in your `client_id` and `client_secret`:
```json http
{
"method": "post",
"headers": {
"accept": "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
"baseUrl": "https://www.ankorstore.com",
"url": "/oauth/token",
"body": "grant_type=client_credentials&client_id={{client_id}}&client_secret={{client_secret}}&scope=*"
}
```
You will receive an `access_token`:
```json
{
"token_type": "Bearer",
"expires_in": 3600,
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9<...>"
}
```
This access token has an expiry, in which you will receive an `401 Unauthenticated` response. To generate a new one
you can re-call the `/oauth/token` endpoint described above.
To authenticate with every other endpoint pass in the header `Authorization: Bearer {token}`.
You can test if your token is working with this endpoint (this is a not a JSON:API based endpoint):
```json http
{
"method": "get",
"headers": {
"accept": "application/json",
"Authorization": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9<...>"
},
"baseUrl": "https://www.ankorstore.com",
"url": "/api/v1/me"
}
```
If successful you will receive a 200 response:
```json
{
"data": {
"type": "user",
"id": 1234,
"email": "api@ankorstore.com",
"first_name": "Test",
"last_name": "Account",
"created_at": "2020-01-28T11:26:35+00:00",
"updated_at": "2022-03-15T15:23:09+00:00"
}
}
```
- name: Stock Concepts
description: |-
ℹ️ This section defines the various concepts and entities used in the API.
### Product variants
Product variants are entities that can be sold via the Ankorstore Marketplace and/or fulfilled via Ankorstore logistics.
They are the primary entity that other systems can use to interact with the stock API
### Batches & Units
Batches represent a physically pickable entity, used as the indivisible quantity for all matters stock tracking.
Unless specified otherwise, any quantity in the ASTRAL API will be the number of batches.
Units describe how many individual units are contained within a batch. It may be used for informational purposes
by the marketplace, but all stock movement tracking is primarily done with batches.
### Bundles
A product variant may be composed of several other product variants. These are called bundles. Within a bundle, each
product variant contained will have a multiplier to indicate how many batches are contained within the bundle.
When stock for a bundle is reserved, the stock of each individual product variant contained within the bundle is
decremented accordingly.
### Lots
A lots refers to a specific quantity of items that are produced, stored, or shipped together and identified with
a lot number. An item may have many lots, and each lot is uniquely identified by a lot number + an optional expiry date.
When reserving stock, you may specify a lot number and expiry date to obtain stock from that specific lot.
If you do not specify a lot number, and the item has lots attached, the system will automatically select the lot
with the earliest expiry date.
## Movements
Ankorstore maintains a double-entry ledger for stock tracking, meaning that for each stock change on a target there
must be an equal and opposite entry on the source.
Movements may be between physical locations, such as a warehouse and retailer, or between virtual stock statuses,
such as available and reserved.
### Postings
Each single stock quantity change on a specific location and status is represented as a Posting, with a movementType
to describe the reason of the change. Postings are considered immutable, forming an accounting-equivalent record of
the full stock history.
### Transactions
Transactions represent a single trigger or event that initiated a set of stock changes, and group together a set of
postings with a timestamp, external identifiers and potentially metadata. Examples include a order reservation,
order shipment, or location snapshot
## Stock state
A stock state represents the current "balance", the quantity for a given product variant across locations, statuses
and potentially lots. It may show for example, how many batches are currently `available` at a given `warehouse`
for each lot of a `product variant` X.
Stock state is always grouped per product variant, so the identifier for stock-state entities corresponds with the
product variant ID for which quantities are shown.
### Location & Stock statuses
Ankorstore maintains 2 concepts of status; location status represents a physical or virtual ledger of stock, while
stock status represents a status for which a balance can be maintained.
A stock status may be a super-set of location status, such as onHand, which is the super-set of available, reserved
blocked, damaged and qualityControl. Conversely, some location statuses may be tracked without being exposed
as stock status
| State | Description | Location status | Stock status | |
|----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------|--------------|---|
| available | Inventory that is considered available for orders or fulfillment | yes | yes | |
| onHand | Inventory that is present at a location. This is a sum of available, reserved, damaged, qualityControl and blocked | no | yes | |
| reserved | Inventory that are set aside, and currently not available for orders or fulfillment | yes | yes | |
| damaged | Inventory that is damaged, and thus no longer available for orders or fulfillment | yes | yes | |
| qualityControl | Inventory that is set aside for inspection, and thus no longer available for orders or fulfillment | yes | yes | |
| blocked | Inventory that is blocked for any reason other than damage or inspection, and thus no longer available for orders or fulfillment. For example: expired | yes | yes | |
| incoming | Inventory that is expected but not yet physically present at the location | yes | yes | |
| void | Wildcard location to source (temporarily) unexplained movements to and from | yes | no | |
## Reservations
Reservations are a specific type of transaction that represent a temporary allocation of stock. A reservation may only
be created when the stock balances in status `available` is higher than the requested quantities, and will move the
requested quantities of stock from the `available` status to the `reserved` status within the same location.
### Postings
Postings are always linked to items, and each item must be linked to at least one "single" product variant. So the
Postings endpoint will return movements for "single" product variants only. If a bundle is specified, movements
should be returned for all single product variants contained within the bundle.
### Stock state
Stock states can represent either a single product variant or a bundle. If a bundle is specified, the quantities
in the stock state should be computed as the largest possible common denominator of the contained single product
variants.
Example: consider a bundle BX containing `10 x PY` + `5 x PZ`, with the stock state for A being `30` and B being `10`.
Then the stock state for BX will be `2`, as PZ `10 / 5 = 2` defines the available number of complete bundles.
- name: State
- name: Reservations
- name: Locations
- name: Movements
components:
requestBodies:
StockReservationRequest:
content:
application/vnd.api+json:
schema:
type: object
properties:
data: {$ref: '#/components/schemas/StockReservation'}
required:
- data
examples:
example-create-order-reservation:
value:
data:
type: reservation
id: 1ed86ab7-c16e-6002-b4d0-0242ac15000c
attributes:
items:
- productVariantId: 0f358c35-9698-46f4-8856-d0441e0533e5
quantity: 10
locationId: 0fba0067-87cb-4e36-9885-2529eb0475cc
- productVariantId: 0f358c35-9698-46f4-8856-d0441e0533e5
quantity: 5
locationId: 0fba0067-87cb-4e36-9885-2529eb0475cc
lotNumber: lot-abc
relatedId: 7326952d-383a-496b-ad8c-63a2517c0dbd
metadata:
masterOrderId: 7326952d-383a-496b-ad8c-63a2517c0dbd
LocationRequest:
content:
application/vnd.api+json:
schema:
type: object
properties:
data: {$ref: '#/components/schemas/LocationResource'}
required:
- data
examples:
example-create-location:
value:
data:
type: location
id: 38cbe75a-9e3c-4adc-a465-351625a88c3f
attributes:
externalId: 1841c9e9-33cc-4fd5-8045-32654adcc78a
type: warehouse
StockStateRequest:
content:
application/vnd.api+json:
schema:
type: object
properties:
data:
type: array
items: {$ref: '#/components/schemas/ProductVariantStockStateResource'}
required:
- data
examples:
example-change-stock-state:
value:
data:
- type: 'stock-state'
id: 1ed86ab7-c16e-6002-b4d0-0242ac15000c
attributes:
quantities:
- quantity: 10
locationId: '3fa85f64-5717-4562-b3fc-2c963f66afa6'
status: 'available'
- type: 'stock-state'
id: 1841c9e9-33cc-4fd5-8045-32654adcc78a
attributes:
quantities:
- quantity: 80
locationId: '3fa85f64-5717-4562-b3fc-2c963f66afa6'
status: 'onHand'
securitySchemes:
CookieKey:
type: apiKey
name: ankorstore_session
in: cookie
responses:
NoContent: # 201
description: No content
ErrorResponse:
description: Example response
content:
application/vnd.api+json:
schema:
type: object
properties:
errors:
type: array
items: {$ref: '#/components/schemas/Error'}
jsonapi: {$ref: '#/components/schemas/jsonapi'}
required:
- errors
StockStateCollectionResponse:
description: Set of stock state records
content:
application/vnd.api+json:
schema:
type: object
properties:
data:
type: array
items: {$ref: '#/components/schemas/ProductVariantStockStateResource'}
jsonapi: {$ref: '#/components/schemas/jsonapi'}
meta: {$ref: '#/components/schemas/paginationMeta'}
links: {$ref: '#/components/schemas/paginationLinks'}
required:
- data
- jsonapi
examples:
example-get-stock-quantities:
value:
data:
- type: 'stock-state'
id: 1ed86ab7-c16e-6002-b4d0-0242ac15000c
attributes:
quantities:
- quantity: 10
locationId: 3fa85f64-5717-4562-b3fc-2c963f66afa6
locationType: warehouse
status: 'available'
lot:
lotNumber: 'lot-20240915-a'
expiryDate: "2024-09-15"
- quantity: 7
locationId: 0f358c35-9698-46f4-8856-d0441e0533e5
locationType: warehouse
status: 'reserved'
lot:
lotNumber: 'lot-20240915-a'
expiryDate: "2024-09-15"
- quantity: 3
locationId: 0f358c35-9698-46f4-8856-d0441e0533e5
locationType: warehouse
status: 'reserved'
- quantity: 2
locationId: 0f358c35-9698-46f4-8856-d0441e0533e5
locationType: warehouse
status: 'blocked'
lot:
lotNumber: 'lot-20231215-x'
expiryDate: "2023-12-15"
- type: 'stock-state'
id: 018fc37c-d9bc-7a25-96e0-9c80c843b6ea
attributes:
quantities:
- quantity: 5
locationId: 018fc37e-4460-7b27-8151-ee7422b71f2e
locationType: warehouse
status: 'incoming'
lot:
lotNumber: 'lot-without-expiry'
- quantity: 7
locationId: 0f358c35-9698-46f4-8856-d0441e0533e5
locationType: warehouse
status: 'available'
lot:
lotNumber: 'lot-without-expiry'
jsonapi:
version: string
meta: {}
LocationCollectionResponse:
description: Set of locations
content:
application/vnd.api+json:
schema:
type: object
properties:
data:
type: array
items: {$ref: '#/components/schemas/LocationResource'}
jsonapi: {$ref: '#/components/schemas/jsonapi'}
meta: {$ref: '#/components/schemas/paginationMeta'}
links: {$ref: '#/components/schemas/paginationLinks'}
required:
- data
- jsonapi
LocationResponse:
description: A single location
content:
application/vnd.api+json:
schema:
type: object
properties:
data: {$ref: '#/components/schemas/LocationResource'}
jsonapi: {$ref: '#/components/schemas/jsonapi'}
required:
- data
- jsonapi
PostingCollectionResponse:
description: Set of postings
content:
application/vnd.api+json:
schema: {$ref: '#/components/schemas/PostingCollection'}
examples:
example-get-postings-simple:
value:
data:
- type: 'posting'
id: 018fc37f-5c0a-7913-a672-9569d7b48756
attributes:
locationId: 20e62581-67a5-4166-8b1f-344e9b06b58b
productVariantId: 018fc380-1c43-75f9-a101-49447fbadeb7
locationStatus: available
movementType: 'order_reserved'
quantity: -10
unitMultiplier: 1
- type: 'posting'
id: 018fc385-5d99-7305-9cf5-a18b40005fbc
attributes:
locationId: 20e62581-67a5-4166-8b1f-344e9b06b58b
productVariantId: 018fc380-1c43-75f9-a101-49447fbadeb7
locationStatus: reserved
movementType: 'order_reserved'
quantity: 10
unitMultiplier: 1
example-get-postings-with-relations:
value:
data:
- type: 'posting'
id: 018fc386-3078-75b3-8e75-6bed4e01c144
attributes:
locationId: 20e62581-67a5-4166-8b1f-344e9b06b58b
productVariantId: 018fc380-1c43-75f9-a101-49447fbadeb7
locationStatus: incoming
movementType: 'replenishment'
quantity: -227
unitMultiplier: 2
relationships:
transaction:
data:
type: 'transaction'
id: 018fc399-3240-78b2-abfa-e8c5df1c304f
state:
data:
type: 'stock-state'
id: 018fc380-1c43-75f9-a101-49447fbadeb7
- type: 'posting'
id: 018fc386-4c76-76cf-9319-533c7dd9b66c
attributes:
locationId: 20e62581-67a5-4166-8b1f-344e9b06b58b
productVariantId: 018fc380-1c43-75f9-a101-49447fbadeb7
locationStatus: available
movementType: 'replenishment'
quantity: 200
unitMultiplier: 2
lotNumber: lot-Y
expiryDate: '2025-02-02'
relationships:
transaction:
data:
type: 'transaction'
id: 018fc399-3240-78b2-abfa-e8c5df1c304f
state:
data:
type: 'stock-state'
id: 018fc380-1c43-75f9-a101-49447fbadeb7
- type: 'posting'
id: 018fc3d3-cd9b-7c96-9b29-fd90bf0eff7b
attributes:
locationId: 20e62581-67a5-4166-8b1f-344e9b06b58b
productVariantId: 018fc380-1c43-75f9-a101-49447fbadeb7
locationStatus: available
movementType: 'replenishment'
quantity: 24
unitMultiplier: 2
lotNumber: lot-X
expiryDate: '2025-01-01'
relationships:
transaction:
# --- truncated at 32 KB (55 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/ankorstore/refs/heads/main/openapi/ankorstore-astral-openapi-original.yml