AkkadAkkad

Team & security

Thirty-nine grants, four restrictions, and one real boundary

Access control in a business app has two separate jobs that are usually confused with each other. One is management: deciding what a member should be shown so they can do their work without wading through everything else. The other is security: making certain one business's data can never reach another's. Akkad treats these as different problems with different guarantees, and says plainly which is which.

A member's access is a list of grants. Each grant is a single, named capability — not a role you have to reverse-engineer, and not a tier where “manager” silently includes eleven things nobody enumerated. You tick what someone does and that is what they can do.

Owners are the exception, and the exception is total: an owner bypasses every check on the platform. That is deliberate. There is always one person who can get to everything, and pretending otherwise produces businesses locked out of their own data by a permission model that was too clever.

01

Thirty-nine grants, in the groups you actually see

These are the eight groups the app presents, with every grant inside them. Nothing is bundled — each name below is a separate switch.

GroupGrants
Orders (7)createOrders · editOrders · deleteOrders · printOrders · sendShipments · viewShipmentLog · accessCashier
Items (4)createItems · editItems · deleteItems · viewCostPrice
Performance & statistics (8)viewDashboardStatistics · viewSalesPerformance · viewInventoryPerformance · viewUsersPerformance · viewCategoriesPerformance · viewExpiryReport · viewStockHealth · viewCitiesPerformance
Finance (6)manageExpenses · manageIncomes · manageCashbox · manageCashier · manageDebts · viewFinancialReport
People & access (4)manageUsers · manageCustomers · manageSuppliers · managePurchaseOrders
Platform settings (7)manageSettings · manageCategories · managePrinting · managePackageSize · manageIntegration · manageShippingCostRules · manageDiscountRules
Data & export (2)exportOrders · exportItems
Online presence (1)manageOnlinePresence

viewCostPrice is worth calling out separately. Cost price is the number most businesses least want circulating, so it is not implied by being able to edit items — it is its own grant, and a member can maintain the catalogue all day without ever seeing what you paid. Grants renamed over the platform's life are migrated automatically, so an older account keeps working without anyone re-ticking boxes.

02

The unusual part

Restrictions that limit rather than allow

Grants say what a member may do. Restrictions say what a member may see, and they run the opposite way round from everything else in the interface: adding an entry makes a member's world smaller, not larger. All four live behind a single “Advanced” row that shows an n / 4 counter, so it is always visible at a glance whether a member is scoped at all. They are hidden entirely for owners, who can never be restricted.

OrganizationEvery endpoint is scoped by organization before anything else runs.
39 permission grantsWhat this member may do — orders, items, reports, finance, settings.
Category visibilityWhich part of the catalogue exists for them at all.
Creator visibilityWhose orders they can see — down to not being notified about the rest.
Each layer narrows the one above it. Hidden records answer 404 rather than 403, so the boundary never confirms that something exists.

No entry means unrestricted

A member with no restriction recorded sees everything — including categories and colleagues added next month. This is the property that matters most in practice: an unrestricted member does not need re-editing every time the business grows.

An empty list means nothing

A recorded restriction holding no selections is a real instruction, and it means the member sees none of that thing. Empty is not the same as absent, and conflating them would be the difference between “sees everything” and “sees nothing”.

Selecting everything removes the restriction

Tick every category and the entry is deleted rather than stored as a list of all current categories. Otherwise a member who was granted the whole catalogue today would silently stop seeing whatever was added tomorrow.

03

The four restrictions

Two govern what a member can change; two govern what a member can see at all.

RestrictionEffect when present
Cannot edit finalised ordersOnce an order's saved status is shipped or delivered, this member can no longer change its contents. They can still read it.
Cannot set finalised statusesShipped and delivered are removed from this member's manual status picker. The carrier integration can still set them — the point is to stop a member marking an order delivered by hand, not to stop the courier reporting reality.
Category visibilityThe member sees only the listed top-level categories, and the items inside them. A subcategory is visible when its parent is allowed. The scope rides every read path — list, search, export, images, pack sizes and batches — and it also governs categories, preload and websites.
Order-creator visibilityThe member sees only orders written by the listed people. The options are every member of the organisation plus a storefront pseudo-creator covering public storefront and landing-page orders, so “their own orders plus everything the website brings in” is expressible.

04

How a hidden record behaves

Hiding a record is easy. Hiding it without leaking its existence through the edges is where the work is.

Hidden records answer 404, not 403

A 403 confirms that the thing exists and you are not allowed to see it. A 404 says nothing at all. Items and orders outside a member's scope answer as though they were never there — on read, on edit, and on appending to an order.

The bulk endpoint is filtered too

Resolving up to four hundred codes in one call is genuinely useful for stocktakes. It is also the ideal shape for a barcode-enumeration probe, which is precisely why the category filter is applied to it. Without that, the one endpoint built for volume would have been the one way around the restriction.

A restricted member is not even notified

Push notification for a new order respects order-creator visibility. Someone who cannot see an order does not receive an alert about it — otherwise the notification itself would tell them what the orders list is hiding.

Filtering happens before counting

Visibility is applied ahead of every other filter and ahead of the count, so page totals and pagination reflect what the member can see. A list that says “87 orders” and shows 41 is a bug in the making; this avoids it structurally. Bulk shipping sweeps are scoped the same way, so a restricted member cannot dispatch the whole organisation in one action.

Restrictions are a management convenience, not a security boundary

