For developers
İşlemde API
Connect your jobs and customers to your own software, accounting or e-commerce system. Read data, open jobs and customers, change statuses, record payments, and get every change instantly by webhook.
- Base URL
/api/v1- Auth
x-api-key- Rate limit
- 600 per minute
- Plan
- Business
Request
GET /api/v1/jobs/1047
x-api-key: isl_••••••••
Response200
{ "number": 1047, "status": { "label": "Teslime hazır" }, "customer": { "name": "Nur Özkan" }, "trackingUrl": "…/t/U7WX42A8"}
in a few lines.
Getting started
- Create a key under Settings → API in the dashboard. The key is shown only once, so store it somewhere safe.
- Put the key in the
ISLEMDE_API_KEYenvironment variable on your server. - Send it with every request in the
x-api-keyheader. Your first request, jobs ready for pickup:
curl "https://islemde.app/api/v1/jobs?status=ready" \
-H "x-api-key: $ISLEMDE_API_KEY"Pick your language once; every example on the page switches to it.
Authentication
Send the key in the x-api-key header or as Authorization: Bearer isl_…. Keys start with isl_ and can access all of the business's jobs and customers.
Never keep the key in a browser, a mobile app or a public repository; always call the API from your own server. If you think it has leaked, revoke it under Settings → API and it stops working immediately.
Read and write
You choose a key's access when you create it. A read-only key (the default) reads lists and records and can't change anything. A read and write key can also open and edit jobs and customers, change statuses, add notes and record payments. Trying to write with a key that lacks write access returns 403 insufficient_scope. Access can't be changed later; create a new key if you need to.
Writes go through the same rules as the dashboard: the same validation, job allowance, SMS and emails to customers, job history and webhooks. The history shows the key's name as the author, e.g. API · Accounting. While the account is read-only (a payment failed or the trial ended), writes are off and reads keep working.
Every job opened with a write key counts toward your job allowance, and the customer may get an SMS. Don't test with real customer phone numbers.
Ready-made client
The examples below use this small helper: one function sends a single request and stops with a clear message on errors, the other fetches every page of a list in turn. Add it to your project once. JavaScript and C# need no extra packages; Python needs requests and PHP needs Guzzle.
// islemde.js · Node 18 ve üzeri, ek paket gerekmez
const BASE = "https://islemde.app/api/v1";
export async function islemde(path, params = {}) {
const url = new URL(BASE + path);
for (const [name, value] of Object.entries(params)) {
if (value != null) url.searchParams.set(name, value);
}
const res = await fetch(url, {
headers: { "x-api-key": process.env.ISLEMDE_API_KEY },
});
const body = await res.json();
if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
return body;
}
// Listenin bütün sayfalarını sırayla getirir:
// for await (const job of all("/jobs")) { … }
export async function* all(path, params = {}) {
let cursor;
while (cursor !== null) {
const page = await islemde(path, { ...params, cursor });
yield* page.data;
cursor = page.nextCursor;
}
}Pagination
List endpoints return up to limit records (25 by default, 100 at most) and a nextCursor. Send it back as cursor for the next page; on the last page it's null. Pages don't shift if new jobs are opened while you read, so you never get the same record twice.
With the ready-made client you don't write the loop yourself; records arrive one by one and pages are fetched in the background:
# İlk sayfa
curl "https://islemde.app/api/v1/jobs?state=all&limit=100" \
-H "x-api-key: $ISLEMDE_API_KEY"
# Sonraki sayfa: cevaptaki nextCursor ile
curl "https://islemde.app/api/v1/jobs?state=all&limit=100&cursor=1046" \
-H "x-api-key: $ISLEMDE_API_KEY"Safe retries
When the network drops, you can't tell whether your request reached us. Add a unique Idempotency-Key header to each new POST request (a UUID, say) and send the same value when you retry. Within 24 hours, a request with the same key and body won't create a second record; you get the first successful response back with an idempotent-replayed: true header.
- Sending the same key with a different body returns
422 idempotency_key_reused. - If the first request is still being processed you get
409 idempotency_in_progress; try again shortly. If a request stalls on our side and never gets a response, its key can be reused after 10 minutes. - Only successful responses are stored: if you got an error, fix it and resend with the same key. Keys are scoped to the business.
curl -X POST "https://islemde.app/api/v1/jobs" \
-H "x-api-key: $ISLEMDE_API_KEY" \
-H "content-type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"customer": {
"name": "Emre Kaya",
"phone": "0544 111 22 33"
},
"item": {
"category": "Telefon",
"brand": "Samsung",
"model": "Galaxy S23"
},
"problem": "Şarj olmuyor.",
"priority": "normal"
}'Errors
Errors come with an HTTP status code and always in the same shape. code never changes, so your program can rely on it; message is readable Turkish you can show to users or write to a log.
{
"error": {
"code": "invalid_api_key",
"message": "API anahtarı geçersiz, süresi dolmuş ya da iptal edilmiş."
}
}For a request with an invalid body, issues says which field was rejected and why; path separates nested fields with dots. Unknown fields are rejected too, so typos never get lost silently.
{
"error": {
"code": "invalid_input",
"message": "Gönderilen bilgilerde hata var.",
"issues": [
{
"path": "customer.phone",
"message": "Geçerli bir telefon numarası girin."
},
{
"path": "problem",
"message": "Sorunu kısaca yazın."
}
]
}
}| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_input | Invalid parameter or body; issues says which field was rejected and why. |
| 400 | invalid_json | The body isn't valid JSON. |
| 400 | invalid_idempotency_key | The Idempotency-Key is malformed. |
| 401 | missing_api_key | No key was sent. |
| 401 | invalid_api_key | The key is wrong, expired or revoked. |
| 403 | plan_feature_locked | The business's plan doesn't include the API. |
| 403 | insufficient_scope | The key is read-only. |
| 403 | workspace_restricted | The account is read-only because a payment failed; writes are off. |
| 403 | trial_expired | The trial has ended; the account is read-only and writes are off. |
| 403 | plan_required | The business needs to pick a plan first; writes are off. |
| 403 | trial_limit_reached | The trial's job allowance is used up; no new jobs can be opened. |
| 403 | overage_cap_reached | This month's extra usage cap has been reached; no new jobs can be opened. |
| 403 | subscription_canceled | The subscription has ended; the account is read-only and writes are off. |
| 403 | forbidden | The key has no access to this record. |
| 404 | not_found | No such record. |
| 409 | conflict | The record just changed or the request can't be fulfilled right now; fetch the current state and try again. |
| 409 | phone_in_use | The phone belongs to another customer. |
| 409 | idempotency_in_progress | A request with the same Idempotency-Key is still being processed. |
| 409 | refund_exceeds_paid | The refund is more than the payments received for the job (minus earlier refunds). |
| 413 | payload_too_large | The body is larger than 64 KB. |
| 422 | idempotency_key_reused | The Idempotency-Key was used with a different body in the last 24 hours. |
| 429 | rate_limited | You've exceeded the per-minute request or write limit; wait the number of seconds in Retry-After. |
| 500 | internal_error | Something went wrong on our side; we've logged it. |
Limits
Each key can make 600 requests a minute, up to 120 of them writes. Request bodies are capped at 64 KB. Go over the limit and you get 429; wait the number of seconds in the Retry-After header, then carry on. If you poll for changes often, use changedSince to ask only for what changed since your last check instead of rereading everything.
OpenAPI
Every endpoint and field is also available as OpenAPI 3.1. Import it into Postman or Insomnia, or generate a client in your own language. Webhook event bodies are in the document's webhooks section too.
Export all jobs to a spreadsheet
Write every job to a single CSV file for Excel or Google Sheets. No need to think about how many pages there are; the helper fetches them all in turn.
- Ask for all open and closed jobs with
state=all. - Write the fields you want for each job on one row.
- Open the file in Excel; choose UTF-8 so Turkish characters show correctly.
import { writeFile } from "node:fs/promises";
import { all } from "./islemde.js";
const cell = (value) => `"${String(value ?? "").replaceAll('"', '""')}"`;
const rows = [["No", "Müşteri", "Cihaz", "Durum", "Açılış"]];
for await (const job of all("/jobs", { state: "all", limit: 100 })) {
rows.push([
job.number,
job.customer.name,
[job.item.brand, job.item.model].filter(Boolean).join(" "),
job.status.label,
job.createdAt.slice(0, 10),
]);
}
await writeFile("isler.csv", rows.map((row) => row.map(cell).join(",")).join("\n"));No,Müşteri,Cihaz,Durum,Açılış
1057,"Nur Özkan","Apple iPhone 12","Teslime hazır",2026-09-24
1056,"Emre Kaya","Samsung Galaxy S21","Onarımda",2026-09-23Uses the ready-made client.
Notify your own system about ready jobs
For your own SMS provider, CRM or team chat: every few minutes, fetch the jobs that became ready for pickup since the last check.
- Ask only for jobs ready for pickup with
status=ready. - Use
changedSinceto get jobs whose status changed since the last check; the filter runs on our side. - Store the check time and run the script every few minutes with a scheduler (cron). The first run returns every job that's ready.
import { readFile, writeFile } from "node:fs/promises";
import { all } from "./islemde.js";
// Kendi SMS, e-posta ya da sohbet gönderiminiz.
const notify = async (text) => console.log(text);
const since = await readFile("son-kontrol.txt", "utf8").catch(() => undefined);
const now = new Date().toISOString();
for await (const job of all("/jobs", { status: "ready", changedSince: since })) {
await notify(`#${job.number} ${job.customer.name} için hazır: ${job.trackingUrl}`);
}
await writeFile("son-kontrol.txt", now);#1047 Nur Özkan için hazır: https://islemde.app/t/U7WX42A8Uses the ready-made client.
Send the day's handovers to accounting
At the end of the day, list the jobs handed over today with their amounts, payments received and balances due. It's all in the job list; no need to open jobs one by one.
- Ask for
state=donewithchangedSinceset to the start of today; only jobs closed today come back. - Jobs with an empty
deliveredAtwere cancelled; skip them. - On each job,
chargeKurusis the amount,paidKuruswhat was received andbalanceKurusthe balance due. Amounts are in kuruş: 350000 means ₺3,500.00. - For every job handed over but not paid for, list with
paymentStatus=owed. - For totals per customer, request
/customers?owing=true; each customer'sowedKurusis their balance due.
import { all } from "./islemde.js";
const startOfDay = new Date(new Date().setHours(0, 0, 0, 0)).toISOString();
const lira = (kurus) =>
new Intl.NumberFormat("tr-TR", { style: "currency", currency: "TRY" }).format(kurus / 100);
let paid = 0;
let owed = 0;
for await (const job of all("/jobs", { state: "done", changedSince: startOfDay })) {
if (!job.deliveredAt) continue; // iptal edilen
const left = Math.max(0, job.balanceKurus ?? 0); // ücretsizde 0, tutar yoksa null
paid += job.paidKurus;
owed += left;
console.log(job.number, job.customer.name, lira(job.chargeKurus ?? 0), lira(job.paidKurus), lira(left));
}
console.log("Alınan", lira(paid), "· Kalan", lira(owed));1047 Nur Özkan 3.500,00 TL 3.500,00 TL 0,00 TL
1041 Emre Kaya 1.250,00 TL 500,00 TL 750,00 TL
Alınan 4.000,00 TL · Kalan 750,00 TLUses the ready-made client.
List jobs
GET/api/v1/jobs
Newest first, page by page.
Parameters
| Name | Description |
|---|---|
statestring | Open jobs, closed ones or all of them.activedoneall Default: active. |
statusstring | Only jobs in this status; the status key. |
qstring | Searches job number, customer name, phone or device. |
changedSincestring | Only jobs whose status changed after this moment; ISO 8601, e.g. 2026-09-26T00:00:00Z. For polling changes regularly. |
paymentStatusstring | By payment status: paid, partial, unpaid, waived, none; balance for jobs with a balance, owed for jobs handed over but not paid for (on credit). With owed, a missing state counts as all.paidpartialunpaidwaivednonebalanceowed |
limitinteger | Records per page, 1–100. Default: 25. |
cursorstring | The nextCursor from the previous response; leave it out for the first page. |
curl "https://islemde.app/api/v1/jobs?status=ready" \
-H "x-api-key: $ISLEMDE_API_KEY"{
"data": [
{
"number": 1047,
"trackingCode": "U7WX42A8",
"trackingUrl": "https://islemde.app/t/U7WX42A8",
"status": {
"key": "ready",
"label": "Teslime hazır",
"category": "open"
},
"item": {
"category": "Telefon",
"brand": "Apple",
"model": "iPhone 12"
},
"problem": "Ekran değişimi.",
"priority": "normal",
"customer": {
"name": "Nur Özkan",
"phone": "+905321234567"
},
"assignee": "Burak Demir",
"estimateKurus": 350000,
"chargeKurus": 350000,
"paidKurus": 100000,
"balanceKurus": 250000,
"paymentStatus": "partial",
"dueAt": "2026-09-27T15:00:00.000Z",
"createdAt": "2026-09-24T08:12:40.000Z",
"statusChangedAt": "2026-09-25T13:02:11.000Z",
"deliveredAt": null,
"cancelledAt": null
}
],
"nextCursor": "1046"
}each record in data
| Field | Description |
|---|---|
numberinteger | Job number; shown as #1047 in the dashboard. |
trackingCodestring | The customer's tracking code (8 characters). |
trackingUrlstring | The tracking page you can send to the customer. On your own domain if you've connected one, otherwise on islemde.app; the same in the API and webhooks. |
statusobject | Current status. |
status.keystring | The status key: received, ready, delivered… |
status.labelstring | Its name in the dashboard. |
status.categorystring | The kind of status.openwaitingdonecancelled |
itemobject | Device or product. |
item.categorystring · can be null | Type, e.g. Phone. |
item.brandstring · can be null | Brand. |
item.modelstring · can be null | Model. |
problemstring | The problem the customer reported. |
prioritystring | Priority.lownormalhighurgent |
customerobject | Customer. |
customer.namestring | Full name. |
customer.phonestring · can be null | Phone, with +90. |
assigneestring · can be null | Name of the assignee. |
estimateKurusinteger · can be null | The estimate given at check-in, in kuruş (185000 = ₺1,850.00). |
chargeKurusinteger · can be null | The job's amount in kuruş: the approved quote's total incl. VAT, otherwise the estimate. null on a cancelled job. |
paidKurusinteger | Payments received minus refunds, in kuruş. Voided records don't count. |
balanceKurusinteger · can be null | Balance due in kuruş: chargeKurus − paidKurus. 0 on a job marked free, null if there's no amount yet; negative when overpaid. |
paymentStatusstring | Payment status: paid, partial (partly paid), unpaid, waived (free / under warranty), none (no amount). Jobs imported by CSV are none until a payment is recorded.paidpartialunpaidwaivednone |
dueAtdate (ISO 8601) · can be null | The promised due date. |
createdAtdate (ISO 8601) | When it was checked in. |
statusChangedAtdate (ISO 8601) | When the status last changed. |
deliveredAtdate (ISO 8601) · can be null | When it was handed over. |
cancelledAtdate (ISO 8601) · can be null | When it was cancelled. |
Get a job
GET/api/v1/jobs/{number}
The job and the latest page of its history: up to 30 events, 10 quotes and 10 warranties. The timeline only holds events the customer can see, so it may be empty. For each list, send the nextCursor or prevCursor from pagination in the matching query parameter; null means there are no more pages in that direction.
Parameters
| Name | Description |
|---|---|
numberpath, required | Job number. |
eventsCursorstring | nextCursor or prevCursor from pagination.events; leave it out for the first page. Up to 2048 characters. |
quotesCursorstring | nextCursor or prevCursor from pagination.quotes; leave it out for the first page. Up to 2048 characters. |
warrantiesCursorstring | nextCursor or prevCursor from pagination.warranties; leave it out for the first page. Up to 2048 characters. |
curl "https://islemde.app/api/v1/jobs/1047" \
-H "x-api-key: $ISLEMDE_API_KEY"{
"pagination": {
"events": {
"nextCursor": null,
"prevCursor": null
},
"quotes": {
"nextCursor": null,
"prevCursor": null
},
"warranties": {
"nextCursor": null,
"prevCursor": null
}
},
"data": {
"number": 1047,
"trackingCode": "U7WX42A8",
"trackingUrl": "https://islemde.app/t/U7WX42A8",
"status": {
"key": "ready",
"label": "Teslime hazır",
"category": "open"
},
"item": {
"category": "Telefon",
"brand": "Apple",
"model": "iPhone 12"
},
"problem": "Ekran değişimi.",
"priority": "normal",
"customer": {
"name": "Nur Özkan",
"phone": "+905321234567",
"email": null
},
"assignee": "Burak Demir",
"estimateKurus": 350000,
"chargeKurus": 350000,
"paidKurus": 100000,
"balanceKurus": 250000,
"paymentStatus": "partial",
"dueAt": "2026-09-27T15:00:00.000Z",
"createdAt": "2026-09-24T08:12:40.000Z",
"statusChangedAt": "2026-09-25T13:02:11.000Z",
"deliveredAt": null,
"cancelledAt": null,
"serial": "356789104455120",
"accessories": "Kılıf",
"timeline": [
{
"type": "created",
"at": "2026-09-24T08:12:40.000Z",
"text": "Cihazınız teslim alındı"
},
{
"type": "status_changed",
"at": "2026-09-25T13:02:11.000Z",
"text": "Teslime hazır"
}
],
"quotes": [
{
"number": 212,
"status": "approved",
"totalKurus": 350000,
"validUntil": "2026-10-01T00:00:00.000Z"
}
],
"warranties": []
}
}data
| Field | Description |
|---|---|
numberinteger | Job number; shown as #1047 in the dashboard. |
trackingCodestring | The customer's tracking code (8 characters). |
trackingUrlstring | The tracking page you can send to the customer. On your own domain if you've connected one, otherwise on islemde.app; the same in the API and webhooks. |
statusobject | Current status. |
status.keystring | The status key: received, ready, delivered… |
status.labelstring | Its name in the dashboard. |
status.categorystring | The kind of status.openwaitingdonecancelled |
itemobject | Device or product. |
item.categorystring · can be null | Type, e.g. Phone. |
item.brandstring · can be null | Brand. |
item.modelstring · can be null | Model. |
problemstring | The problem the customer reported. |
prioritystring | Priority.lownormalhighurgent |
customerobject | Customer. |
customer.namestring | Full name. |
customer.phonestring · can be null | Phone, with +90. |
customer.emailstring · can be null | Email. |
assigneestring · can be null | Name of the assignee. |
estimateKurusinteger · can be null | The estimate given at check-in, in kuruş (185000 = ₺1,850.00). |
chargeKurusinteger · can be null | The job's amount in kuruş: the approved quote's total incl. VAT, otherwise the estimate. null on a cancelled job. |
paidKurusinteger | Payments received minus refunds, in kuruş. Voided records don't count. |
balanceKurusinteger · can be null | Balance due in kuruş: chargeKurus − paidKurus. 0 on a job marked free, null if there's no amount yet; negative when overpaid. |
paymentStatusstring | Payment status: paid, partial (partly paid), unpaid, waived (free / under warranty), none (no amount). Jobs imported by CSV are none until a payment is recorded.paidpartialunpaidwaivednone |
dueAtdate (ISO 8601) · can be null | The promised due date. |
createdAtdate (ISO 8601) | When it was checked in. |
statusChangedAtdate (ISO 8601) | When the status last changed. |
deliveredAtdate (ISO 8601) · can be null | When it was handed over. |
cancelledAtdate (ISO 8601) · can be null | When it was cancelled. |
serialstring · can be null | Serial or IMEI number. |
accessoriesstring · can be null | Items left with the device, e.g. case, charger. |
timeline[]list | The history the customer also sees, oldest first. Internal notes aren't included. |
timeline[].typestring | created, status_changed, quote_sent… |
timeline[].atdate (ISO 8601) | When it happened. |
timeline[].textstring | The sentence the customer sees on the tracking page, e.g. Ready for pickup. |
quotes[]list | Quotes. |
quotes[].numberinteger | Quote number. |
quotes[].statusstring | Its status.draftsentapprovedrejectedexpiredcancelled |
quotes[].totalKurusinteger | Total incl. VAT, in kuruş. |
quotes[].validUntildate (ISO 8601) · can be null | Valid until. |
warranties[]list | Warranty certificates. |
warranties[].codestring | Certificate code. |
warranties[].statusstring | active or void. |
warranties[].monthsinteger | Length, in months. |
warranties[].endsAtdate (ISO 8601) | End date. |
Create a job
POST/api/v1/jobs
Opens a job for an existing customer or with a new one. As in the dashboard, it counts toward your job allowance, the customer gets the check-in SMS and a job.created event is sent.
Needs a read and write key. Safe to retry with an Idempotency-Key.
Body (JSON)
| Field | Description |
|---|---|
customerIdstring | The existing customer's ID. Send either customerId or customer. |
customerobject | A new customer. If a customer with the same phone exists, no new one is created and the job is linked to them. |
customer.namestring · required | Full name or company name, 2–120 characters. |
customer.phonestring | Phone; 0532 123 45 67, 5321234567 or +905321234567 all work. |
customer.emailstring | Email. |
customer.kindstring | Individual or business. Default: individual.individualcompany |
customer.taxIdstring | Tax number (10 digits) or Turkish ID number (11 digits). Never returned. |
customer.taxOfficestring | Tax office. |
customer.citystring | Province, spelled in Turkish: İstanbul, İzmir… |
customer.districtstring | District; send it together with the province. |
customer.addressstring | Street address, up to 300 characters. |
customer.notesstring | A note about the customer for your team. Never returned. |
itemobject | Device or product. |
item.categorystring | Type, e.g. Phone. |
item.brandstring | Brand. |
item.modelstring | Model. |
problemstring · required | The problem the customer reported, 3–2000 characters. |
serialstring | Serial or IMEI number. |
accessoriesstring | Items left with the device. |
intakeConditionstring | Its condition at check-in: scratches, cracks… |
estimateKurusinteger | Estimated amount in kuruş (185000 = ₺1,850.00). |
prioritystring | Priority. Default: normal.lownormalhighurgent |
dueAtdate (ISO 8601) | The promised due date; ISO 8601. |
privateNotestring | A team-only note. The API and webhooks never send it back. |
curl -X POST "https://islemde.app/api/v1/jobs" \
-H "x-api-key: $ISLEMDE_API_KEY" \
-H "content-type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"customer": {
"name": "Emre Kaya",
"phone": "0544 111 22 33"
},
"item": {
"category": "Telefon",
"brand": "Samsung",
"model": "Galaxy S23"
},
"problem": "Şarj olmuyor.",
"priority": "normal"
}'{
"data": {
"number": 1058,
"trackingCode": "K3PX9M2D",
"trackingUrl": "https://islemde.app/t/K3PX9M2D",
"status": {
"key": "received",
"label": "Teslim alındı",
"category": "open"
},
"item": {
"category": "Telefon",
"brand": "Samsung",
"model": "Galaxy S23"
},
"problem": "Şarj olmuyor.",
"priority": "normal",
"customer": {
"name": "Emre Kaya",
"phone": "+905441112233"
},
"assignee": null,
"estimateKurus": null,
"chargeKurus": null,
"paidKurus": 0,
"balanceKurus": null,
"paymentStatus": "none",
"dueAt": null,
"createdAt": "2026-09-27T09:30:00.000Z",
"statusChangedAt": "2026-09-27T09:30:00.000Z",
"deliveredAt": null,
"cancelledAt": null
}
}data
| Field | Description |
|---|---|
numberinteger | Job number; shown as #1047 in the dashboard. |
trackingCodestring | The customer's tracking code (8 characters). |
trackingUrlstring | The tracking page you can send to the customer. On your own domain if you've connected one, otherwise on islemde.app; the same in the API and webhooks. |
statusobject | Current status. |
status.keystring | The status key: received, ready, delivered… |
status.labelstring | Its name in the dashboard. |
status.categorystring | The kind of status.openwaitingdonecancelled |
itemobject | Device or product. |
item.categorystring · can be null | Type, e.g. Phone. |
item.brandstring · can be null | Brand. |
item.modelstring · can be null | Model. |
problemstring | The problem the customer reported. |
prioritystring | Priority.lownormalhighurgent |
customerobject | Customer. |
customer.namestring | Full name. |
customer.phonestring · can be null | Phone, with +90. |
assigneestring · can be null | Name of the assignee. |
estimateKurusinteger · can be null | The estimate given at check-in, in kuruş (185000 = ₺1,850.00). |
chargeKurusinteger · can be null | The job's amount in kuruş: the approved quote's total incl. VAT, otherwise the estimate. null on a cancelled job. |
paidKurusinteger | Payments received minus refunds, in kuruş. Voided records don't count. |
balanceKurusinteger · can be null | Balance due in kuruş: chargeKurus − paidKurus. 0 on a job marked free, null if there's no amount yet; negative when overpaid. |
paymentStatusstring | Payment status: paid, partial (partly paid), unpaid, waived (free / under warranty), none (no amount). Jobs imported by CSV are none until a payment is recorded.paidpartialunpaidwaivednone |
dueAtdate (ISO 8601) · can be null | The promised due date. |
createdAtdate (ISO 8601) | When it was checked in. |
statusChangedAtdate (ISO 8601) | When the status last changed. |
deliveredAtdate (ISO 8601) · can be null | When it was handed over. |
cancelledAtdate (ISO 8601) · can be null | When it was cancelled. |
Update a job
PATCH/api/v1/jobs/{number}
Only the fields you send change; a field sent as null is cleared. The change is logged in the job's history for the team; the signed check-in ticket stays as it was.
Needs a read and write key.
Parameters
| Name | Description |
|---|---|
numberpath, required | Job number. |
Body (JSON)
| Field | Description |
|---|---|
itemobject | Device or product. |
item.categorystring | Type, e.g. Phone. |
item.brandstring | Brand. |
item.modelstring | Model. |
problemstring | The problem the customer reported, 3–2000 characters. |
serialstring · null clears it | Serial or IMEI number. |
accessoriesstring · null clears it | Items left with the device. |
intakeConditionstring · null clears it | Its condition at check-in: scratches, cracks… |
estimateKurusinteger · null clears it | Estimated amount in kuruş (185000 = ₺1,850.00). |
prioritystring | Priority. Default: normal.lownormalhighurgent |
dueAtdate (ISO 8601) · null clears it | The promised due date; ISO 8601. |
privateNotestring · null clears it | A team-only note. The API and webhooks never send it back. |
curl -X PATCH "https://islemde.app/api/v1/jobs/1047" \
-H "x-api-key: $ISLEMDE_API_KEY" \
-H "content-type: application/json" \
-d '{
"item": {
"model": "iPhone 12 Pro"
},
"estimateKurus": 420000,
"dueAt": null
}'{
"data": {
"number": 1047,
"trackingCode": "U7WX42A8",
"trackingUrl": "https://islemde.app/t/U7WX42A8",
"status": {
"key": "ready",
"label": "Teslime hazır",
"category": "open"
},
"item": {
"category": "Telefon",
"brand": "Apple",
"model": "iPhone 12 Pro"
},
"problem": "Ekran değişimi.",
"priority": "normal",
"customer": {
"name": "Nur Özkan",
"phone": "+905321234567"
},
"assignee": "Burak Demir",
"estimateKurus": 420000,
"chargeKurus": 350000,
"paidKurus": 100000,
"balanceKurus": 250000,
"paymentStatus": "partial",
"dueAt": null,
"createdAt": "2026-09-24T08:12:40.000Z",
"statusChangedAt": "2026-09-25T13:02:11.000Z",
"deliveredAt": null,
"cancelledAt": null
}
}data
| Field | Description |
|---|---|
numberinteger | Job number; shown as #1047 in the dashboard. |
trackingCodestring | The customer's tracking code (8 characters). |
trackingUrlstring | The tracking page you can send to the customer. On your own domain if you've connected one, otherwise on islemde.app; the same in the API and webhooks. |
statusobject | Current status. |
status.keystring | The status key: received, ready, delivered… |
status.labelstring | Its name in the dashboard. |
status.categorystring | The kind of status.openwaitingdonecancelled |
itemobject | Device or product. |
item.categorystring · can be null | Type, e.g. Phone. |
item.brandstring · can be null | Brand. |
item.modelstring · can be null | Model. |
problemstring | The problem the customer reported. |
prioritystring | Priority.lownormalhighurgent |
customerobject | Customer. |
customer.namestring | Full name. |
customer.phonestring · can be null | Phone, with +90. |
assigneestring · can be null | Name of the assignee. |
estimateKurusinteger · can be null | The estimate given at check-in, in kuruş (185000 = ₺1,850.00). |
chargeKurusinteger · can be null | The job's amount in kuruş: the approved quote's total incl. VAT, otherwise the estimate. null on a cancelled job. |
paidKurusinteger | Payments received minus refunds, in kuruş. Voided records don't count. |
balanceKurusinteger · can be null | Balance due in kuruş: chargeKurus − paidKurus. 0 on a job marked free, null if there's no amount yet; negative when overpaid. |
paymentStatusstring | Payment status: paid, partial (partly paid), unpaid, waived (free / under warranty), none (no amount). Jobs imported by CSV are none until a payment is recorded.paidpartialunpaidwaivednone |
dueAtdate (ISO 8601) · can be null | The promised due date. |
createdAtdate (ISO 8601) | When it was checked in. |
statusChangedAtdate (ISO 8601) | When the status last changed. |
deliveredAtdate (ISO 8601) · can be null | When it was handed over. |
cancelledAtdate (ISO 8601) · can be null | When it was cancelled. |
Change status
POST/api/v1/jobs/{number}/status
Moves the job to another stage. The customer's SMS and email go out just as if it were changed in the dashboard; delivered hands the job over. Get status keys from the Statuses endpoint.
Needs a read and write key. Safe to retry with an Idempotency-Key.
Parameters
| Name | Description |
|---|---|
numberpath, required | Job number. |
Body (JSON)
| Field | Description |
|---|---|
statusstring | The status key, e.g. ready. Send either status or statusId. |
statusIdstring | The status's ID. |
curl -X POST "https://islemde.app/api/v1/jobs/1047/status" \
-H "x-api-key: $ISLEMDE_API_KEY" \
-H "content-type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"status": "ready"
}'{
"data": {
"number": 1047,
"trackingCode": "U7WX42A8",
"trackingUrl": "https://islemde.app/t/U7WX42A8",
"status": {
"key": "ready",
"label": "Teslime hazır",
"category": "open"
},
"item": {
"category": "Telefon",
"brand": "Apple",
"model": "iPhone 12"
},
"problem": "Ekran değişimi.",
"priority": "normal",
"customer": {
"name": "Nur Özkan",
"phone": "+905321234567"
},
"assignee": "Burak Demir",
"estimateKurus": 350000,
"chargeKurus": 350000,
"paidKurus": 100000,
"balanceKurus": 250000,
"paymentStatus": "partial",
"dueAt": "2026-09-27T15:00:00.000Z",
"createdAt": "2026-09-24T08:12:40.000Z",
"statusChangedAt": "2026-09-25T13:02:11.000Z",
"deliveredAt": null,
"cancelledAt": null
}
}data
| Field | Description |
|---|---|
numberinteger | Job number; shown as #1047 in the dashboard. |
trackingCodestring | The customer's tracking code (8 characters). |
trackingUrlstring | The tracking page you can send to the customer. On your own domain if you've connected one, otherwise on islemde.app; the same in the API and webhooks. |
statusobject | Current status. |
status.keystring | The status key: received, ready, delivered… |
status.labelstring | Its name in the dashboard. |
status.categorystring | The kind of status.openwaitingdonecancelled |
itemobject | Device or product. |
item.categorystring · can be null | Type, e.g. Phone. |
item.brandstring · can be null | Brand. |
item.modelstring · can be null | Model. |
problemstring | The problem the customer reported. |
prioritystring | Priority.lownormalhighurgent |
customerobject | Customer. |
customer.namestring | Full name. |
customer.phonestring · can be null | Phone, with +90. |
assigneestring · can be null | Name of the assignee. |
estimateKurusinteger · can be null | The estimate given at check-in, in kuruş (185000 = ₺1,850.00). |
chargeKurusinteger · can be null | The job's amount in kuruş: the approved quote's total incl. VAT, otherwise the estimate. null on a cancelled job. |
paidKurusinteger | Payments received minus refunds, in kuruş. Voided records don't count. |
balanceKurusinteger · can be null | Balance due in kuruş: chargeKurus − paidKurus. 0 on a job marked free, null if there's no amount yet; negative when overpaid. |
paymentStatusstring | Payment status: paid, partial (partly paid), unpaid, waived (free / under warranty), none (no amount). Jobs imported by CSV are none until a payment is recorded.paidpartialunpaidwaivednone |
dueAtdate (ISO 8601) · can be null | The promised due date. |
createdAtdate (ISO 8601) | When it was checked in. |
statusChangedAtdate (ISO 8601) | When the status last changed. |
deliveredAtdate (ISO 8601) · can be null | When it was handed over. |
cancelledAtdate (ISO 8601) · can be null | When it was cancelled. |
Add a note
POST/api/v1/jobs/{number}/notes
Adds a note to the job. Only your team sees it by default; with customerVisible it also shows on the customer's tracking page.
Needs a read and write key. Safe to retry with an Idempotency-Key.
Parameters
| Name | Description |
|---|---|
numberpath, required | Job number. |
Body (JSON)
| Field | Description |
|---|---|
bodystring · required | The note, 1–4000 characters. |
customerVisibletrue/false | Show it to the customer too. Default: false. |
curl -X POST "https://islemde.app/api/v1/jobs/1047/notes" \
-H "x-api-key: $ISLEMDE_API_KEY" \
-H "content-type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"body": "Parça siparişi verildi, perşembe gelecek.",
"customerVisible": true
}'{
"data": {
"id": "0f9c2a64-7d0e-4b8f-a0f1-65e0b5d2c7a3",
"body": "Parça siparişi verildi, perşembe gelecek.",
"customerVisible": true,
"author": "API · Muhasebe",
"createdAt": "2026-09-27T10:05:00.000Z"
}
}data
| Field | Description |
|---|---|
idstring | The note's ID. |
bodystring | Note. |
customerVisibletrue/false | Also shown on the customer's tracking page. |
authorstring | Author; the key's name for notes from the API. |
createdAtdate (ISO 8601) | When it was added. |
List a job's payments
GET/api/v1/jobs/{number}/payments
Payments and refunds recorded on the job, newest first; voided ones include voidedAt. The remaining balance is in the job's balanceKurus field.
Parameters
| Name | Description |
|---|---|
numberpath, required | Job number. |
curl "https://islemde.app/api/v1/jobs/1047/payments" \
-H "x-api-key: $ISLEMDE_API_KEY"{
"data": [
{
"id": "8d3f0c1e-54a2-4a71-9b7e-2f6a1c0d9e44",
"kind": "payment",
"amountKurus": 100000,
"method": "cash",
"note": "Kapora",
"receivedAt": "2026-09-24T08:14:02.000Z",
"receivedBy": "Selin Aydın",
"atHandover": false,
"voidedAt": null,
"voidReason": null
}
]
}each record in data
| Field | Description |
|---|---|
idstring | The record's ID. |
kindstring | payment for a payment received, refund for a refund to the customer.paymentrefund |
amountKurusinteger | Amount in kuruş; always positive. |
methodstring | cash, card (the business's own POS), transfer (bank transfer / EFT), other.cashcardtransferother |
notestring · can be null | Note. |
receivedAtdate (ISO 8601) | When the payment was received. |
receivedBystring · can be null | Who recorded it; the key's name for records from the API. |
atHandovertrue/false | Taken at pickup. |
voidedAtdate (ISO 8601) · can be null | When it was voided, if it was; voided records don't count. |
voidReasonstring · can be null | Reason for voiding. |
Record a payment
POST/api/v1/jobs/{number}/payments
Records a payment or refund; the job's balance, cash register and reports update just as in the dashboard, and a payment.recorded event is sent. İşlemde doesn't charge anyone, it only records the payment you received. Send the same Idempotency-Key when retrying; within 24 hours a payment is never recorded twice.
Needs a read and write key. Safe to retry with an Idempotency-Key.
Parameters
| Name | Description |
|---|---|
numberpath, required | Job number. |
Body (JSON)
| Field | Description |
|---|---|
kindstring | payment or refund. Default: payment.paymentrefund |
amountKurusinteger · required | Amount in kuruş; between 1 and 100000000 (₺1,000,000). |
methodstring · required | Payment method.cashcardtransferother |
notestring | Note, up to 300 characters. |
curl -X POST "https://islemde.app/api/v1/jobs/1047/payments" \
-H "x-api-key: $ISLEMDE_API_KEY" \
-H "content-type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"amountKurus": 250000,
"method": "card",
"note": "Kalan ödeme"
}'{
"data": {
"payment": {
"id": "c41b7a2e-0f3d-4e8a-9a55-7d21e6b0f913",
"kind": "payment",
"amountKurus": 250000,
"method": "card",
"note": "Kalan ödeme",
"receivedAt": "2026-09-27T16:40:12.000Z",
"receivedBy": "API · Muhasebe",
"atHandover": false,
"voidedAt": null,
"voidReason": null
},
"job": {
"number": 1047,
"trackingCode": "U7WX42A8",
"trackingUrl": "https://islemde.app/t/U7WX42A8",
"status": {
"key": "ready",
"label": "Teslime hazır",
"category": "open"
},
"item": {
"category": "Telefon",
"brand": "Apple",
"model": "iPhone 12"
},
"problem": "Ekran değişimi.",
"priority": "normal",
"customer": {
"name": "Nur Özkan",
"phone": "+905321234567"
},
"assignee": "Burak Demir",
"estimateKurus": 350000,
"chargeKurus": 350000,
"paidKurus": 350000,
"balanceKurus": 0,
"paymentStatus": "paid",
"dueAt": "2026-09-27T15:00:00.000Z",
"createdAt": "2026-09-24T08:12:40.000Z",
"statusChangedAt": "2026-09-25T13:02:11.000Z",
"deliveredAt": null,
"cancelledAt": null
}
}
}data
| Field | Description |
|---|---|
paymentobject | The record. |
payment.idstring | The record's ID. |
payment.kindstring | payment for a payment received, refund for a refund to the customer.paymentrefund |
payment.amountKurusinteger | Amount in kuruş; always positive. |
payment.methodstring | cash, card (the business's own POS), transfer (bank transfer / EFT), other.cashcardtransferother |
payment.notestring · can be null | Note. |
payment.receivedAtdate (ISO 8601) | When the payment was received. |
payment.receivedBystring · can be null | Who recorded it; the key's name for records from the API. |
payment.atHandovertrue/false | Taken at pickup. |
payment.voidedAtdate (ISO 8601) · can be null | When it was voided, if it was; voided records don't count. |
payment.voidReasonstring · can be null | Reason for voiding. |
jobobject | The job after the record; balanceKurus gives the remaining balance. |
job.numberinteger | Job number; shown as #1047 in the dashboard. |
job.trackingCodestring | The customer's tracking code (8 characters). |
job.trackingUrlstring | The tracking page you can send to the customer. On your own domain if you've connected one, otherwise on islemde.app; the same in the API and webhooks. |
job.statusobject | Current status. |
job.status.keystring | The status key: received, ready, delivered… |
job.status.labelstring | Its name in the dashboard. |
job.status.categorystring | The kind of status.openwaitingdonecancelled |
job.itemobject | Device or product. |
job.item.categorystring · can be null | Type, e.g. Phone. |
job.item.brandstring · can be null | Brand. |
job.item.modelstring · can be null | Model. |
job.problemstring | The problem the customer reported. |
job.prioritystring | Priority.lownormalhighurgent |
job.customerobject | Customer. |
job.customer.namestring | Full name. |
job.customer.phonestring · can be null | Phone, with +90. |
job.assigneestring · can be null | Name of the assignee. |
job.estimateKurusinteger · can be null | The estimate given at check-in, in kuruş (185000 = ₺1,850.00). |
job.chargeKurusinteger · can be null | The job's amount in kuruş: the approved quote's total incl. VAT, otherwise the estimate. null on a cancelled job. |
job.paidKurusinteger | Payments received minus refunds, in kuruş. Voided records don't count. |
job.balanceKurusinteger · can be null | Balance due in kuruş: chargeKurus − paidKurus. 0 on a job marked free, null if there's no amount yet; negative when overpaid. |
job.paymentStatusstring | Payment status: paid, partial (partly paid), unpaid, waived (free / under warranty), none (no amount). Jobs imported by CSV are none until a payment is recorded.paidpartialunpaidwaivednone |
job.dueAtdate (ISO 8601) · can be null | The promised due date. |
job.createdAtdate (ISO 8601) | When it was checked in. |
job.statusChangedAtdate (ISO 8601) | When the status last changed. |
job.deliveredAtdate (ISO 8601) · can be null | When it was handed over. |
job.cancelledAtdate (ISO 8601) · can be null | When it was cancelled. |
List customers
GET/api/v1/customers
Starting with the most recent job, page by page.
Parameters
| Name | Description |
|---|---|
qstring | Searches name, phone or email. |
owingstring | With true, only customers who still owe on handed-over jobs.truefalse Default: false. |
limitinteger | Records per page, 1–100. Default: 25. |
cursorstring | The nextCursor from the previous response; leave it out for the first page. |
curl "https://islemde.app/api/v1/customers" \
-H "x-api-key: $ISLEMDE_API_KEY"{
"data": [
{
"id": "c30f5268-a5a7-4269-a9c0-bc135f6a90ae",
"name": "Nur Özkan",
"phone": "+905321234567",
"email": null,
"jobCount": 3,
"lastJobAt": "2026-09-24T08:12:40.000Z",
"owedKurus": 250000,
"createdAt": "2026-06-02T10:40:00.000Z"
}
],
"nextCursor": null
}each record in data
| Field | Description |
|---|---|
idstring | The customer's ID. |
namestring | Full name. |
phonestring · can be null | Phone, with +90. |
emailstring · can be null | Email. |
jobCountinteger | Total number of jobs. |
lastJobAtdate (ISO 8601) · can be null | When the latest job was opened. |
owedKurusinteger | Balance owed on jobs handed over but not paid for (on credit), in kuruş. Jobs marked free and jobs imported by CSV don't count; 0 if nothing is owed. |
createdAtdate (ISO 8601) | When the record was created. |
Get a customer
GET/api/v1/customers/{id}
The customer's record, job count and balance owed on handed-over jobs.
Parameters
| Name | Description |
|---|---|
idpath, required | The customer's ID. |
curl "https://islemde.app/api/v1/customers/5b1e7f7a-0c1d-4a55-9d51-2f0c8f3b8a10" \
-H "x-api-key: $ISLEMDE_API_KEY"{
"data": {
"id": "5b1e7f7a-0c1d-4a55-9d51-2f0c8f3b8a10",
"kind": "individual",
"name": "Emre Kaya",
"phone": "+905441112233",
"email": "emre@example.com",
"city": "İzmir",
"district": "Karşıyaka",
"address": null,
"smsOptOut": false,
"createdAt": "2026-09-27T09:30:00.000Z",
"jobCount": 2,
"owedKurus": 0
}
}data
| Field | Description |
|---|---|
idstring | The customer's ID. |
kindstring | Individual or business.individualcompany |
namestring | Full name or company name. |
phonestring · can be null | Phone, with +90. |
emailstring · can be null | Email. |
citystring · can be null | Province. |
districtstring · can be null | District. |
addressstring · can be null | Street address. |
smsOptOuttrue/false | The customer doesn't want SMS messages. |
createdAtdate (ISO 8601) | When the record was created. |
jobCountinteger | Total number of jobs. |
owedKurusinteger | Balance owed on jobs handed over but not paid for (on credit), in kuruş. Jobs marked free and jobs imported by CSV don't count; 0 if nothing is owed. |
Add a customer
POST/api/v1/customers
Creates a new customer and sends a customer.created event. If a customer with the same phone exists it returns 409; search for them by phone with List customers.
Needs a read and write key. Safe to retry with an Idempotency-Key.
Body (JSON)
| Field | Description |
|---|---|
namestring · required | Full name or company name, 2–120 characters. |
phonestring | Phone; 0532 123 45 67, 5321234567 or +905321234567 all work. |
emailstring | Email. |
kindstring | Individual or business. Default: individual.individualcompany |
taxIdstring | Tax number (10 digits) or Turkish ID number (11 digits). Never returned. |
taxOfficestring | Tax office. |
citystring | Province, spelled in Turkish: İstanbul, İzmir… |
districtstring | District; send it together with the province. |
addressstring | Street address, up to 300 characters. |
notesstring | A note about the customer for your team. Never returned. |
curl -X POST "https://islemde.app/api/v1/customers" \
-H "x-api-key: $ISLEMDE_API_KEY" \
-H "content-type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"name": "Emre Kaya",
"phone": "0544 111 22 33",
"email": "emre@example.com",
"city": "İzmir",
"district": "Karşıyaka"
}'{
"data": {
"id": "5b1e7f7a-0c1d-4a55-9d51-2f0c8f3b8a10",
"kind": "individual",
"name": "Emre Kaya",
"phone": "+905441112233",
"email": "emre@example.com",
"city": "İzmir",
"district": "Karşıyaka",
"address": null,
"smsOptOut": false,
"createdAt": "2026-09-27T09:30:00.000Z"
}
}data
| Field | Description |
|---|---|
idstring | The customer's ID. |
kindstring | Individual or business.individualcompany |
namestring | Full name or company name. |
phonestring · can be null | Phone, with +90. |
emailstring · can be null | Email. |
citystring · can be null | Province. |
districtstring · can be null | District. |
addressstring · can be null | Street address. |
smsOptOuttrue/false | The customer doesn't want SMS messages. |
createdAtdate (ISO 8601) | When the record was created. |
Update a customer
PATCH/api/v1/customers/{id}
Only the fields you send change; a field sent as null is cleared. A customer whose personal data has been erased can't be changed.
Needs a read and write key.
Parameters
| Name | Description |
|---|---|
idpath, required | The customer's ID. |
Body (JSON)
| Field | Description |
|---|---|
namestring | Full name or company name, 2–120 characters. |
phonestring · null clears it | Phone; 0532 123 45 67, 5321234567 or +905321234567 all work. |
emailstring · null clears it | Email. |
kindstring | Individual or business.individualcompany |
taxIdstring · null clears it | Tax number (10 digits) or Turkish ID number (11 digits). Never returned. |
taxOfficestring · null clears it | Tax office. |
citystring · null clears it | Province, spelled in Turkish: İstanbul, İzmir… |
districtstring · null clears it | District; send it together with the province. |
addressstring · null clears it | Street address, up to 300 characters. |
notesstring · null clears it | A note about the customer for your team. Never returned. |
smsOptOuttrue/false | The customer doesn't want SMS messages. |
curl -X PATCH "https://islemde.app/api/v1/customers/5b1e7f7a-0c1d-4a55-9d51-2f0c8f3b8a10" \
-H "x-api-key: $ISLEMDE_API_KEY" \
-H "content-type: application/json" \
-d '{
"email": "emre.kaya@example.com",
"smsOptOut": true
}'{
"data": {
"id": "5b1e7f7a-0c1d-4a55-9d51-2f0c8f3b8a10",
"kind": "individual",
"name": "Emre Kaya",
"phone": "+905441112233",
"email": "emre.kaya@example.com",
"city": "İzmir",
"district": "Karşıyaka",
"address": null,
"smsOptOut": true,
"createdAt": "2026-09-27T09:30:00.000Z"
}
}data
| Field | Description |
|---|---|
idstring | The customer's ID. |
kindstring | Individual or business.individualcompany |
namestring | Full name or company name. |
phonestring · can be null | Phone, with +90. |
emailstring · can be null | Email. |
citystring · can be null | Province. |
districtstring · can be null | District. |
addressstring · can be null | Street address. |
smsOptOuttrue/false | The customer doesn't want SMS messages. |
createdAtdate (ISO 8601) | When the record was created. |
Statuses
GET/api/v1/statuses
The stages in the business's workflow, in order. Use these keys when changing status and filtering jobs; they don't change even if the business renames a stage.
curl "https://islemde.app/api/v1/statuses" \
-H "x-api-key: $ISLEMDE_API_KEY"{
"data": [
{
"id": "d2b8c1e0-2f4a-4c1b-9a77-1f5e3c9b0a11",
"key": "received",
"label": "Teslim alındı",
"customerLabel": "Cihazınız teslim alındı",
"category": "open",
"isInitial": true
},
{
"id": "8a3f6d52-6b1e-4f0d-8c3e-7e2a9b4c5d22",
"key": "ready",
"label": "Teslime hazır",
"customerLabel": "Teslime hazır",
"category": "open",
"isInitial": false
},
{
"id": "4c7e9a13-0d2b-4e6f-b1a8-3f5c7d9e1b33",
"key": "delivered",
"label": "Teslim edildi",
"customerLabel": "Teslim edildi",
"category": "done",
"isInitial": false
}
]
}each record in data
| Field | Description |
|---|---|
idstring | The status's ID. |
keystring | A key that never changes: received, repairing, ready, delivered, cancelled… |
labelstring | Its name in the dashboard. |
customerLabelstring | Its name on the customer's tracking page. |
categorystring | The kind of status.openwaitingdonecancelled |
isInitialtrue/false | New jobs open with this status. |
How webhooks work
Instead of polling, get changes the moment they happen: when a job is opened, its status changes or a customer approves a quote, İşlemde sends a POST request to your URL. Add the URL under Settings → API → Webhook URLs and choose the events you want. The signing secret (whsec_…) is shown only once.
- The URL must start with
https://and use port 443 or 1024 and up (8443, for example). It can't contain a username or password; URLs that resolve to internal, local or reserved IP addresses are rejected and checked again on every delivery. - The body is the event type plus the same records as the REST API. Team-only notes, tax numbers and approval links are never sent.
- Every body includes
apiVersion(currently2026-10-01). Adding fields doesn't change the version; if a field is removed or its meaning changes, it gets a new date. - The Send test button sends a
pingevent to your URL; recent deliveries, response codes and errors show on the same page.
{
"id": "evt_3f9a1c2b7d8e4f60a1b2c3d4e5f60718",
"type": "job.status_changed",
"apiVersion": "2026-10-01",
"createdAt": "2026-09-25T13:02:11.482Z",
"data": {
"job": {
"number": 1047,
"trackingCode": "U7WX42A8",
"trackingUrl": "https://islemde.app/t/U7WX42A8",
"status": {
"key": "ready",
"label": "Teslime hazır",
"category": "open"
},
"item": {
"category": "Telefon",
"brand": "Apple",
"model": "iPhone 12"
},
"problem": "Ekran değişimi.",
"priority": "normal",
"customer": {
"name": "Nur Özkan",
"phone": "+905321234567"
},
"assignee": "Burak Demir",
"estimateKurus": 350000,
"chargeKurus": 350000,
"paidKurus": 100000,
"balanceKurus": 250000,
"paymentStatus": "partial",
"dueAt": "2026-09-27T15:00:00.000Z",
"createdAt": "2026-09-24T08:12:40.000Z",
"statusChangedAt": "2026-09-25T13:02:11.000Z",
"deliveredAt": null,
"cancelledAt": null
},
"previousStatus": {
"key": "repairing",
"label": "Onarımda",
"category": "open"
}
}
}| Header | Meaning |
|---|---|
islemde-event | The event type, e.g. job.status_changed. ping for a test delivery. |
islemde-delivery | This delivery's ID; it stays the same across retries. |
islemde-signature | t=<unix seconds>,v1=<signature>; for 24 hours after the secret is rotated there are two v1 values. Verification below. |
content-type | application/json |
user-agent | Islemde-Webhooks/1.0 |
Events
The job in data is the same job record as in the List jobs endpoint; customer matches the Add customer response.
| Event | When | data |
|---|---|---|
job.created | When a job is opened in the dashboard, through the API or from a check-in ticket. | job |
job.updated | When a job's details are edited. Not sent if only the team-only note changed. | job, changes (the fields that changed) |
job.status_changed | When a job moves to another status, including automatic moves after a quote is approved. | job, previousStatus |
job.delivered | When the device is handed over. Sent again if the handover is redone. | job, handover (who collected it, payment, whether free, signature; null when handed over by a status change) |
job.note_added | When a note visible to the customer is added. Internal notes aren't sent. | job, note |
quote.sent | When a quote is sent to the customer. | job, quote |
quote.approved | When the customer signs and approves a quote. | job, quote |
quote.rejected | When the customer declines a quote. | job, quote |
warranty.issued | When a warranty certificate is issued. | job, warranty |
payment.recorded | When a payment or refund is recorded: a deposit, at pickup or later, or through the API. | job (balanceKurus: the balance after the record), payment |
payment.voided | When a mistaken payment or refund record is voided. | job, payment (with voidedAt and voidReason set) |
customer.created | When a new customer is created in the dashboard, at check-in or through the API. Not sent for CSV imports. | customer |
Verifying the signature
Every request's islemde-signature header looks like t=1790000000,v1=5f2b…. v1 is the HMAC-SHA256 digest (lowercase hex) of t + "." + body using your signing secret. Compare it with your own in constant time, and reject t values older than 5 minutes. Use the body exactly as received, without parsing it: a single space breaks the signature.
// Node 18+ ve Express: npm install express
import crypto from "node:crypto";
import express from "express";
const app = express();
const secret = process.env.ISLEMDE_WEBHOOK_SECRET; // whsec_ ile başlar
// İmza ham gövde üzerinden hesaplanır: JSON'u ayrıştırmadan önce doğrulayın.
app.post("/islemde-webhook", express.raw({ type: "application/json" }), (req, res) => {
const parts = (req.get("islemde-signature") ?? "").split(",").map((part) => part.split("="));
const t = parts.find(([key]) => key === "t")?.[1];
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${req.body}`)
.digest("hex");
// Anahtar yenilendikten sonraki 24 saatte iki v1 gelir: biri tutması yeter.
const valid = parts.some(
([key, value]) =>
key === "v1" &&
value.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(value), Buffer.from(expected)),
);
// 5 dakikadan eski imzaları reddedin: yakalanıp yeniden gönderilen istekler geçmez.
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
if (!valid || !fresh) return res.sendStatus(400);
const event = JSON.parse(req.body);
// Aynı olay birden fazla gelebilir: event.id'yi saklayıp tekrarları atlayın.
res.sendStatus(200); // Önce cevap verin; uzun işleri sonra yapın.
console.log(event.type, event.data.job?.number);
});
app.listen(3000);When you rotate the secret, events carry two signatures for 24 hours: t=…,v1=<new>,v1=<old>. Try each v1 value; if one matches, the request is valid. That way you can update your server within that window without downtime.
Retries
Any non-2xx response, a wait over 5 seconds, a connection error or a redirect (3xx) counts as a failure; redirects aren't followed. A failed delivery is retried at these intervals: 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 24 hours. After the seventh attempt we give up; you can resend it by hand from the list under Settings → API.
If deliveries to a URL (retries included) fail 20 times in a row and nothing has got through for 24 hours, the URL is turned off and the business owner is notified. Short outages don't turn it off. Fix the URL and turn it back on from the same place; events that happen while it's off aren't sent.
Best practices
- Verify the signature first, then return
200right away; do long work in your own queue. Responses that take over 5 seconds are retried. - The same event can arrive more than once. Store each event's
idand skip the ones you've already handled. - Event order isn't guaranteed. For the current state, check the
statusChangedAtfield or reread the job withGET /api/v1/jobs/{number}. - If your URL was down for a long time, collect the changes in between with
changedSince. - Keep the signing secret as safe as your API key; rotate it if you think it has leaked.
Need another endpoint? Write to us from Support in the dashboard or at destek@islemde.app. Changes are announced on the What's new page.