API & Integrations

Everything in GarmentFlow is reachable over HTTP. The Desk, the floor apps and the dashboards all talk to the same endpoints you can call — there is no private interface they use and you don't.

That means you can read production data into Power BI, push an order in from your webshop, or build your own phone app, without anyone writing a special integration for you.

This page starts from nothing and works up to a working mobile app.

The two kinds of endpoint

Resource API — one endpoint per DocType, generated automatically. Create, read, update and delete records.

/api/resource/<DocType>

Method API — one endpoint per whitelisted server function. Returns whatever that function returns, usually something already assembled for a screen.

/api/method/<full.dotted.path.to.function>

Rule of thumb: resource to read or write records; method when GarmentFlow already computes what you want.

Step 1 — Create an integration user

Do not use your own login. Create a user that exists only for the integration, so you can see what it did and switch it off without locking anyone out.

  1. Awesome Bar (Cmd/Ctrl + K) → user+ Add User.
  2. Email integration.powerbi@yourfactory.com, first name Power BI Integration.
  3. User Type: System User.
  4. Under Roles, give it only what it needs. For a read-only reporting user, that is typically Manufacturing User — not System Manager.
  5. Save.
Important

The API enforces exactly the same permissions as the interface. A user who cannot open a Production Order in Desk gets a 403 for it over the API. This is your security boundary — get the roles right and the integration cannot exceed them, whatever the code does.

Step 2 — Generate API keys

  1. Open the user.
  2. Scroll to Settings → API Access.
  3. Click Generate Keys.
  4. The API Secret is shown once. Copy it now.
  5. The API Key stays visible on the user record.

You now have a pair:

API Key:    a1b2c3d4e5f6g7h
API Secret: s9t8u7v6w5x4y3z

Every request carries them in a header:

Authorization: token a1b2c3d4e5f6g7h:s9t8u7v6w5x4y3z

Note the format: the word token, a space, then key and secret separated by a colon.

Caution

Never put an API secret in a mobile app or a web page. Anyone can extract it. A phone app should log its user in and use a session, or talk to a small backend of yours that holds the secret. See Securing what you build.

Step 3 — Make your first call

Test with curl before writing any code — it isolates whether the problem is the API or your program:

curl -X GET \
  'https://your-site.com/api/resource/Production%20Order?limit_page_length=5' \
  -H 'Authorization: token a1b2c3d4e5f6g7h:s9t8u7v6w5x4y3z'

A successful response:

{
  "data": [
    { "name": "PO-2026-0042" },
    { "name": "PO-2026-0041" }
  ]
}

Note %20 for the space in Production Order. Most HTTP libraries do this for you.

If this works and your program doesn't, the difference is in your program. If this fails, fix it here first.

Reading records

A list

GET /api/resource/Production Order

By default you get names only. Ask for fields:

GET /api/resource/Production Order?fields=["name","status","end_date","total_planned_qty"]

Filtering

Filters are JSON, a list of [fieldname, operator, value]:

?filters=[["status","=","In Progress"]]
?filters=[["status","in",["Released","In Progress"]],["end_date","<","2026-09-01"]]

Operators: =, !=, >, <, >=, <=, in, not in, like, between, is (with set / not set).

Sorting and paging

?order_by=end_date asc
?limit_start=0&limit_page_length=50

limit_page_length=0 returns everything — avoid it on large tables. Page instead: limit_start=0, then 50, then 100.

A complete read:

GET /api/resource/Production Order
    ?fields=["name","status","end_date","total_planned_qty"]
    &filters=[["status","in",["Released","In Progress"]]]
    &order_by=end_date asc
    &limit_page_length=50

One record, in full

GET /api/resource/Production Order/PO-2026-0042

This returns every field including child tables — the operation plan, the items, the materials summary. The list endpoint never returns child tables; fetch the document when you need them.

Writing records

Create

curl -X POST 'https://your-site.com/api/resource/Fabric Test Certificate' \
  -H 'Authorization: token KEY:SECRET' \
  -H 'Content-Type: application/json' \
  -d '{
        "tech_pack": "TP-0031",
        "test_date": "2026-08-20",
        "laboratory": "Textil Lab SA",
        "result": "Pass"
      }'

