Skip to content

Overview

The Faturei Hoje public API lets another system read and write your company data without going through the panel. They are HTTP calls with JSON, authenticated by a key that you create, rotate and revoke in the panel.

Each key belongs to one company and acts as one member of it. A key never does more than that member would do on screen.

  • An ERP, a CRM or your own system that needs the same data the company sees in the panel.
  • A website or a form that sends contacts into the company.
  • An automation tool that fires HTTP calls, such as n8n, Make and Zapier.

The call goes out from your server. The key must not show up in your customer’s browser nor inside an app installed on their phone.

https://api.fatureihoje.com/public/v1

That address serves only /public/v1 and the /.well-known/security.txt file. Any other path answers 404. Every response carries the Request-Id header and Cache-Control: no-store.

The v1 is under construction. Today the API publishes thirteen areas:

AreaRoutes
AccountGET /public/v1/me: the key’s company, the permissions granted, the member it acts as and the call limit
CalendarGET, POST, PATCH and DELETE on /public/v1/appointments, plus POST /public/v1/appointments/{id}/status
ClientsGET, POST, PATCH and DELETE on /public/v1/clients and on /public/v1/clients/{id}/addresses
LeadsPOST /public/v1/leads and GET /public/v1/leads/{id}: form capture, with the contact task
QuotesGET, POST, PATCH and DELETE on /public/v1/quotes, plus POST on /{id}/send, /{id}/approve, /{id}/reject, /{id}/cancel and /{id}/convert_to_sale
Service ordersGET, POST, PATCH and DELETE on /public/v1/service_orders, plus POST on /{id}/status, /{id}/start, /{id}/complete and /{id}/cancel, POST /public/v1/service_orders/from_quote to turn an approved quote into a service order, and GET /public/v1/service_order_types with the organization service order types
TasksGET, POST, PATCH and DELETE on /public/v1/tasks, plus POST /public/v1/tasks/{id}/status
TeamGET /public/v1/team: who works at the company, in a single list, to own the contact task
Products and stockGET, POST, PATCH and DELETE on /public/v1/products, on /public/v1/products/{id}/variations and on /public/v1/product_categories, plus the balance in GET /public/v1/products/{id}/stock and the ledger in GET /public/v1/stock_movements (stock is read only)
SalesGET, POST and PATCH on /public/v1/sales, plus POST and GET on /{id}/versions, POST on /{id}/cancel, /{id}/payments and /{id}/payments/{payment_id}/cancel, and PATCH /{id}/installments (no delete: a sale is canceled)
ServicesGET, POST, PATCH and DELETE on /public/v1/services and on /public/v1/service_categories
FinanceGET, POST, PATCH and DELETE on /public/v1/financial_transactions, /public/v1/financial_accounts and /public/v1/financial_categories, plus POST /public/v1/financial_transactions/{id}/mark_paid, /transfer, /recurring and /installments
WebhooksGET, POST, PATCH and DELETE on /public/v1/webhook_endpoints, plus POST on /{id}/rotate_secret and /{id}/ping, the delivery log on GET /{id}/deliveries and the retry on POST /{id}/deliveries/{delivery_id}/retry, owner and admins only; and the events of the last 30 days on GET /public/v1/events and /public/v1/events/{id}, cut to the modules the key can read

Webhook delivery is described in Webhooks: the delivery format, the signature, retries and the event catalog. While an area is not in the Reference, it does not answer: the Reference is generated from the API code, so it never lists a route that does not exist.

Capturing a lead does not overwrite a record that already exists: if the phone number or the email is already there, the API reuses the record, answers deduplicated: true and changes neither its data nor its status.

The team list carries only the name, the type and whether the person is active. Contact details, hourly cost, commission and portal access code never leave the API.

Tasks follow the same rules as the dashboard: a task starts as todo, changes status through POST /public/v1/tasks/{id}/status (blocking requires the reason), and notify_via_whatsapp notifies the owner on WhatsApp when the task is created. Deleting a task deletes it for good, and takes with it the history, the comments, the attachments, the dependencies and, when the task is the first of a repeating series, the other occurrences.

Internal tasks of the Facilities module do not show up in this area and cannot be changed by it, neither in the list nor by identifier: to the API they answer the same 404 as a task that does not exist.

Appointments follow the same rules as the dashboard: they count against the monthly plan quota, they accept a rough time of day (morning, afternoon, evening, all_day) that moves the start to that window in the organization time zone, and the chosen address has to belong to the client of the appointment. Whoever connected Google Calendar sees the appointment show up, change and leave along with it: creating mirrors the event, updating updates it, cancelling and deleting remove it. The WhatsApp reminder the organization sets up in the automations covers an appointment booked through the API exactly as it covers one booked on screen, and it treats whoever booked it as one of the recipients.

