Developer documentation
Everything you need to build a Stockcito app: manifests, permissions, Public API, UI extensions, automations and Marketplace publishing, explained with practical examples.
Introduction
What is a Stockcito app?
An app is an external service (your own server, in any language) that connects to a Stockcito retailer account to add a capability: read orders and products through an API, extend the dashboard with declarative components, or react automatically to events through Stockcito Flow.
Your app never runs code inside Stockcito. Everything happens over HTTP: Stockcito calls your service (webhooks, UI extensions, Flow actions), or your service calls Stockcito (Public API) with a token issued to the retailer when they install your app. Requests between both sides are signed with HMAC so neither side can be impersonated.
Get started
Your developer account
It is separate from a retailer account. You never share a login with the retailer installing your app.
Create your developer account
Sign up at /developers with your email. This is separate from creating a retailer account.
Register your first app
Add its name, description, category and your server's base URL (endpoints.baseUrl).
Write your manifest
The JSON file that declares what your app can do. See The manifest below.
Test it with a test account
Install it in your own test retailer account before submitting it for Marketplace review.
Your developer dashboard lists all your apps and delivery summaries for each installation (webhooks sent and whether they failed). It never shows the retailer's business data: you will only see that delivery #123 to installation #45 failed with HTTP 500, not their actual sales or customers.
Reference
The manifest
One JSON document describing your app: who it is, which permissions it requests, and which extensions it provides.
{
"key": "acme.puntos",
"name": "Acme Loyalty",
"description": "Purchase rewards program",
"category": "Crecimiento",
"version": "1.0.0",
"author": { "name": "Acme SRL", "url": "https://acme.dev" },
"roles": ["owner", "admin"],
"scopes": ["orders:read", "customers:read"],
"events": ["order.paid"],
"extensions": {
"admin": ["dashboard.card"],
"dna": [{ "key": "loyalty-widget", "name": "Loyalty widget" }],
"automationActions": [{ "key": "add_points", "name": "Add points" }]
},
"endpoints": {
"baseUrl": "https://acme.dev",
"webhookPath": "/webhook"
}
}keystringA unique namespace containing a dot, e.g. acme.loyalty. This is your stable identifier; do not change it between versions.
categoryenumAccepted enum values: Escritorio, Comercio, Contenido, Crecimiento, Integraciones, Sistema (currently submitted in Spanish).
rolesstring[]Retailer roles allowed to install your app: owner, admin, manager, cajero (cashier). Defaults to owner only.
scopesstring[]Permissions used by the Public API (see Scopes). They are shown as-is on the consent screen before installation.
eventsstring[]Webhook events you subscribe to (order.created, order.paid, order.shipped, order.cancelled, product.published, inventory.low, customer.registered).
extensions.adminstring[]UI extensions served inside the retailer Dashboard: dashboard.card, order.sidebar, product.sidebar, customer.sidebar.
extensions.dnaobject[]Modules your app offers for the retailer's public store (Studio → Home design).
extensions.automationActionsobject[]Actions Stockcito Flow can call on your server when a retailer creates an automation.
endpoints.baseUrlurlYour server root. Webhook, UI and action paths are resolved relative to it. It must use public HTTPS; Stockcito rejects private IPs and localhost to prevent SSRF.
endpoints.webhookPathpathWhere you receive the events declared in events. Defaults to /webhook.
Permissions
Available scopes
orders:readRead ordersorders:writeCreate or update ordersproducts:readRead productsproducts:writeCreate or update productscustomers:readRead customerscustomers:writeCreate or update customersinventory:readRead inventoryinventory:writeUpdate inventorysales:readRead salessales:writeCreate or update salesshipping:readRead shipping zonesshipping:writeUpdate shipping zonesCurrently, *:read scopes grant access to each Public API resource listed below. *:write scopes are reserved for future use. Request only the permissions your app needs, since retailers see each one on the consent screen before installation.
Flow
Installation and consent
Retailers never install your app blindly — they always see a consent screen first.
The retailer enters your manifest URL (or pastes the JSON directly) under Settings → Apps. Stockcito downloads and validates it, then shows your name, description, version and a plain-language permission list, such as “this app can read orders and customers,” before they confirm.
After confirmation, Stockcito creates a unique access token for that installation and displays it once. The retailer (or your app, if requested) uses it as Authorization: Bearer <token> with the Public API. Stockcito stores only a hash; if the token is lost, it cannot be recovered and the app must be reinstalled.
Reference
Public API
Read-only access to the business data of the retailer who installed your app, authenticated with an installation token.
Base URLurlhttps://api.stockcito.com/api/public/v1
AuthorizationheaderBearer <token> — The token issued when the app was installed.
Stockcito-VersionheaderRequired on every request. Current value: 2026-09. Date-based versioning lets us introduce changes without breaking existing integrations.
Rate limitlímite60 requests per minute per token (not per IP), separate from the general API limit.
Available resources (all read-only and paginated with page/perPage):
GET /productsproducts:readGET /customerscustomers:readGET /salessales:readGET /inventoryinventory:readGET /ordersorders:readGET /returnsorders:readGET /shippingshipping:readcurl "https://api.stockcito.com/api/public/v1/products?page=1&perPage=20" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Stockcito-Version: 2026-09"
# {"data":[...], "total":123, "page":1, "perPage":20}Reference
UI extensions
Show your own content inside a retailer's Dashboard without writing HTML, CSS or JavaScript.
When you declare dashboard.card under extensions.admin, Stockcito sends a signed GET request to {baseUrl}/stockcito-ui/dashboard-card and renders your response as a card in the retailer's Dashboard. You never send HTML. Instead, send a declarative node tree with a closed set of values, so nothing outside the supported interface can be injected.
// Your server responds to GET /stockcito-ui/dashboard-card
{
"type": "card",
"title": "Points balance",
"children": [
{ "type": "text", "value": "1,240 active points", "weight": "bold" },
{ "type": "badge", "label": "+80 this week", "tone": "success" },
{ "type": "link", "label": "View details", "href": "https://acme.dev/panel" }
]
}Available nodes: text, badge, link, stack (groups children in a row or column) and card (nests the others). Each level is limited to 20 children and responses to 20 KB. If your server is down, too slow or returns invalid data, Stockcito hides the card without breaking the retailer's Dashboard.
Each signed request includes X-Stockcito-Timestamp and X-Stockcito-Signature: sha256=..., calculated from the signing key, timestamp, a period, then the path. Verify it on your server and reject timestamps more than five minutes away to prevent replay attacks.
Verify the signature in Node/Express:
const crypto = require("crypto")
const SIGNING_KEY = process.env.STOCKCITO_SIGNING_KEY // issued by Stockcito at installation
const TOLERANCE_SECONDS = 300
app.get("/stockcito-ui/dashboard-card", (req, res) => {
const timestamp = req.header("X-Stockcito-Timestamp")
const signatureHeader = req.header("X-Stockcito-Signature") || ""
const path = req.path // "/stockcito-ui/dashboard-card"
const now = Math.floor(Date.now() / 1000)
if (!timestamp || Math.abs(now - Number(timestamp)) > TOLERANCE_SECONDS) {
return res.status(401).send("Invalid or expired timestamp")
}
const expected = crypto
.createHmac("sha256", SIGNING_KEY)
.update(`${timestamp}.${path}`)
.digest("hex")
// timingSafeEqual prevents timing attacks.
const received = signatureHeader.replace("sha256=", "")
const valid =
received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected))
if (!valid) return res.status(401).send("Invalid signature")
res.json({
type: "card",
title: "Points balance",
children: [{ type: "text", value: "1,240 active points", weight: "bold" }],
})
})Reference
DNA Modules
Add a module served by your app to the retailer's public store through Studio.
Declare each module with a stable key and display name in extensions.dna. In Studio → Home design, the retailer can choose which module to add. Stockcito requests its content from {baseUrl}/stockcito-ui/dna/{key} using the same signed UI protocol, validates it against the supported node schema, and caches successful content for 60 seconds per retailer. Public visitors see the rendered module; your app never receives direct access to the store or its database.
GET https://acme.dev/stockcito-ui/dna/reviews-widget · X-Stockcito-Timestamp · X-Stockcito-Signature
Reference
Flow Actions
Actions a retailer can invoke automatically from their Flow automations.
Each action in extensions.automationActions becomes available when the retailer builds a rule such as “when X happens, do Y.” When triggered, Stockcito sends a signed POST to {baseUrl}/stockcito-actions/{actionKey}, with the event payload in the body.
POST /stockcito-actions/add_points
Content-Type: application/json
X-Stockcito-Timestamp: 1735689600
X-Stockcito-Signature: sha256=<HMAC of the body with your key>
{ "orderId": 4821, "total": 15400, "customerId": 92 }Verify the signature as you do for UI extensions, but here the HMAC covers the timestamp, a period, then the body (the complete JSON, not a path). Return HTTP 2xx on success. A transient HTTP 4xx/5xx (for example, a temporary outage) triggers retries with backoff. Do not return 2xx for a permanent failure you have identified: Stockcito would treat it as successfully processed.
Complete minimal server (Node, no framework): receives the action, verifies the body signature and responds:
const http = require("http")
const crypto = require("crypto")
const SIGNING_KEY = process.env.STOCKCITO_SIGNING_KEY
http.createServer((req, res) => {
if (req.method !== "POST" || req.url !== "/stockcito-actions/add_points") {
res.writeHead(404).end()
return
}
let body = ""
req.on("data", (chunk) => { body += chunk })
req.on("end", () => {
const timestamp = req.headers["x-stockcito-timestamp"]
const received = (req.headers["x-stockcito-signature"] || "").replace("sha256=", "")
const expected = crypto
.createHmac("sha256", SIGNING_KEY)
.update(`${timestamp}.${body}`)
.digest("hex")
if (received.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected))) {
res.writeHead(401).end("Invalid signature")
return
}
const { orderId, total, customerId } = JSON.parse(body)
// Add points, create a shipment, or run your business logic here.
console.log(`Order ${orderId} for ${customerId}: $${total}`)
res.writeHead(200, { "Content-Type": "application/json" })
res.end(JSON.stringify({ ok: true }))
})
}).listen(3000)Distribution
Marketplace
Publish your app so retailers can find and install it with confidence.
Submit your app for review
When you create or update an app at /developers, it becomes “pending”; it is never published automatically.
Stockcito reviews it
A reviewer checks that the manifest matches what the app claims to do. You may receive a rejection with a reason.
A snapshot is approved
Installations use the approved version (approvedManifest), never your in-progress draft. A new version under review cannot silently replace an installed version.
Set a price (optional)
Offer it for free or charge once per installation through Mercado Pago Connect. Recurring app subscriptions are not available today.
To charge for your app, connect your own Mercado Pago account from /developers (the same OAuth flow retailers use for store payments, for you as a developer). Purchases are charged to your Mercado Pago token; Stockcito keeps a platform fee, and the app stays inactive until the payment webhook confirms the funds were credited.
Tools
TypeScript SDK
An initial TypeScript SDK (packages/sdk-ts) provides types for Public API resources. It is not published to a public registry such as npm yet. For now, the supported integration is direct HTTP + JSON, documented above and available from any language.
Best practices
Security
Public HTTPS is required
Your endpoints.baseUrl must resolve to a public IP. Stockcito rejects localhost, private IPs (10.x, 192.168.x, etc.) and redirects. We never call services inside your private network.
Always verify signatures
Never process a webhook, UI fetch or Flow action before validating X-Stockcito-Signature with the exact signing key issued to you.
Reject stale timestamps
A timestamp more than five minutes away from your clock may indicate a replay. Reject it even if its signature is valid.
Keep access tokens secret
A Public API installation token is equivalent to a password for that retailer's data. Protect it like a database credential.
Support
Frequently asked questions
Does a retailer need to find my app in the Marketplace to use it?▼
No. Any retailer can install your app by entering its manifest URL or pasting the JSON directly, without Marketplace review. The Marketplace helps retailers who do not know you discover an app that Stockcito has reviewed.
Can I change my manifest without breaking existing installations?▼
Yes. Existing installations keep using the approved version until you submit an update and that version passes its own review.
How can a retailer test my app?▼
Install it by pasting the manifest directly. You do not need Marketplace approval to test it in your own test account.
What happens if my server is down when Stockcito calls it?▼
UI extensions and DNA modules are simply hidden, so the retailer's experience keeps working. For Flow Actions and webhooks, Stockcito retries with exponential backoff for a period before marking the delivery as failed.