This is worth being blunt about. Visibility restrictions are designed to keep a member's screen relevant and to reduce the blast radius of ordinary mistakes. They are not designed to withstand someone determined to defeat them, and if their stored data is malformed they fail open — the member sees more, not less, because the alternative is a business that silently cannot work. The real boundary, the one built to hold, is organisation scoping. Do not use a category restriction where you mean a separate organisation.

05

The boundary that does hold

Every business endpoint on the platform is addressed by organisation, and every one of them passes through the same check before any work happens: does this organisation exist, is this user a member of this organisation, and does this user hold the permission this endpoint requires. There is no path that skips it and no endpoint that assumes membership from context.

The two failure modes are deliberately distinct. A missing organisation or user is a 404. A real organisation you are not a member of, or a permission you do not hold, is a 403 naming which of the two it was. That distinction is for the person debugging their own setup — it never reveals anything about a business you have no relationship with.

The bulk-sync endpoint that feeds offline mode validates membership explicitly rather than inheriting it. That endpoint returns an entire organisation's catalogue in one response, and it was worth writing the check twice rather than discovering that any authenticated user could pull down anyone's snapshot.

06

Sessions and sign-in

Bearer tokens, nothing else

Authentication is a signed token carrying the user id, sent as a bearer token. There is no cookie session and no alternative path to authenticate a request. Tokens are held in the device's secure storage on the client rather than in ordinary app storage.

Long-lived on purpose

Access tokens last six months. A shop floor where staff are signed out mid-shift produces workarounds — shared logins, written-down passwords — that are worse than the risk the short expiry was guarding against.

Social sign-in verified properly

Apple Sign-In tokens are verified against Apple's own public keys, cached for twenty-four hours. Google and Apple both run through brokered flows where the confidential secret never leaves the server.

07

Rate limits, by endpoint

Limits are keyed on the real client address, proxy-aware, so a shared gateway does not make one customer's traffic look like an attack.

Endpoint groupLimit
Authentication5 per minute and 20 per hour
Push registration / deletion10 per minute
Push sending50 per hour
Bulk import8 per day
Bulk image URLs5 per day
Item image generation25 per day, keyed per organisation
Client diagnostics12 per hour
News5 per hour
Announcements20 per hour and 50 per hour
Public storefront500 per hour, keyed per address and store
Storefront checkout20 per hour
Storefront browsing30 to 60 per minute
Public order creation20 per hour
Payments30 per minute
Discount quote60 per minute
Promo code validation30 per minute

The storefront limits are keyed per address and per store rather than globally, so a busy shop's shoppers are never throttled by another shop's traffic.

08

Underneath every request

The layer nobody should ever have to think about, listed here because it is reasonable to ask what it consists of.

Request size caps

Any request declaring more than 30 MB is rejected outright. The multipart parser independently caps both file size and part size at 30 MB, so the limit cannot be sidestepped by splitting an upload into parts.

Query hardening

Any API request asking for more than 100 records is rejected, as is any query string longer than a thousand characters. Both are limits that legitimate use never reaches and scraping tools reach immediately.

Probes are recorded, not just refused

Requests for the usual targets — an admin panel, a WordPress path, an environment file, a config file — return 404 and are written as security events. Refusing them is the minimum; knowing when they started is the useful part.

Security headers on every response

Content-type sniffing off, framing denied, a strict referrer policy, and a server identity header that reports Akkad rather than the real stack. The last one is not security by itself; it removes a free hint.

Per-request identifiers

Every request carries an identifier that is echoed back to the caller and threaded through structured logging. When something goes wrong you can point at one specific request rather than describing roughly when it happened.

Retention with an end date

Monitoring events are kept thirty days and request logs fourteen. Organisations inactive for twelve months are warned and then deleted. Data that is no longer needed is a liability, and the cleanup runs on a schedule rather than on good intentions.

09

Questions

If I restrict a member to two categories, do they get new categories automatically?

No — that is the point of the inverse semantics. A member with a restriction sees only what is listed, and a category created later is not listed. A member with no restriction at all inherits everything new automatically. If you want someone to keep seeing the whole catalogue as it grows, leave them unrestricted rather than ticking every box.

What is the difference between an empty restriction and no restriction?

No entry means unrestricted — the member sees everything, now and later. An empty list is an explicit instruction that the member sees nothing of that kind. They look similar in a database and behave as opposites, so the interface keeps them distinct.

Why does a hidden order return “not found” rather than “not allowed”?

Because “not allowed” confirms it exists. If a member can learn that order 4417 is real simply by being refused it, the restriction has leaked the thing it was hiding. Hidden records behave as though they were never created.

Can I use category restrictions to separate two businesses I own?

No. Restrictions are a management convenience and are documented as failing open if their data is malformed. Separate businesses should be separate organisations, which is the boundary that is actually enforced on every request.

Will a restricted member get a notification about an order they cannot see?

No. Order-creator visibility is applied to push notifications as well as to the orders list, so the alert cannot reveal what the list is hiding.

Can an owner be restricted?

No. Owners bypass every permission check, and the restriction controls are hidden for them entirely. Someone always has to be able to reach everything.

Reports & analytics

Nine reports, each behind its own grant — including the team leaderboard that honours creator visibility.

Orders

The lifecycle these restrictions govern: statuses, editing, change logs and idempotent creation.

Apps & offline

Six platform targets, offline order taking, printing and the notification system permissions gate.

Посмотрите на своём собственном каталоге

Начните с подходящего тарифа, импортируйте товары из таблицы — и уже в тот же день у вас будет работающий магазин, касса и книга заказов.

Есть бесплатный тариф. Карта для пробы не нужна. Импорт перенесёт ваш нынешний каталог.