Here the API is deliberately different from the dashboard: the appointment PATCH does not accept status. On screen the update writes the status without checking the transition, which lets a cancelled appointment come back to life or jump straight to completed; the API does not copy that. Sending status on the PATCH answers 422 naming the field, and the status change happens on POST /public/v1/appointments/{id}/status, which validates the transition: from scheduled and confirmed you can go to any other, completed only goes back to confirmed, and cancelled and no_show are final. Anything else answers 409 conflict. This divergence is deliberate, and it is not the only one in this phase: the Facilities module internal task answers 404 here, and assignee_type is required together with assignee_id on the task, where the dashboard guesses the kind of person. Wherever the API diverges from the dashboard on purpose, the Reference says so on the field or on the operation.

Deleting an appointment deletes it for good, and only the owner and the admins of the organization can. A service order that pointed at it stays, without the link. The Google Calendar event identifier and link, and the flag saying the reminder was already sent, never leave in the response.

Service orders follow the same rules as the dashboard: they count against the plan quotas, get the organization number, always have the total computed (subtotal minus discount plus extra charges), require client, technicians, type and address to belong to the organization, and enforce through the API the fields the organization marked as required in the service order settings. When the organization requires a type, the values accepted in type_id are in GET /public/v1/service_order_types, which also says, in required_on_create, whether the type is required. The status (status) is what drives the rules; the board column in the dashboard is presentation and is not in v1. As in the dashboard, there is no forbidden transition: any status can go to any other through POST /public/v1/service_orders/{id}/status, and start, complete and cancel do what the dashboard buttons do. The lead technician is technician_id, and the support team is support_technicians; technician_id on the list returns the orders where the technician is the lead or is on the team.

The three side effects of a service order are the dashboard ones, through the same code: send_to_technician on create notifies the technician and the team on WhatsApp, and only when the order has a lead technician (technician_id filled): without a technician nobody is notified; create_appointment on create (with scheduled_start) creates the calendar appointment and mirrors it into Google Calendar; and POST /public/v1/service_orders/{id}/complete with link_financial_transaction posts the income to finance. Creating an appointment and posting to finance write to another module, so, when the appointment is actually created or the posting is requested, the key also needs appointments:create and finance:create, respectively; without them the answer is 403 permission_missing and nothing is written.

A completed or cancelled order cannot be edited, as in the dashboard: the PATCH answers 409 conflict. For the same reason, start, complete and cancel answer 409 on a completed or cancelled order, and repeating the completion does not create another income entry; to reopen, use POST /public/v1/service_orders/{id}/status. An older order linked to a client of another organization does not post income when completed (422). Deleting an order archives it, which is what the dashboard does: the order is not removed from the database, but it answers 404 on every route and leaves every list. Only the owner and the admins can. On an older order linked to a client of another organization, no data of that client leaves: name, email, phone and address come as null. The public document link, the draft WhatsApp assembles, the technician location, the labor cost and the technician phone never leave in the response. Two differences from the dashboard, both in favor of the integrator: an archived order also answers 404 on the lookup by identifier, and a PATCH without priority keeps the priority the order had.

A quote follows the same rules as the dashboard: it counts toward the plan quotas, gets the organization number, has the subtotal, the total and the monthly amount always computed from the items (an optional item only counts when included, and the monthly amount, which is the items in the recurring_monthly section, never adds to the total), derives the type from the items and uses the organization default terms and payment conditions when the field is left out. Client and seller have to belong to the organization. The list is the dashboard pipeline: quote templates, old versions and quotes absorbed in a merge do not show up in it; the last two can still be read by identifier, and a template answers 404. Building blocks, merging quotes, versioning and the cost mirror stay in the dashboard only.

The quote actions do what the dashboard does, through the same code, and as in the dashboard no transition is forbidden. send marks the quote as sent and activates the portal of the linked client; it does not send a message or an email and does not create a public link, because in the dashboard the message and the portal access code are separate steps. Sending through the API only marks the quote as sent: the client portal access code is generated and delivered by the dashboard, and without it the client cannot open the portal. approve is the dashboard status picker: it records the approval in the history with the name of who approved, approves every block, fires the quote approved automations and, when the organization turned on the automatic contract and the quote has a monthly amount, creates the contract, only once. reject requires the reason. convert_to_sale creates the sale with items, payment plan, finance entries and stock decrease, as the dashboard conversion window does, and POST /public/v1/service_orders/from_quote creates the service order from an approved quote, as the “Convert to service order” window does, with the same technician notice and the same appointment as creating an order; the key also needs quotes:read, and an older quote linked to a client of another organization is refused with 422. Converting to a sale writes to other modules: the key needs sales:create, plus finance:create when the request asks for the finance posting (track_in_finance: true or an account); the stock decrease does not ask for products:update, because decreasing stock is part of selling. Without track_in_finance, the sale goes to finance through the company setting, and that does not require finance:create (see Permissions).

