Permissions
Each key carries a list of permissions, chosen at creation. The format is module:action, always in English, and the actions are read, create, update and delete.
clients:readclients:createservice_orders:updateGET /public/v1/me returns in key.permissions exactly what the key has.
Modules and actions
Section titled “Modules and actions”| Module | Permissions |
|---|---|
Clients clients | clients:read clients:create clients:update clients:delete |
Leads leads | leads:read leads:create |
Service orders service_orders | service_orders:read service_orders:create service_orders:update service_orders:delete |
Quotes quotes | quotes:read quotes:create quotes:update quotes:delete |
Sales sales | sales:read sales:create sales:update |
Finance finance | finance:read finance:create finance:update finance:delete |
Schedule appointments | appointments:read appointments:create appointments:update appointments:delete |
Products and stock products | products:read products:create products:update products:delete |
Services services | services:read services:create services:update services:delete |
Tasks tasks | tasks:read tasks:create tasks:update tasks:delete |
Team team | team:read |
Webhooks webhooks | webhooks:read webhooks:create webhooks:update webhooks:delete |
Events events | events:read |
Not every module accepts the four actions. sales has no delete because the panel does not delete a sale, and team and events are read only.
This table comes from the API contract, from the same file the panel and the server read. A new module shows up here on its own. Having the permission ticked on the key does not mean the route of that module already exists: the Reference is the list of what answers today.
GET /public/v1/me answers any valid key, with no specific permission required.
Keys created in the first version of the API are still valid: leads:write counts as leads:create, and leads:read and team:read stay the same.
Effective permission is an intersection
Section titled “Effective permission is an intersection”What a key can actually do is where four things meet:
key permission ∩ member role ∩ member permissions ∩ company plan
One of them saying no is enough for the call to be refused. That is why a key with clients:create can still get a 403.
1. The key permission
Section titled “1. The key permission”The key needs the permission the route requires. Without it, the answer is 403 with the code permission_missing. A public route with no declared permission is refused too: the default is to deny, never to allow.
Lead capture touches two modules, and the key needs the permissions of both:
POST /public/v1/leadsrequiresleads:create. When the call opens the contact task, either the default one (a body withouttask) or the one you describe intask, it also requirestasks:create. Without it, the answer is 403permission_missingand nothing is saved, not even the lead. Withtask: nullno task is opened, andleads:createis enough.- If the phone number or the email was already on file, the response carries
deduplicated: true. In that case, a key with neitherleads:readnorclients:readgets onlyobjectandid, in bothleadandtask, and not the name, email, status and source of whoever was already in the base. The task comes back reduced because its default title is “Entrar em contato com” followed by the name of the existing record; in the dashboard, the title stays complete. A key that only captures does not read clients.
Service orders also write to other modules in two options, and the rule is the same:
POST /public/v1/service_orderswithcreate_appointment: trueandscheduled_startcreates a calendar appointment, and also requiresappointments:create. Withoutscheduled_startno appointment is created, as in the dashboard, and the permission is not required.POST /public/v1/service_orders/{id}/completewithlink_financial_transaction: trueposts the income to finance, and also requiresfinance:create.
Without the second permission, the answer is 403 permission_missing and nothing is saved: neither the order nor the completion. Without those two options, the service order permission is enough.
The quote conversions follow the same rule, and each extra permission is only required when the effect really happens:
POST /public/v1/quotes/{id}/convert_to_salealways creates a sale, and also requiressales:create.- When the request ASKS for the finance posting (
track_in_finance: trueor an account inpayment_plan.account_id), it also requiresfinance:create. When the field is left out and the sale goes to finance because the organization setting posts sales (the default), the permission is NOT required: that posting is an organization automation, like the contract on approval (see below), and the dashboard screen also lets someone who does not see finance convert that way. - Sending
track_in_financeor the account requires the finance permission on the member: the dashboard conversion window hides both from whoever does not see finance (403member_permission_denied). decrement_stock: truedecreases the stock of catalog products and does NOT requireproducts:update: decreasing stock is part of selling, and the dashboard sale paths (including the seller portal) decrease it without the products permission.products:*is only required on the catalog routes themselves (products, variations, categories and stock).POST /public/v1/service_orders/from_quoterequiresservice_orders:createandquotes:readon the key, and the quotes permission on the member (the order is born with the quote content, and converting what the key cannot read would do more than the person would) and, withcreate_appointment: trueandscheduled_start, alsoappointments:create, as creating a service order does.
Without the extra permission, the answer is 403 permission_missing and nothing is saved: neither the sale nor the service order.
Sales follow the same rule. The key needs the route sales permission and, when the effect really happens, the permission of the module it writes to:
- Creating a sale that ASKS for the finance posting (
track_in_finance: trueor an account inpayment_plan.account_id) requiresfinance:create. Without the field, the sale goes to finance through the company setting (the default), and that does NOT requirefinance:create: it is a company automation, the same one the screen applies to someone who does not see finance. The stock decrease (decrement_stock,trueby default) does not requireproducts:update: it is part of selling, as in the dashboard. - Receiving, reversing a payment, rescheduling installments, creating a new version and canceling the sale are SALES actions, like the dashboard Receipts tab, which a member without finance operates: they only ask for
sales:updateon the key and the sales permission on the member, even on a sale that is in finance or that decreased stock. What these actions change in finance (entries) and in stock (returns) is a sale automation. - The finance permission only comes in when the request CHOOSES something from finance: an account (
account_idon a payment orpayment_plan.account_idon the sale and on the version) requiresfinance:readandfinance:create(on the version,finance:update) on the key, and the finance permission on the member. Decidingtrack_in_financeon creation requires the finance permission on the member. The dashboard screen hides both from whoever does not see finance.
Without the extra permission, the answer is 403 and nothing is saved.
Finance uses finance:read, finance:create, finance:update and finance:delete, and the member finance permission, in its three areas (entries, accounts and categories):
- Reading is
finance:read. Creating an entry, transfer, recurrence, installments, account and category isfinance:create. Updating andmark_paidarefinance:update. Deleting isfinance:delete. - Roles are the dashboard ones: only owners and admins create, update and delete categories, and only they delete accounts. Any other role gets 403
member_permission_denied. mark_paidon a sale installment records the SALE payment, and only asks for the finance permission: the payment is a consequence of the action, as in the dashboard, and does not ask forsales:*. It is the sales principle in the other direction: receiving through the sale does not ask forfinance:*.- Every finance read schedules the sync of sales into finance, like the screen. It is a company automation and does not ask for
sales:read.
Approving a quote can create a contract, when the organization turned on the automatic monthly contract. That creation is an organization automation, which runs the same way when the client approves through the portal, and it does not ask for an extra permission on the key: quotes:update is enough to approve.
2. The member role
Section titled “2. The member role”The key acts as a member of the company, chosen at creation. Their role applies in the API exactly as it applies in the panel:
- OWNER and ADMIN go through every area.
- MEMBER depends on the permissions ticked in their profile, both to read and to write.
- VIEWER never writes. They read the areas they have ticked.
A key never acts as a member whose role is above the role of whoever created it. An ADMIN does not issue a key that acts as an OWNER.
3. The member permissions
Section titled “3. The member permissions”Within the MEMBER and VIEWER roles, the panel ticks area by area what the person can reach. The API honours the same ticks. If the person does not see finance on screen, the key that acts as them does not read finance through the API. The refusal is 403 with the code member_permission_denied.
The same ticks trim lists that bring different areas together. GET /public/v1/team returns the team the panel would show to the member: technicians only with the technicians permission, sellers only with the sales permission, and panel members and assistants with the tasks permission. OWNER and ADMIN see everyone. Someone without one of those permissions gets the list without those people, not an error.
This applies immediately. Change the ticks in the panel and the next call already feels it, with nothing to wait for. And if the member is suspended or removed from the company, the key stops authenticating: the answer is no longer a 403, it becomes the 401 from Authentication.
4. The company plan
Section titled “4. The company plan”The plan needs to include the API, and the plan’s record quotas apply through the API just the same. The refusals are 403 with plan_feature_unavailable (the plan does not include the feature) and plan_limit_reached (the quota is used up). A subscription that is not in good standing answers 403 with subscription_inactive.
How to tell which of the four refused
Section titled “How to tell which of the four refused”The error code says which layer refused:
permission_missing: the key refused. Create a key with the missing permission, or point the integration at a key that already has it.member_permission_denied: the member’s role or permissions refused. Adjust that member’s access in the panel, or issue the key for another member.plan_feature_unavailable: the plan refused. The company plan does not include that feature.plan_limit_reached: the plan quota refused. The quota for the period is used up.subscription_inactive: the subscription refused. Settle the subscription in the panel.access_denied: a 403 with no mapped cause. The API refused and none of the codes above applies. If you get this one, send therequest_idto support.
401, 403 and 404
Section titled “401, 403 and 404”The three answers mean different things, and the difference matters while debugging.
401, authentication_error. The API does not know who is calling. Every authentication refusal returns the same answer, with the code api_key_invalid. See Authentication.
403, permission_error. The API knows who is calling and does not allow it. It is one of the four layers above.
404, invalid_request_error. The resource does not exist, or it exists and belongs to another company. An id from another company answers 404, never 403: answering 403 would confirm that the id exists somewhere. A path that is not an API route also answers 404, with the code route_not_found.
Read it straight: 401 is a key problem, 403 is a right problem, 404 is an address or id problem.
Choosing the permissions of a key
Section titled “Choosing the permissions of a key”- Tick only what the integration uses. An integration that reads clients does not need
clients:delete. - One key per integration. Revoking one does not affect the others, and the usage log shows who called what.
- Issue the key for the right member. If the integration only needs to read, issue it for a member who only reads: then not even a wrong tick on the key opens writing.