The response is the created document, including its generated name.

Update

curl -X PUT 'https://your-site.com/api/resource/Fabric Test Certificate/FTC-2026-0007' \
  -H 'Authorization: token KEY:SECRET' \
  -H 'Content-Type: application/json' \
  -d '{"result": "Fail"}'

Send only the fields you are changing.

Submit and cancel

These are not field edits — they are transitions. Set docstatus:

curl -X PUT 'https://your-site.com/api/resource/Production Order/PO-2026-0042' \
  -H 'Authorization: token KEY:SECRET' \
  -H 'Content-Type: application/json' \
  -d '{"docstatus": 1}'

1 submits, 2 cancels. All the normal validation runs — a document that would fail in the interface fails here, with the same message.

Delete

curl -X DELETE 'https://your-site.com/api/resource/Fabric Test Certificate/FTC-2026-0007' \
  -H 'Authorization: token KEY:SECRET'

Calling methods

For data GarmentFlow already assembles, a method call beats reconstructing it from records.

curl -X GET \
  'https://your-site.com/api/method/garments_manufacturing.garments_manufacturing.api.production_studio.get_production_overview' \
  -H 'Authorization: token KEY:SECRET'

The result always arrives under message:

{ "message": { "batch_board": [...], "open_orders": 14 } }

Some useful ones:

Method Returns
garments_manufacturing.garments_manufacturing.api.production_studio.get_production_overview Batch counts by status, open orders, headline KPIs
garments_manufacturing.garments_manufacturing.api.maintenance_studio.get_maintenance_overview Machine health, work-center rollup, upcoming maintenance
garments_manufacturing.garments_manufacturing.page.floor_monitor.floor_monitor.get_floor_data Every work center with its health and fleet KPIs
garments_manufacturing.api.packing.get_packing_queue Orders at the packing operation with counters

Arguments go as query parameters for GET, or a JSON body for POST:

/api/method/garments_manufacturing.api.packing.get_packing_queue?production_unit=LINE-01
Note

These are internal endpoints, documented here because they are useful. They can change between versions. For an integration that must not break, prefer the resource API on documented DocTypes, or write your own API server script — an endpoint you control, that returns exactly what you need.

Errors

Status Meaning Usual fix
200 Fine
400 Bad request Malformed JSON, or a filter that isn't valid JSON
401 Not authenticated Header missing or malformed; check token key:secret
403 Authenticated, not allowed The user lacks the role
404 Not found Wrong DocType name or record name — check spelling and case
417 Validation failed Your data broke a rule; the message says which
500 Server error Check the site's Error Log

Error bodies contain a message and often a server traceback. Log the whole body while developing — the answer is usually in it.

Webhooks — GarmentFlow calling you

Everything above is you calling GarmentFlow. A Webhook is the reverse: GarmentFlow posts to your URL when something happens, so you don't have to poll.

  1. Awesome Bar → webhook+ Add Webhook.
  2. Document Type: Production Order.
  3. Doc Event: on_submit (also available: after_insert, on_update, on_cancel, on_trash).
  4. Request URL: your endpoint, e.g. https://api.yourcompany.com/hooks/gf-order.
  5. Request Method: POST.
  6. Condition (optional): doc.total_planned_qty > 1000.
  7. Webhook Headers: add your own auth header so your endpoint can verify the caller.
  8. Webhook Data: tick the fields to send, or write a JSON template.
  9. Save.

Two things to build for: your endpoint should respond quickly and do its work afterwards, and it should be idempotent — a retry may deliver the same event twice.

Check Webhook Request Log to see what was sent and what came back.

Building your own app

A phone app for something GarmentFlow's own apps don't cover — a customer order-status app, a supplier drop-off screen, a plant-manager dashboard.

Whatever you build it in, the shape is the same: log in or authenticate, call /api/resource or /api/method, render the JSON.

Option A — FlutterFlow (no code)

