Download OpenAPI specification:
Describes the SteelSolution partner integration as it maps to Tukang.com's existing order system. This reference and the call-flow page are the authoritative description of Tukang.com's side; where any other document disagrees, these two are correct. Read the call-flow page first for the journey and the order of calls, then this one for the exact contract.
Version 1.0.0, last updated 2026-08-05. Every revision to either page bumps this number and both pages carry the same one, so a version you can see is a contract you can rely on. Check it before assuming a saved copy is current.
Every request needs X-API-Key and X-Signature headers - see the
Authorization panel on each endpoint below for how to compute
X-Signature.
Every error looks like this:
{ "message": "Vendor not found", "error_code": "VENDOR_NOT_FOUND" }
Clients should branch on error_code. The message field is intended
for human readers and its wording may change.
Each endpoint documents the codes it returns. Two additional codes may be
returned by any endpoint: NOT_FOUND (404) when the path or method does
not exist, and PAYLOAD_TOO_LARGE (413) when the request body exceeds
the size limit below.
Note that a request body which is not valid JSON returns 400
VALIDATION_ERROR even when credentials are absent or incorrect. The
body is parsed before credentials are verified. A 400 where a 401 was
expected therefore indicates a malformed body.
Requests are validated strictly. A field that is not listed in the schema
is rejected with 400 VALIDATION_ERROR rather than ignored, at any
depth. Send only the fields each endpoint declares.
Each field is validated against the type, format and any range shown in its schema below.
preferred_slot on the match endpoint and job_time on the create
endpoint are both validated against the same rule. A time is accepted
when all of these hold:
The first two combine rather than applying separately. The earliest bookable time is whichever is later, three hours from now or the next time the window opens. A customer ordering at 19:01 cannot book at all that day, because three hours later is 22:01, past the window; the earliest is 08:00 the next morning.
Both endpoints apply this, so an unusable time is refused at /match
rather than after the customer has chosen a vendor and paid. A rejected
time returns 400 VALIDATION_ERROR with a message saying which of
the three rules was broken.
Every date-time must carry an explicit UTC offset, for example
2026-09-10T09:00:00+07:00, or Z. A value without one is rejected
with 400 VALIDATION_ERROR.
2026-09-10T09:00:00 has no offset. We would read it as UTC, which is
16:00 in Jakarta. The booking would then be checked against the wrong
time, and the vendor would arrive seven hours late.
Times we send back always carry +07:00.
Request bodies must be smaller than 8 KB. Normal requests are much smaller than this.
Requests exceeding it are rejected by a web application firewall before
they reach this API. The firewall responds with 403 and an HTML body
rather than the JSON error format above, and no record of the request is
retained on our side. The firewall may also reject requests for other
reasons, such as an unrecognised User-Agent.
An HTML response therefore indicates a firewall rejection rather than an API error. Please report the source IP address so that it can be added to the allow list.
The webhook and this API use the same words, so a push and a status check made at the same moment agree.
| Value | Sent on the webhook | Returned by this API |
|---|---|---|
scheduled |
no | yes |
in_progress |
yes | yes |
visit_complete |
yes | yes |
estimate_ready |
yes | yes |
retention_started |
yes | yes |
install_complete |
yes | yes |
closed |
no | yes |
What each one means:
scheduled - created, vendor assigned, work not started.
in_progress - the vendor has started the site visit, or the project
is running, or it is in its retention period.
visit_complete - the vendor finished the job during the site visit
and no cost estimate follows. Final state for those jobs.
estimate_ready - the cost estimate has been sent and is waiting on
the customer's decision.
retention_started - every RAB line item is complete, before final
sign-off.
install_complete - the job is finished.
closed - the customer rejected the cost estimate. The visit itself
completed; the work did not proceed.
There is no push for scheduled, because nothing has happened yet, or
for closed, because you sent us that decision yourself.
One limitation worth designing around. On jobs that need a cost
estimate, our records do not distinguish a finished site visit from one
still under way until the estimate is sent. So between the
visit_complete push and the estimate_ready push, a status check
answers in_progress. Treat the push as the reliable signal for that
window. A status check does return visit_complete for jobs the vendor
completes during the visit, where no estimate follows and it is the
final state.
Each value's meaning is listed with the status field on the endpoints
below.
Tukang.com calls a single webhook on SteelSolution's CMS whenever a site visit's status changes.
The payload carries the site visit id and the webhook status at the top
level, then the full record under site_visit. That record is the same
shape as the GET /site-visits/{id} response below, with fields that do
not apply to the current status set to null.
{
"site_visit_id": "7501130001",
"status": "estimate_ready",
"site_visit": {
"site_visit_id": "7501130001",
"status": "estimate_ready",
"vendor": { "id": "VND0001122", "name": "Budi Konstruksi" },
"job_time": "2026-07-10T09:00:00+07:00",
"rab": { }
}
}
The status at the top level and the one inside site_visit carry the
same value, because both directions use one set of words. The single
exception is the visit_complete push on a job that needs a cost
estimate: the record reads in_progress there, for the reason given
under "Status values" above. On a job the vendor completes during the
visit, both read visit_complete.
Because the whole record travels with the push, you do not need to call
GET /site-visits/{id} after receiving one. That endpoint stays
available for reconciliation and for checking an order at any time.
Webhook status values, in the order they can occur:
in_progress - vendor has started the site visit.
visit_complete - the site visit itself has ended. rab is not yet
populated at this point, whether or not this job will need one.
estimate_ready - fires separately, later: can be hours to days
after visit_complete, not the same call. rab is now populated,
including a materials list (each item carrying the sku from your
product catalogue), a total service amount, and a RAB PDF URL. Only
fires for jobs that need an
estimate; jobs that do not require one stop after visit_complete.
retention_started - every RAB line item has been marked complete
by the vendor, before final install sign-off. This fires before
install_complete below, not at the same time.
install_complete - installation work is fully finished.
Failed outbound pushes are retried automatically with backoff, so your receiver must be idempotent. If you miss a push, the status check endpoint below can be called at any time and returns the current state.
Returns up to 5 nearby vendors with profile, reviews, and site-visit cost, pre-filtered to vendors available at the requested slot.
required | object |
| preferred_slot required | string <date-time> |
{- "location": {
- "lat": -6.2088,
- "lng": 106.8456
}, - "preferred_slot": "2026-07-10T09:00:00+07:00"
}{- "message": "",
- "result": [
- {
- "vendor_id": "VND0001122",
- "name": "Budi Konstruksi",
- "rating": 4.8,
- "review_count": 126,
- "site_visit_cost": 100000,
- "distance": "3.2 km",
- "city": "Bekasi",
- "about_us": "10 tahun pengalaman renovasi rumah...",
- "certificates": "Sertifikat K3, ...",
- "bluescope_certified": true,
- "k3_certified": true,
- "bnsp_certified": true,
- "jobs_completed": 142,
- "reviews": [
- {
- "customer_name": "Andi",
- "rating": 5,
- "feedback": "Kerja rapi dan tepat waktu",
- "created_date": "2026-06-20"
}
]
}, - {
- "vendor_id": "VND0003344",
- "name": "PT Konstruksi Baja Nusantara",
- "rating": null,
- "review_count": 0,
- "site_visit_cost": 100000,
- "distance": "8.1 km",
- "city": "Tangerang",
- "about_us": "Kontraktor konstruksi baja...",
- "certificates": "",
- "bluescope_certified": true,
- "k3_certified": false,
- "bnsp_certified": false,
- "portfolio": [ ],
- "jobs_completed": 0,
- "reviews": [ ]
}
]
}Called once the customer's site-visit fee payment succeeds. Payment amount is fixed on Tukang.com's side and not accepted from this request.
| order_id required | string <= 64 characters SteelSolution's own order id, currently 9 numeric digits. Stored against your call so a retry with the same id is treated as the same order rather than creating a second one. |
| vendor_id required | string Vendor selected by the customer from the match response. |
| name required | string |
| email required | string <email> |
| phone required | string |
| city required | string City name (kota/kabupaten). |
| district required | string District name (kecamatan). |
| village required | string Village name (desa/kelurahan). |
| job_location required | string |
| job_location_lat required | number <float> |
| job_location_lng required | number <float> |
| job_location_detail | string or null |
| job_time required | string <date-time> |
| photo_url_1 | string or null |
| photo_url_2 | string or null |
| notes | string or null |
{- "order_id": "387492838",
- "vendor_id": "VND0001122",
- "name": "Andi Wijaya",
- "email": "andi.wijaya@example.com",
- "phone": "81234567890",
- "city": "Jakarta Selatan",
- "district": "Kebayoran Baru",
- "village": "Senayan",
- "job_location": "Jl. Merdeka No. 1",
- "job_location_lat": -6.2088,
- "job_location_lng": 106.8456,
- "job_location_detail": "Belakang gudang",
- "job_time": "2026-07-10T09:00:00+07:00",
- "photo_url_2": null,
- "notes": "Renovasi"
}{- "message": "Success create order",
- "result": {
- "site_visit_id": "7501130001",
- "status": "scheduled"
}
}Called once material for the order has been marked received. Notifies the assigned vendor to prepare for the job.
| order_id required | string SteelSolution's own order id (same value sent to /site-visits). |
| received_at required | string <date-time> |
{- "order_id": "387492838",
- "received_at": "2026-07-10T09:00:00+07:00"
}{- "message": "Success log material received",
- "result": {
- "site_visit_id": "7501130001"
}
}Optional status check, callable anytime for an order that exists in the system. rab is only present once a cost estimate exists for the order. job_time always reflects the current scheduled time, including after a reschedule.
| id required | string |
{- "message": "",
- "result": {
- "site_visit_id": "7501130001",
- "status": "estimate_ready",
- "vendor": {
- "id": "VND0001122",
- "name": "Budi Konstruksi"
}, - "customer_name": "Andi Wijaya",
- "job_location": "Jl. Merdeka No. 1",
- "job_time": "2026-07-10T09:00:00+07:00",
- "work_list": "Renovasi",
- "notes": "Renovasi",
- "photo_url_2": null,
- "rab": {
- "quote_amount": 5000000,
- "start_time": "2026-07-12",
- "duration": 14,
- "retention_day": 7,
- "total_service_amount": 1500000,
- "materials": [
- {
- "sku": "CBS-COLORBOND-SPAN940",
- "name": "Baja Ringan C75",
- "qty": 12,
- "unit": "batang",
- "price": 85000
}
]
}, - "complete_work_title": null,
- "complete_notes": null,
- "complete_photo_url_1": null,
- "complete_photo_url_2": null,
- "created_at": "2026-07-06T14:00:00+07:00",
- "last_modify_at": "2026-07-08T09:00:00+07:00"
}
}Called when the customer rejects the estimate/quotation.
| id required | string |
| reject_notes | string or null Free-text reason for rejection (optional). |
{- "reject_notes": "Harga terlalu tinggi"
}{- "message": "Success reject estimate",
- "result": {
- "site_visit_id": "7501130001",
- "status": "closed"
}
}Called once the customer accepts the estimate/quotation and payment succeeds. Payment amount is fixed on Tukang.com's side and not accepted from this request.
| id required | string |
No fields required.
{ }{- "message": "Success accept estimate",
- "result": {
- "site_visit_id": "7501130001",
- "status": "in_progress"
}
}