An approved or cancelled quote cannot be edited, as in the dashboard: the PATCH answers 409 conflict. A quote absorbed in a merge can still be read, but takes no edit, action, deletion or conversion (409): the parent quote is what counts, and the dashboard screen locks it too. Both conversions charge the plan quotas, as creating a service order or a sale directly does. On update, items replaces the list, and an item that already existed keeps the cost frozen when it was quoted, as in the dashboard. Deleting a quote deletes it for good, and only the owner and the admins can; a quote that already became an active sale cannot be deleted (409) until the sale is cancelled. The same document (or the same block) does not become two active sales, and the quote does not become two service orders: the second conversion answers 409. The item unit cost, the pricing breakdown and the document public link never come out in the response, and on an older quote linked to a client of another organization no data of that client comes out.

The catalog follows the same rules as the dashboard: products and services count toward the plan quotas, the internal code (sku) does not repeat within the company, the category must belong to the same company (any other answers 422 with param category_id), and the product category tree has two levels. Every write to a product, variation, category or service revalidates the online store, as when saving from the screen. Replacement cost, average cost, material cost and the cost of stock movements never come out in a response, and they are not input fields either.

Stock is read only. GET /public/v1/products/{id}/stock returns the balance of the product and of each variation, the minimum stock and the unit, and GET /public/v1/stock_movements returns the ledger: every receipt, exit and adjustment, with the balance after the movement. The balance changes through creation (opening balance), sales, goods receipts and the stock of the product or variation PATCH. As in the dashboard, that stock is the absolute balance (“set it to 40”): the difference from the current balance becomes a manual adjustment (manual_adjustment) in the ledger, signed by the member the key acts as.

Deleting a product deletes it for good, as the dashboard does, and takes its variations, the whole stock ledger of the product and of its variations, and the supplier links with it. Goods receipts stay, with the item description and without the link, and quotes, service orders and sales keep the item as text and do not change. Deleting a variation takes its ledger with it. A category with products or services cannot be deleted (409). Deleting a service also deletes it for good.

A company with more than one store in the same subscription can share products between them in the dashboard. Each store has its own copy of the product, and a key of one store only sees and only changes that store’s copy: another store’s copy answers 404 on every route. is_shared and is_share_master say whether the product is shared and whether this copy is the master record. Editing the master through the API carries the synced fields to the copies in the other stores, exactly as in the dashboard, and revalidates each one’s online store; editing a copy changes only its own store. The balance never travels between stores. Deleting the master does not delete the copies: the oldest one becomes the master.

The PATCH of a product, variation, category and service changes only the fields you send: a field that is not sent stays exactly as it is, including price, balance, description and the service status. Some checks that the dashboard runs in its form apply here on the server and answer 422 naming the field: the video must be a YouTube address and only shows with the address filled in; the custom button label and the “replace the default buttons” option only apply with the button address; images (image_url) must be a full http or https address; and the service lc116_code must be an item of the LC 116 service list the dashboard offers, because it goes straight into the invoice issuing.

A sale follows the same rules as the dashboard, through the same code: it counts in the plan quota, gets the company number, builds the payment plan, records the payments, posts to finance and decreases the stock of catalog products, like the screen. Subtotal and total are computed from the items exactly as the form computes them, with a single discount mode (discount_cents or discount_percentage, from 0 to 100). A catalog item must be a product of the company, with the variation when the product has variations, and the name and promotional price come from the catalog; a free item takes the name you send. A credit sale (unpaid) needs a client. The sale, the version and the payment made through the API are marked with created_channel equal to public_api, including the sale created by convert_to_sale; what is done in the dashboard stays panel.

The sale id is one, for good. In the dashboard, every commercial change (items, amounts, client, seller or balance plan) creates a new version of the sale; through the API that change is POST /public/v1/sales/{id}/versions, with a reason, and the sale keeps answering by the same id, with a higher version. In a new version, a catalog item already on the sale keeps its recorded name and promotion, as on the screen; only a new item comes from the catalog. GET /public/v1/sales/{id}/versions returns the history. The list shows each sale once, in the version in effect today, and the sale_id of the stock ledger and of the converted quote is this same id. PATCH only changes notes and production; a commercial field in PATCH answers 422 naming the field, because a commercial change has its own route. There is no delete: a sale is canceled, and canceling reverses the payments, returns the decreased stock and frees the originating quote or service order to be converted again.

The sale locks are the dashboard’s, action by action, including what only the screen prevents in the dashboard:

