Skip to content

Create service orders from another system

A ticket comes into your help desk, your monitoring center or your ERP, and it needs to become a service order for the field team. This guide opens the order through the API and shows how to follow what happens to it afterwards.

An order created through the API is the same as one created in the dashboard: it counts against the plan quotas, gets the company’s number, has its total calculated and applies the automatic checklists the company configured.

PermissionWhat for
service_orders:readRead the order types and follow the order
service_orders:createCreate the order
service_orders:updateChange status, start, complete and cancel
team:readOptional. Find the technician in charge
appointments:createOnly if the order will create the calendar appointment (create_appointment)
finance:createOnly if completing will post the revenue (link_financial_transaction)
quotes:readOnly to convert a quote into an order (from_quote). The member also needs access to quotes

Before the first order: what the company requires

Section titled “Before the first order: what the company requires”

Three reads prevent almost every 422 before it happens. Do them once and keep the result; they rarely change.

  • Order types. GET /public/v1/service_order_types lists the registered types (CCTV, alarm, gate). Each one’s id is the value for type_id. When required_on_create is true, the company requires a type, and creating without type_id returns 422. A type with active: false was removed from the dashboard picker.
  • Technicians. GET /public/v1/team returns the team. Technicians come with type: "technician", and their id goes in technician_id and in support_technician_ids.
  • Client. The order can point to a record (client_id), and then it must belong to the company. If you do not link clients between the two systems yet, see Sync clients with a CRM or ERP.

The company can also mark fields as required when opening an order, in the dashboard’s service order settings. The API enforces the same fields, and whatever is missing comes back in errors on the 422.

Terminal window
# 1. The company's order types (keep them; they rarely change)
curl "https://api.fatureihoje.com/public/v1/service_order_types?limit=100" \
-H "Authorization: Bearer $FH_API_KEY"
# 2. Create the order. The idempotency key comes from the ticket number.
curl -X POST "https://api.fatureihoje.com/public/v1/service_orders" \
-H "Authorization: Bearer $FH_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ticket-4821" \
-d '{
"client_id": "0d9a7c5e-3f1b-4d2a-8c6e-4a2b0c8e6f1d",
"client_name": "Marcenaria Souza",
"client_phone": "+5511988887777",
"client_description": "The electric gate does not close all the way.",
"service_address": "Rua das Flores, 120, São Paulo, SP",
"type_id": "7b1e4c2a-9d3f-4a6b-8c5e-1f2a3b4c5d6e",
"scheduled_start": "2026-10-05T12:00:00Z",
"scheduled_end": "2026-10-05T14:00:00Z",
"priority": "high",
"internal_notes": "Ticket 4821 from the help desk",
"items": [
{
"description": "Technical visit",
"quantity": 1,
"unit_price_cents": 15000,
"total_cents": 15000
}
]
}'

What is worth knowing about the fields (the full list is in the Reference):

  • client_name is required even with client_id: it is the name as printed on the order, and it stays there if the record is deleted.
  • With scheduled_start the order starts as scheduled; without it, pending.
  • Money goes in cents. The dashboard stores each item’s total_cents as you sent it, without recalculating from the quantity, and the order subtotal is their sum. See Dates and money.
  • The order has no field for your system’s id. Store the order id on your ticket and, if you want the team to see the ticket number, put it in internal_notes, which does not appear on the client’s document.

In the example, the Idempotency-Key is ticket-4821. So if your system sends the same ticket twice (a queue retry, an operator clicking again), the second call returns the order from the first one instead of opening another.

Two things to keep in mind:

  • The response is kept for 24 hours. After that, the same key creates again. So store the order id on the ticket as soon as it comes back, and check that field before creating.
  • The same key with a different body returns 422 idempotency_key_reused. If the ticket changed before the order was created, it is a different operation.

See Idempotency.

They are the same as in the dashboard form, and off by default:

  • send_to_technician: true notifies the technician in charge and the support team over WhatsApp after creating. Without technician_id, nobody is notified.
  • create_appointment: true, together with scheduled_start, creates the calendar appointment and mirrors it to the Google Calendar of whoever connected one. It needs appointments:create on the key.

The order moves through the same actions as the dashboard buttons:

ActionRoute
Change status (any to any)POST /public/v1/service_orders/{id}/status
StartPOST /public/v1/service_orders/{id}/start
Complete, optionally posting the revenuePOST /public/v1/service_orders/{id}/complete
CancelPOST /public/v1/service_orders/{id}/cancel
EditPATCH /public/v1/service_orders/{id}

A completed or canceled order does not accept edit, start, complete or cancel: the response is 409 conflict. To reopen it, change the status through the status route. Completing respects the required checklist and the technician’s completion order, and returns 409 when something is missing.

To know when the technician started or finished, do not keep polling the order. Subscribe an endpoint to service_order.status_changed and service_order.updated (see Create an endpoint), and use incremental sync as a safety net.

If your flow starts with a Faturei Hoje quote, do not recreate the items by hand: POST /public/v1/service_orders/from_quote converts an approved quote into an order, through the same conversion as the dashboard dialog. It needs quotes:read on the key and access to quotes for the key’s member.

ResponseWhat to do
422 validation_failedAn invalid field, or a field the company requires is missing. The field is in errors
404 resource_not_foundA client or technician that does not exist or does not belong to the company
403 plan_limit_reachedThe plan’s order quota is used up. The company sorts it out in the dashboard
403 permission_missingThe key is missing a permission, such as appointments:create with create_appointment
429 rate_limit_exceededWait for Retry-After and retry. See Rate limits
5xx or network failureRetry with the same Idempotency-Key