FlutterFlow builds iOS and Android apps visually. It is the fastest route if you don't have a mobile developer.

  1. Create the project. New project, blank app.
  2. Add the API call. Left sidebar → API Calls+ Add API Call. - API Call Name: GetOpenOrders - Method Type: GET - API URL: https://your-site.com/api/resource/Production Order - Headers: Authorizationtoken KEY:SECRET, and Acceptapplication/json - Query Parameters:
    • fields = ["name","status","end_date","total_planned_qty"]
    • filters = [["status","in",["Released","In Progress"]]]
  3. Test it. Press Test API Call. You should see the JSON. FlutterFlow shows the response tree — this is where you confirm the path to your data is $.data.
  4. Create the JSON paths. In the response panel, name the fields you want: $.data[:].name, $.data[:].status, $.data[:].end_date.
  5. Build the screen. Add a ListView, then a Container inside it. Select the ListView → Generate Dynamic ChildrenBackend Query → your API call → the $.data path.
  6. Bind the text. Drop Text widgets in the container and bind each to a JSON path from the item variable.
  7. Run it in Test Mode, then publish to the stores.

Filter and search by adding query parameters bound to a text field, and re-running the call.

Caution

The key-and-secret approach above is fine for an internal app on managed devices. For anything that leaves your control, use the login flow — call /api/method/login with usr and pwd, keep the session cookie, and drop the static secret.

Option B — Android Studio (Kotlin)

For a native Android app.

Add the dependency in build.gradle:

implementation("com.squareup.retrofit2:retrofit:2.11.0")
implementation("com.squareup.retrofit2:converter-gson:2.11.0")

Define the response and the endpoint:

data class Order(
    val name: String,
    val status: String?,
    val end_date: String?,
    val total_planned_qty: Double?
)

data class OrderResponse(val data: List<Order>)

interface GarmentFlowApi {
    @GET("api/resource/Production Order")
    suspend fun getOrders(
        @Query("fields") fields: String =
            """["name","status","end_date","total_planned_qty"]""",
        @Query("filters") filters: String =
            """[["status","in",["Released","In Progress"]]]""",
        @Query("limit_page_length") limit: Int = 50
    ): OrderResponse
}

Build the client, attaching the auth header to every request:

val client = OkHttpClient.Builder()
    .addInterceptor { chain ->
        val request = chain.request().newBuilder()
            .addHeader("Authorization", "token $API_KEY:$API_SECRET")
            .addHeader("Accept", "application/json")
            .build()
        chain.proceed(request)
    }
    .build()

val api = Retrofit.Builder()
    .baseUrl("https://your-site.com/")
    .client(client)
    .addConverterFactory(GsonConverterFactory.create())
    .build()
    .create(GarmentFlowApi::class.java)

Call it from a coroutine:

lifecycleScope.launch {
    try {
        val orders = api.getOrders().data
        adapter.submitList(orders)
    } catch (e: Exception) {
        Log.e("GarmentFlow", "Fetch failed", e)
    }
}

Keep the credentials out of source control and out of the APK — read them from an authenticated login, or from your own backend.

Option C — Xcode (Swift)

For iOS, with no third-party library:

struct Order: Codable {
    let name: String
    let status: String?
    let end_date: String?
    let total_planned_qty: Double?
}

struct OrderResponse: Codable {
    let data: [Order]
}