Sale situationAnswers 409Keeps working
Cancelededit, new version, cancel again, receive, reverse, rescheduleread
With an active invoice (on the sale, its origin or a payment), invoice_locked: truenew version and cancel; reversing the payment that has the invoice (has_active_invoice: true)editing notes and production, receiving, rescheduling, reversing the other payments
With the seller commission already paidnew versioncancel, receive, reschedule, edit notes and production
From a contract (receivable_managed_by: contract)receive, reschedule, reversenew version, cancel, edit notes and production

Also like the screen: receive only on an open installment (installment_id of a settled installment answers 422), reschedule only with an open installment (409), reverse only a payment that was not reversed (409), a new version only when something commercial changes (422), and the production fields only with production turned on (422).

Account and finance entries are finance data. account_id (of the sale and of each payment) and finance_transactions only come in the response when the key has finance:read and the member it represents has access to finance (owners and admins do). Without that, these fields do not come, exactly as in the dashboard. Installments (installments) and payments (payments) always come, and a member without finance receives, reverses and reschedules as they do on the screen; they just cannot choose the account or decide whether the sale goes to finance (403). The seller commission, whether the sale decreased stock, the internal allocation of payments to installments, user ids and internal version ids never come out. On an old sale linked to a client, seller, account or product of another company, that link comes out null.

Finance follows the same rules as the dashboard, through the same code: an entry checks account, category and client, an expense on a credit card account goes to the cycle statement (on_statement), and overdue is recomputed when the entry is written. As on the screen form, the category must have the same type as the entry, “show in the client portal” only applies to an expense with a client, the payment date only exists on a paid entry (without it, the entry date), and without account_id the entry goes to the company default account, which the screen already selects; account_id: null is “no account”. Recurrence and installments create the whole series at once, with the same limits as the screen, and a transfer creates the outflow and the inflow linked to each other. An entry made through the API gets created_channel equal to public_api.

On a series, PATCH and DELETE accept ?scope=this, this_and_future or all, like the dashboard series window, and DELETE also accepts keep_paid (the default is true, like the window, which comes checked). PATCH changes only the fields sent: what does not come, status included, stays as it is. An entry created by a sale (source: sale) only changes category, notes and portal, and is not deleted; sale_id is the same id as GET /public/v1/sales/{id}. An entry imported from the bank (source: bank) also has the fields the screen locks and is not deleted. Changing a protected field answers 409 conflict: it is a conflict with the entry state, not a missing permission. mark_paid settles the open entry, and on an entry already paid it returns it as it is, with no error; on a sale installment it records the sale payment, and the response is the new received entry; the installment is canceled, so repeating on the same id answers 409, without charging twice. To retry safely after a timeout, send Idempotency-Key.

A delete never removes more than what was asked. The entries of a series hang on its FIRST entry, so deleting that first one with scope=this would take the whole series, and deleting with keep_paid when that first one is not paid would also take the paid ones. In those two cases the API answers 409 conflict with param: "scope" and deletes nothing: delete with scope=all (and keep_paid=false, to delete the paid ones too) or from another entry of the series.

Every finance read (entries, accounts and categories) schedules, like the screen, the sync of older sales into finance, so what you read is what the dashboard shows. Accounts carry the current balance computed like the screen (current_balance_cents) and, on a credit card, the used and available limit; limit, closing day, due day and alert only exist on credit card and supplier credit. An account with entries is not deleted (409), and only owners and admins delete accounts. Only owners and admins create, edit and delete categories, and the color is one of the 18 of the screen palette (any other answers 422 with param color); the first read of a company with no category creates the default ones, like the dashboard. Open Finance data (external identifiers, synced balance, bank connection), reconciliation and attachment data never come out.

Deleting a client deletes the record for good, as the dashboard does, and takes addresses, notes, group links, portal access, inventory and construction projects with it. Service orders, sales, quotes, tasks and financial entries stay, without the client.

The permissions table in this section already shows the full vocabulary of modules and actions, because that vocabulary is part of the contract and is what the panel uses to create keys. Having a permission ticked on the key does not mean the route of that module already exists.

  • There is no test environment. Every call uses your company’s real data.
  • A key does not manage keys. Creating, rotating and revoking happen in the panel, with the user’s password.
  • Cost and margin are not exposed in the API.
  • The API does not configure the company. Plan, members and preferences stay in the panel.

The API is available from the Start plan on. On the free plan the company does not create keys, and a call made with a key of a company that lost the feature answers 403 with the code plan_feature_unavailable.

The per-minute call budget belongs to the company, adding up all of its keys. GET /public/v1/me returns the current budget in limits.requests_per_minute.

This documentation exists in Portuguese, English and Spanish. Only the text changes. Field names, route paths, enum values and error codes are the same in all three languages.

In the API, the Accept-Language header picks the language of the error message (pt-BR is the default, en and es are also accepted). The error code never changes with the language: write your code against it, never against the text.