Tukang.com: SteelSolution Partner API (1.0.0)

Download OpenAPI specification:

Tukang.com Team: api@tukang.com

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.

Authentication

Every request needs X-API-Key and X-Signature headers - see the Authorization panel on each endpoint below for how to compute X-Signature.

Errors

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.

Request fields

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.

Site visit times

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:

  • at least 3 hours from now, because the vendor has to prepare and travel;
  • between 08:00 and 22:00 Jakarta time inclusive, so 22:00 is a valid start and 22:01 is not;
  • no more than 14 days ahead.

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.

Date and time format

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 size

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.

Status values

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.

Outbound status webhook

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.

Outbound delivery reliability

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.

vendor-match

Match nearby vendors for an installation request

Returns up to 5 nearby vendors with profile, reviews, and site-visit cost, pre-filtered to vendors available at the requested slot.

Authorizations:
(ApiKeyAuthSignatureAuth)
Request Body schema: application/json
required
required
object
preferred_slot
required
string <date-time>

Responses

Request samples

Content type
application/json
{
  • "location": {
    },
  • "preferred_slot": "2026-07-10T09:00:00+07:00"
}

Response samples

Content type
application/json
{
  • "message": "",
  • "result": [
    ]
}

site-visit

Create a site-visit order

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.

Authorizations:
(ApiKeyAuthSignatureAuth)
Request Body schema: application/json
required
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

Responses

Request samples

Content type
application/json
{
  • "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_1": "https://.../photo1.jpg",
  • "photo_url_2": null,
  • "notes": "Renovasi"
}

Response samples

Content type
application/json
{
  • "message": "Success create order",
  • "result": {
    }
}

Notify that material has been received

Called once material for the order has been marked received. Notifies the assigned vendor to prepare for the job.

Authorizations:
(ApiKeyAuthSignatureAuth)
Request Body schema: application/json
required
order_id
required
string

SteelSolution's own order id (same value sent to /site-visits).

received_at
required
string <date-time>

Responses

Request samples

Content type
application/json
{
  • "order_id": "387492838",
  • "received_at": "2026-07-10T09:00:00+07:00"
}

Response samples

Content type
application/json
{
  • "message": "Success log material received",
  • "result": {
    }
}

Check the current status of a site-visit order

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.

Authorizations:
(ApiKeyAuthSignatureAuth)
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "message": "",
  • "result": {
    }
}

site-visit-decision

Reject the vendor's cost estimate

Called when the customer rejects the estimate/quotation.

Authorizations:
(ApiKeyAuthSignatureAuth)
path Parameters
id
required
string
Request Body schema: application/json
optional
reject_notes
string or null

Free-text reason for rejection (optional).

Responses

Request samples

Content type
application/json
{
  • "reject_notes": "Harga terlalu tinggi"
}

Response samples

Content type
application/json
{
  • "message": "Success reject estimate",
  • "result": {
    }
}

Accept the vendor's cost estimate

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.

Authorizations:
(ApiKeyAuthSignatureAuth)
path Parameters
id
required
string
Request Body schema: application/json
optional
object (AcceptRequest)

No fields required.

Responses

Request samples

Content type
application/json
{ }

Response samples

Content type
application/json
{
  • "message": "Success accept estimate",
  • "result": {
    }
}