The finpo API
finpo runs the counter, the stock and the theft checks of shops, restaurants, pharmacies and hotels. The API lets your software work with a business’s finpo records: keep an online shop’s stock in step, send orders to the kitchen, read the day’s sales into an accounting package, or know a customer’s visits in a loyalty app.
It is a REST API with JSON in and out. Every object has an object and an id; money is a whole number in the smallest unit of the business’s currency (kobo for naira, so 75000 is ₦750) with the currency beside it; times are ISO 8601. It runs on finpo cloud, beside the records the shop computer syncs.
Sales cannot be written through the API. They are signed where they happen, at the counter, which is what lets finpo check every one of them. Send an order instead: the shop takes it and it becomes a sale.
https://finpo.nikcano.com/api/developer/v1Authentication
Every request carries a key in the Authorization header, as Bearer. The business’s owner makes keys under Settings, Developers, and chooses what each may do.
- Secret keys (
fk_live_...,fk_test_...) are for your server. Keep them like a password; never put one in a browser or an app. - Publishable keys (
fkp_live_...,fkp_test_...) are made to be seen: put them in a shop’s own website or app. They can only read the catalogue and stock and place orders (business:read, products:read, stock:read, orders:write).
A key is shown once, when it is made. The owner can roll it (a new secret, the old one stops) or revoke it at any time. The scopes a key can have:
business:read: See the business and its branchesproducts:read: See products and categoriesproducts:write: Add and change productsstock:read: See stock levelscustomers:read: See customerscustomers:write: Add and change customerssales:read: See salesorders:read: See online ordersorders:write: Send online orders to the shopwebhooks:write: Manage webhook endpointscosts:read: See cost prices (the owner’s figures)
curl https://finpo.nikcano.com/api/developer/v1/business \
-H "Authorization: Bearer fk_test_..."Test mode
A test key reads the business’s real records, within its scopes, so you build against a real catalogue. Every write is checked in full and answered as it would be, with livemode: false, but nothing is saved. Test keys work whether or not the business has a plan, so you can start before the owner pays. Live keys need finpo cloud running for the business.
Errors
A failed request answers with an HTTP status and a body with type, code, a message written for a person, the param at fault when there is one, and a request_id to quote to us. The types are invalid_request_error, authentication_error, permission_error, rate_limit_error and api_error.
api_key_missing(401): No key was sent, or it is not in the right form.api_key_invalid(401): The key is not known, or it was revoked or rolled.account_inactive(403): The business is not active on finpo.plan_inactive(403): A live key needs finpo cloud running for the business. Test keys always work.scope_missing(403): The key does not have the scope this needs.secret_key_required(403): A publishable key cannot do this. Use a secret key on your server.route_missing(404): There is no such endpoint.resource_missing(404): No object has that id (or a line names a product or unit that does not exist).parameter_missing(400): A required parameter was not sent.parameter_invalid(400): A parameter has the wrong type or value, or is not a parameter of this request.body_invalid(400): The body is not a JSON object.resource_conflict(409): It clashes with something that exists, such as a customer’s name or a product code.idempotency_key_reused(409): The Idempotency-Key was used with a different request.rate_limited(429): Too many requests for this key. Wait for the Retry-After header’s seconds.internal_error(500): A fault of ours. It is recorded; try again.
{
"error": {
"type": "invalid_request_error",
"code": "parameter_missing",
"message": "\"name\" is required.",
"param": "name",
"doc_url": "/docs/api#errors-parameter_missing",
"request_id": "req_9437b3403c9a4e5fb17b"
}
}Pagination
Lists come newest first, as { object: "list", data, has_more, url }. Ask for up to 100 with limit (20 by default). For the next page send starting_after with the id of the last object you have; for the page before, ending_before with the id of the first.
{
"object": "list",
"data": [
"..."
],
"has_more": true,
"url": "/api/developer/v1/products"
}Idempotent requests
Send an Idempotency-Key header (a UUID is ideal) with any POST. If the network fails and you send it again with the same key, you get the first answer back, with Idempotent-Replayed: true, and nothing is made twice. Keys are kept 24 hours. The same key with a different request is refused.
Versions
The version is in the path, v1. Within it, changes only add: new endpoints, new parameters, new fields. Every answer carries Finpo-Version: 2026-10-12. A change that would break code gets a new dated version, announced in advance.
Rate limits
A live secret key may send 100 requests a minute (20 while the business has no plan running), a test key 60, and a publishable key 30 a minute from each visitor. Each answer carries RateLimit-Limit and RateLimit-Remaining; over the limit you get a 429 with Retry-After in seconds.
Browsers and CORS
The API answers browsers from any origin: every response allows it, and a browser’s preflight is answered at once. No cookie is ever read, so only the key decides what a request may do. In a browser, use a publishable key.
Webhooks
finpo can tell your server the moment something happens, instead of you asking every minute. Add an endpoint in Settings, Developers or with the API, choose its events, and keep the signing secret it shows once.
Each delivery is a POST of { event, at, data } with the headers X-Finpo-Event and X-Finpo-Signature: t=...,v1=..., an HMAC-SHA256 of <t>.<body> with your secret. Refuse a delivery whose signature does not match or whose time is more than five minutes away. A delivery that fails is tried three times. Events: sale.recordedstock.changeditem.upsertedcustomer.upsertedorder.created
import crypto from 'node:crypto';
// header: the X-Finpo-Signature header, as "t=1760264400,v1=5257a8..."
export function isFromFinpo(secret, rawBody, header) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const age = Math.abs(Date.now() / 1000 - Number(parts.t));
if (!parts.t || !parts.v1 || age > 300) return false;
const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}MCP for AI tools
finpo runs an MCP server, so an AI assistant can read and work with a business’s records in conversation: what sold most this week, which customers owe money, what is running low. It speaks Streamable HTTP at https://finpo.nikcano.com/api/mcp, protocol 2026-07-28 (stateless, with server/discover), and answers initialize for clients on 2025-11-25.
It uses the same keys, scopes, limits and test mode as the API. Each operation below is a tool of the same name (its MCP tool is named under each one), and a key only sees the tools its scopes allow. It also offers the resources finpo://business and finpo://docs/api, and the prompts daily_summary, restock_list and customer_lookup. Use a test key until you trust the assistant with real writes.
claude mcp add --transport http finpo https://finpo.nikcano.com/api/mcp \
--header "Authorization: Bearer fk_test_..."Business
Retrieve the business
GET /api/developer/v1/business
The business the key belongs to: its name, its account number, the currency its money is in, and what kind of business it is.
Scope: business:read. Secret or publishable keys. MCP tool: retrieve_business.
No parameters.
curl https://finpo.nikcano.com/api/developer/v1/business \
-H "Authorization: Bearer fk_test_..."{
"object": "business",
"id": "cmv2a1b2c0001bus",
"name": "Adaku Mart",
"account_number": "85063792",
"currency": "NGN",
"kind": "provisions",
"livemode": true
}Locations
List locations
GET /api/developer/v1/locations
The business’s branches: every place it sells from or keeps stock in. Most shops have one.
Scope: business:read. Secret or publishable keys. MCP tool: list_locations.
No parameters.
curl https://finpo.nikcano.com/api/developer/v1/locations \
-H "Authorization: Bearer fk_test_..."{
"object": "list",
"data": [
{
"object": "location",
"id": "cmv2a1b2c0001loc",
"name": "Victoria Island",
"code": "VIC",
"address": "12 Adeola Odeku Street",
"is_default": true,
"active": true
}
],
"has_more": false,
"url": "/api/developer/v1/locations"
}Categories
List categories
GET /api/developer/v1/categories
The categories products are grouped in, as the shop arranged them.
Scope: products:read. Secret or publishable keys. MCP tool: list_categories.
No parameters.
curl https://finpo.nikcano.com/api/developer/v1/categories \
-H "Authorization: Bearer fk_test_..."{
"object": "list",
"data": [
{
"object": "category",
"id": "cmv2a1b2c0001cat",
"name": "Milk and beverages",
"parent": null
}
],
"has_more": false,
"url": "/api/developer/v1/categories"
}Products
List products
GET /api/developer/v1/products
The shop’s products, newest first. `query` finds products the way the shop’s own counter does: a barcode, a code, or a name, even typed wrong.
Scope: products:read. Secret or publishable keys. MCP tool: list_products.
Parameters
limitintegerin queryHow many to return, 1 to 100. The default is 20.
starting_afterstringin queryThe id of the last object of the page before: the next page starts after it.
ending_beforestringin queryThe id of the first object of the page after: the page before ends before it.
querystringin queryA barcode, a SKU, or words of the name. Forgiving of spelling.
categorystringin queryOnly products in this category.
activebooleanin queryOnly products on sale (true) or only those taken off sale (false).
curl https://finpo.nikcano.com/api/developer/v1/products?limit=20&query=peak%20milk \
-H "Authorization: Bearer fk_test_..."{
"object": "list",
"data": [
{
"object": "product",
"id": "cmv2a1b2c0001prd",
"name": "Peak evaporated milk 160g",
"sku": "PEAK-160",
"brand": "Peak",
"kind": "good",
"barcodes": [
"6151100055512"
],
"category": "cmv2a1b2c0001cat",
"image_url": null,
"active": true,
"base_unit": "piece",
"container_size": 1,
"container_label": "tin",
"currency": "NGN",
"units": [
{
"key": "FULL",
"label": "Tin",
"quantity": 1,
"price": 75000,
"price_min": null,
"price_max": null
}
],
"created": "2026-10-12T09:00:00.000Z",
"updated": "2026-10-12T09:00:00.000Z"
}
],
"has_more": false,
"url": "/api/developer/v1/products"
}Retrieve a product
GET /api/developer/v1/products/{id}
One product, with every unit it is sold in and its price.
Scope: products:read. Secret or publishable keys. MCP tool: retrieve_product.
Parameters
idstringrequiredin pathThe object’s id.
curl https://finpo.nikcano.com/api/developer/v1/products/product_id \
-H "Authorization: Bearer fk_test_..."{
"object": "product",
"id": "cmv2a1b2c0001prd",
"name": "Peak evaporated milk 160g",
"sku": "PEAK-160",
"brand": "Peak",
"kind": "good",
"barcodes": [
"6151100055512"
],
"category": "cmv2a1b2c0001cat",
"image_url": null,
"active": true,
"base_unit": "piece",
"container_size": 1,
"container_label": "tin",
"currency": "NGN",
"units": [
{
"key": "FULL",
"label": "Tin",
"quantity": 1,
"price": 75000,
"price_min": null,
"price_max": null
}
],
"created": "2026-10-12T09:00:00.000Z",
"updated": "2026-10-12T09:00:00.000Z"
}Create a product
POST /api/developer/v1/products
Adds a product, sold one at a time at one price. It is written exactly as a product added in finpo is, with its signed record, and checked against the plan’s limits. For sizes, cartons and fractions, add the product in finpo itself.
Scope: products:write. Secret keys only. MCP tool: create_product.
Parameters
namestringrequiredWhat the product is called at the counter.
priceintegerrequiredThe price of one, in the smallest unit of the business’s currency (kobo for naira).
unit_labelstringWhat one is called: "Tin", "Pack", "Plate". The default is "Each".
skustringThe shop’s own code for it. One is made up when none is sent.
barcodesarray of stringBarcodes printed on it.
brandstringThe maker.
categorystringThe id of a category.
kindstringA thing on a shelf (the default), a plate on a menu, or a service that holds no stock.
One of:
goodmenuservicecostintegerWhat one cost the shop. Only kept when the key has the `costs:read` scope.
opening_quantityintegerHow many the shop has now. The default is none.
locationstringThe location the opening quantity is at. The default is the main one.
curl -X POST https://finpo.nikcano.com/api/developer/v1/products \
-H "Authorization: Bearer fk_test_..." \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"name":"Peak evaporated milk 160g","price":75000,"unit_label":"Tin","sku":"PEAK-160","barcodes":["6151100055512"],"brand":"Peak","opening_quantity":24}'{
"object": "product",
"id": "cmv2a1b2c0001prd",
"name": "Peak evaporated milk 160g",
"sku": "PEAK-160",
"brand": "Peak",
"kind": "good",
"barcodes": [
"6151100055512"
],
"category": "cmv2a1b2c0001cat",
"image_url": null,
"active": true,
"base_unit": "piece",
"container_size": 1,
"container_label": "tin",
"currency": "NGN",
"units": [
{
"key": "FULL",
"label": "Tin",
"quantity": 1,
"price": 75000,
"price_min": null,
"price_max": null
}
],
"created": "2026-10-12T09:00:00.000Z",
"updated": "2026-10-12T09:00:00.000Z"
}Stock levels
List stock levels
GET /api/developer/v1/stock_levels
How much of each product is at each location, in the product’s own unit, as counted and confirmed. `pending_change` is what staff recorded and the owner has not approved yet.
Scope: stock:read. Secret or publishable keys. MCP tool: list_stock_levels.
Parameters
limitintegerin queryHow many to return, 1 to 100. The default is 20.
starting_afterstringin queryThe id of the last object of the page before: the next page starts after it.
ending_beforestringin queryThe id of the first object of the page after: the page before ends before it.
productstringin queryOnly this product.
locationstringin queryOnly this location.
curl https://finpo.nikcano.com/api/developer/v1/stock_levels?limit=20 \
-H "Authorization: Bearer fk_test_..."{
"object": "list",
"data": [
{
"object": "stock_level",
"id": "cmv2a1b2c0001stk",
"product": "cmv2a1b2c0001prd",
"location": "cmv2a1b2c0001loc",
"quantity": 24,
"pending_change": 0,
"containers": 24,
"unit": "piece",
"container_label": "tin",
"container_size": 1,
"updated": "2026-10-12T09:00:00.000Z"
}
],
"has_more": false,
"url": "/api/developer/v1/stock_levels"
}Customers
List customers
GET /api/developer/v1/customers
The shop’s customers, newest first, with their visits, points and what they owe. `query` finds a name even typed wrong, or a phone number.
Scope: customers:read. Secret keys only. MCP tool: list_customers.
Parameters
limitintegerin queryHow many to return, 1 to 100. The default is 20.
starting_afterstringin queryThe id of the last object of the page before: the next page starts after it.
ending_beforestringin queryThe id of the first object of the page after: the page before ends before it.
querystringin queryA name or a phone number.
curl https://finpo.nikcano.com/api/developer/v1/customers?limit=20&query=Ngozi \
-H "Authorization: Bearer fk_test_..."{
"object": "list",
"data": [
{
"object": "customer",
"id": "cmv2a1b2c0001cus",
"name": "Ngozi Eze",
"phone": "+2348031234567",
"email": null,
"visits": 12,
"points": 340,
"currency": "NGN",
"balance_owed": 0,
"lifetime_spend": 4820000,
"last_visit": "2026-10-11T17:20:00.000Z",
"created": "2026-03-02T10:00:00.000Z"
}
],
"has_more": false,
"url": "/api/developer/v1/customers"
}Retrieve a customer
GET /api/developer/v1/customers/{id}
One customer by id: their name and contacts, how often they come and what they have spent, their points, and what they owe the shop.
Scope: customers:read. Secret keys only. MCP tool: retrieve_customer.
Parameters
idstringrequiredin pathThe object’s id.
curl https://finpo.nikcano.com/api/developer/v1/customers/customer_id \
-H "Authorization: Bearer fk_test_..."{
"object": "customer",
"id": "cmv2a1b2c0001cus",
"name": "Ngozi Eze",
"phone": "+2348031234567",
"email": null,
"visits": 12,
"points": 340,
"currency": "NGN",
"balance_owed": 0,
"lifetime_spend": 4820000,
"last_visit": null,
"created": "2026-03-02T10:00:00.000Z"
}Create a customer
POST /api/developer/v1/customers
Adds a customer. A business keeps one customer per name, so a name already on the list is refused with `resource_conflict`.
Scope: customers:write. Secret keys only. MCP tool: create_customer.
Parameters
namestringrequiredTheir name.
phonestringTheir phone number, which is how the shop finds them again.
notesstringAnything the shop should know.
curl -X POST https://finpo.nikcano.com/api/developer/v1/customers \
-H "Authorization: Bearer fk_test_..." \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"name":"Ngozi Eze","phone":"08031234567"}'{
"object": "customer",
"id": "cmv2a1b2c0001cus",
"name": "Ngozi Eze",
"phone": "08031234567",
"email": null,
"visits": 0,
"points": 0,
"currency": "NGN",
"balance_owed": 0,
"lifetime_spend": 0,
"last_visit": null,
"created": "2026-10-12T09:00:00.000Z"
}Sales
List sales
GET /api/developer/v1/sales
Sales recorded at the counter and on the shop computer, newest first, with their lines and payments. Sales cannot be written through the API: they are signed where they happen, which is what lets finpo check them.
Scope: sales:read. Secret keys only. MCP tool: list_sales.
Parameters
limitintegerin queryHow many to return, 1 to 100. The default is 20.
starting_afterstringin queryThe id of the last object of the page before: the next page starts after it.
ending_beforestringin queryThe id of the first object of the page after: the page before ends before it.
day_fromstringin queryThe first business day, as YYYY-MM-DD.
day_tostringin queryThe last business day, as YYYY-MM-DD.
locationstringin queryOnly sales at this location.
curl https://finpo.nikcano.com/api/developer/v1/sales?limit=20&day_from=2026-10-01 \
-H "Authorization: Bearer fk_test_..."{
"object": "list",
"data": [
{
"object": "sale",
"id": "cmv2a1b2c0001sal",
"receipt_number": "VIC-000018",
"location": "cmv2a1b2c0001loc",
"business_day": "2026-10-12",
"created": "2026-10-12T13:05:00.000Z",
"currency": "NGN",
"total": 700000,
"gross": 700000,
"customer": null,
"voided": false,
"refunded": false,
"lines": [
{
"product": "cmv2a1b2c0001prd",
"unit": "PLATE",
"quantity": 2,
"unit_price": 350000,
"list_price": 350000
}
],
"payments": [
{
"method": "cash",
"amount": 700000,
"reference": null
}
]
}
],
"has_more": false,
"url": "/api/developer/v1/sales"
}Retrieve a sale
GET /api/developer/v1/sales/{id}
One sale, and whether it was cancelled or refunded since.
Scope: sales:read. Secret keys only. MCP tool: retrieve_sale.
Parameters
idstringrequiredin pathThe object’s id.
curl https://finpo.nikcano.com/api/developer/v1/sales/sale_id \
-H "Authorization: Bearer fk_test_..."{
"object": "sale",
"id": "cmv2a1b2c0001sal",
"receipt_number": "VIC-000018",
"location": "cmv2a1b2c0001loc",
"business_day": "2026-10-12",
"created": "2026-10-12T13:05:00.000Z",
"currency": "NGN",
"total": 700000,
"gross": 700000,
"customer": null,
"voided": false,
"refunded": false,
"lines": [],
"payments": []
}Orders
List orders
GET /api/developer/v1/orders
Orders the shop has received, from its own staff, its order page, its tables and the API, newest first.
Scope: orders:read. Secret keys only. MCP tool: list_orders.
Parameters
limitintegerin queryHow many to return, 1 to 100. The default is 20.
starting_afterstringin queryThe id of the last object of the page before: the next page starts after it.
ending_beforestringin queryThe id of the first object of the page after: the page before ends before it.
statusstringin queryOnly orders in this state.
One of:
newconfirmedpreparingreadyout_for_deliverycompletedcancelled
curl https://finpo.nikcano.com/api/developer/v1/orders?limit=20 \
-H "Authorization: Bearer fk_test_..."{
"object": "list",
"data": [
{
"object": "order",
"id": "cmv2a1b2c0001ord",
"number": 42,
"location": "cmv2a1b2c0001loc",
"channel": "online",
"fulfilment": "pickup",
"status": "new",
"customer": null,
"customer_name": "Ngozi Eze",
"customer_phone": "+2348031234567",
"delivery_address": null,
"due": null,
"currency": "NGN",
"total": 150000,
"note": null,
"lines": [
{
"product": "cmv2a1b2c0001prd",
"unit": "FULL",
"name": "Peak evaporated milk 160g",
"quantity": 2,
"unit_price": 75000,
"amount": 150000
}
],
"sale": null,
"created": "2026-10-12T09:00:00.000Z",
"updated": "2026-10-12T09:00:00.000Z"
}
],
"has_more": false,
"url": "/api/developer/v1/orders"
}Retrieve an order
GET /api/developer/v1/orders/{id}
One order, with its lines and where it is now.
Scope: orders:read. Secret keys only. MCP tool: retrieve_order.
Parameters
idstringrequiredin pathThe object’s id.
curl https://finpo.nikcano.com/api/developer/v1/orders/order_id \
-H "Authorization: Bearer fk_test_..."{
"object": "order",
"id": "cmv2a1b2c0001ord",
"number": 42,
"location": "cmv2a1b2c0001loc",
"channel": "online",
"fulfilment": "pickup",
"status": "new",
"customer": null,
"customer_name": "Ngozi Eze",
"customer_phone": "+2348031234567",
"delivery_address": null,
"due": null,
"currency": "NGN",
"total": 150000,
"note": null,
"lines": [
{
"product": "cmv2a1b2c0001prd",
"unit": "FULL",
"name": "Peak evaporated milk 160g",
"quantity": 2,
"unit_price": 75000,
"amount": 150000
}
],
"sale": null,
"created": "2026-10-12T09:00:00.000Z",
"updated": "2026-10-12T09:00:00.000Z"
}Create an order
POST /api/developer/v1/orders
Sends an order to the shop, the way a customer’s order from its order page arrives: staff see it on the Orders board, prepare it, and take the money at the counter, where it becomes a signed sale. Prices are always the shop’s own: the order is priced by finpo from the products and units sent. Safe with a publishable key, from a shop’s own website or app.
Scope: orders:write. Secret or publishable keys. MCP tool: create_order.
Parameters
customer_namestringrequiredWho the order is for.
customer_phonestringrequiredA number the shop can call.
fulfilmentstringCollected at the shop (the default) or delivered.
One of:
pickupdeliverydelivery_addressstringWhere to deliver it. Needed for a delivery.
linesarray of objectrequiredWhat is being ordered.
productstringrequiredThe product’s id.
unitstringThe key of the unit it is sold in. The default is its first unit.
quantityintegerrequiredHow many.
notestringAnything the shop should know.
locationstringThe location to send it to. The default is the main one.
curl -X POST https://finpo.nikcano.com/api/developer/v1/orders \
-H "Authorization: Bearer fk_test_..." \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"customer_name":"Ngozi Eze","customer_phone":"08031234567","lines":[{"product":"cmv2a1b2c0001prd","unit":"FULL","quantity":2}]}'{
"object": "order",
"id": "cmv2a1b2c0001ord",
"number": 42,
"location": "cmv2a1b2c0001loc",
"channel": "online",
"fulfilment": "pickup",
"status": "new",
"customer": null,
"customer_name": "Ngozi Eze",
"customer_phone": "+2348031234567",
"delivery_address": null,
"due": null,
"currency": "NGN",
"total": 150000,
"note": null,
"lines": [
{
"product": "cmv2a1b2c0001prd",
"unit": "FULL",
"name": "Peak evaporated milk 160g",
"quantity": 2,
"unit_price": 75000,
"amount": 150000
}
],
"sale": null,
"created": "2026-10-12T09:00:00.000Z",
"updated": "2026-10-12T09:00:00.000Z"
}Webhook endpoints
List webhook endpoints
GET /api/developer/v1/webhook_endpoints
The addresses finpo sends events to. The signing secret is shown only when an endpoint is created.
Scope: webhooks:write. Secret keys only. MCP tool: list_webhook_endpoints.
No parameters.
curl https://finpo.nikcano.com/api/developer/v1/webhook_endpoints \
-H "Authorization: Bearer fk_test_..."{
"object": "list",
"data": [
{
"object": "webhook_endpoint",
"id": "cmv2a1b2c0001whk",
"url": "https://example.com/finpo/webhooks",
"enabled_events": [
"sale.recorded",
"order.created"
],
"active": true,
"created": "2026-10-12T09:00:00.000Z"
}
],
"has_more": false,
"url": "/api/developer/v1/webhook_endpoints"
}Create a webhook endpoint
POST /api/developer/v1/webhook_endpoints
Starts sending the chosen events to an address. The answer carries the signing secret, once: keep it, and check every delivery’s `Finpo-Signature` header with it (see Webhooks).
Scope: webhooks:write. Secret keys only. MCP tool: create_webhook_endpoint.
Parameters
urlstringrequiredAn https address of yours.
enabled_eventsarray of stringrequiredWhich events to send: sale.recorded, stock.changed, item.upserted, customer.upserted, order.created.
curl -X POST https://finpo.nikcano.com/api/developer/v1/webhook_endpoints \
-H "Authorization: Bearer fk_test_..." \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/finpo/webhooks","enabled_events":["sale.recorded","order.created"]}'{
"object": "webhook_endpoint",
"id": "cmv2a1b2c0001whk",
"url": "https://example.com/finpo/webhooks",
"enabled_events": [
"sale.recorded"
],
"active": true,
"created": "2026-10-12T09:00:00.000Z",
"secret": "whsec_..."
}Delete a webhook endpoint
DELETE /api/developer/v1/webhook_endpoints/{id}
Stops sending events to that address.
Scope: webhooks:write. Secret keys only. MCP tool: delete_webhook_endpoint.
Parameters
idstringrequiredin pathThe object’s id.
curl -X DELETE https://finpo.nikcano.com/api/developer/v1/webhook_endpoints/webhook_id \
-H "Authorization: Bearer fk_test_..."{
"object": "webhook_endpoint",
"id": "cmv2a1b2c0001whk",
"deleted": true
}Changelog
2026-10-12: the first version. Products, stock levels, customers, sales, orders, locations, categories, webhook endpoints; secret and publishable keys; test mode; the MCP server.