func fetchOrders() async throws -> [Order] {
    var components = URLComponents(string: "https://your-site.com/api/resource/Production Order")!
    components.queryItems = [
        URLQueryItem(name: "fields",
                     value: #"["name","status","end_date","total_planned_qty"]"#),
        URLQueryItem(name: "filters",
                     value: #"[["status","in",["Released","In Progress"]]]"#),
        URLQueryItem(name: "limit_page_length", value: "50")
    ]

    var request = URLRequest(url: components.url!)
    request.setValue("token \(apiKey):\(apiSecret)", forHTTPHeaderField: "Authorization")
    request.setValue("application/json", forHTTPHeaderField: "Accept")

    let (data, response) = try await URLSession.shared.data(for: request)

    guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
        throw URLError(.badServerResponse)
    }

    return try JSONDecoder().decode(OrderResponse.self, from: data).data
}

Display it in SwiftUI:

struct OrdersView: View {
    @State private var orders: [Order] = []

    var body: some View {
        List(orders, id: \.name) { order in
            VStack(alignment: .leading) {
                Text(order.name).bold()
                Text(order.status ?? "—").font(.caption)
            }
        }
        .task {
            orders = (try? await fetchOrders()) ?? []
        }
    }
}

Store credentials in the Keychain, never in the source.

Power BI

For statistical analysis, forecasting and board reporting, pull GarmentFlow data straight into Power BI.

Connecting

  1. Power BI Desktop → Get DataWebAdvanced.
  2. URL parts: https://your-site.com/api/resource/Production Order?fields=["name","status","end_date","total_planned_qty"]&limit_page_length=0
  3. HTTP request header parameters: Authorizationtoken KEY:SECRET
  4. OK, then choose Anonymous for authentication — your credentials are already in the header.
  5. Power BI opens the Query Editor with a JSON record. Expand data into a list, then To Table, then expand the columns.

As an M query

Paste this into the Advanced Editor instead:

let
    Source = Json.Document(
        Web.Contents(
            "https://your-site.com",
            [
                RelativePath = "api/resource/Production Order",
                Query = [
                    fields = "[""name"",""status"",""end_date"",""total_planned_qty""]",
                    limit_page_length = "0"
                ],
                Headers = [
                    Authorization = "token KEY:SECRET",
                    Accept = "application/json"
                ]
            ]
        )
    ),
    data = Source[data],
    Table = Table.FromList(data, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    Expanded = Table.ExpandRecordColumn(Table, "Column1",
        {"name", "status", "end_date", "total_planned_qty"},
        {"Order", "Status", "Due Date", "Planned Qty"}),
    Typed = Table.TransformColumnTypes(Expanded, {
        {"Due Date", type date},
        {"Planned Qty", type number}
    })
in
    Typed

Using RelativePath and Query rather than one concatenated URL is what allows scheduled refresh in the Power BI Service — a hard-coded full URL often won't refresh.

Practical notes

  • Pull the tables you need and model in Power BI. Production Order, Production Batch, Operation Ledger Entry and Batch Card as separate queries, related on their key columns, gives you far more than one flattened query.
  • limit_page_length=0 returns everything. Fine for masters and a few thousand orders; for the operation ledger, filter by date and page.
  • Use a Query Report for heavy aggregation. If the numbers need work, build a Query Report and let the database do it; Power BI then imports a small, ready result.
  • Give the Power BI user a read-only role. It never needs write.
  • Schedule refresh in the Power BI Service; overnight is usually enough, and it avoids loading the site during production hours.

Excel and Google Sheets

Same data, less setup:

  • Excel — Data → From Web, with the same URL and header.
  • Google Sheets — an Apps Script UrlFetchApp.fetch() with the header, writing into a sheet.
  • Anything — a report exported to CSV, or emailed on a schedule with Auto Email Report.

Securing what you build

  • One user per integration, with the least roles that work. You can then see in the change history exactly what each integration did.
  • Never embed a secret in anything a user can download. A public app needs either a user login or a backend of yours holding the secret.
  • Rotate keys when someone leaves or a laptop is lost — regenerating on the user immediately invalidates the old pair.
  • Prefer webhooks to polling. Polling every minute is thousands of pointless requests a day.
  • Test against a staging site. An integration that writes records should prove itself somewhere that isn't your live floor.
  • Handle failure. The network will drop. Retry with a delay, and make writes safe to repeat.

Troubleshooting

Symptom Cause
401 on every call Header not token key:secret, or the secret was regenerated
403 on some DocTypes The integration user lacks that role
Empty data with 200 Filters match nothing, or a permission query is restricting the user
Child tables missing You used the list endpoint. Fetch the single document instead
filters ignored Not valid JSON, or not URL-encoded
Works in curl, not in code Your library isn't sending the header, or it re-encoded the URL. Log the request it actually sent
Power BI won't refresh in the service URL built by concatenation — use RelativePath and Query

What to do next

If you need an endpoint shaped exactly for your integration, write one: API server scripts. If you need it to hold data GarmentFlow doesn't, start at Creating DocTypes.

WhatsApp