Creating a job
POST {BASE_URL}/jobsA job is created for a customer who already exists, at one of their sites, against one of their contacts. Find those three first — see finding a customer — then send their IDs.
If the customer is new, this is the wrong endpoint: create a request instead, which can carry the customer’s details and let the office turn it into work.
Fields
Section titled “Fields”| Field | Required | Notes |
|---|---|---|
| external_id | No | Your system’s own reference. Must be unique if sent; a repeat returns 409. |
| job_code | No | Shown as the job’s label in EyeOnTask. |
| description | No | What the work is. |
| instruction | No | How to go about it — instructions for the fieldworker. |
| priority_id | No | A numeric ID from GET /priorities. Falls back to the company’s default. |
| customer.id | Yes | From GET /customers. |
| customer.site.id | Yes | From GET /customers/{id}/sites. |
| customer.contact.id | Yes | From GET /customers/{id}/contacts. |
| customer.name, site.name, contact.name | No | Informational only — the IDs are authoritative. A name that disagrees with its ID changes nothing. |
| job_types[] | Yes | A non-empty array; each entry carries a job_type_id. |
| schedule.start_at | No | ISO-8601 with an offset or Z. Required if end_at is sent. |
| schedule.end_at | No | Omit it and the job gets a one-hour duration. |
| schedule.display_text | No | Free text shown beside the schedule — it appears on the scheduler strip. |
Send external_id. It is optional to the API and essential to you: it is what stops a retry, a replayed webhook in your own system or a re-run batch creating the same job twice, and it is how you find the EyeOnTask job again from your side later.
The job arrives Not Dispatched
Section titled “The job arrives Not Dispatched”Every job created through the API gets the default status Not Dispatched, and the caller cannot set or change it.
That is the right default rather than a limitation: the job is on the board for the office to look at, but nothing has gone to anybody’s phone. Somebody decides who is going and dispatches it.
From there the job’s life is inside EyeOnTask, and the API will not tell you about it — there is no endpoint to read a job or its status back.
Example request
Section titled “Example request”{ "external_id": "CRM-JOB-10001", "job_code": "JOB-10001", "description": "Annual AC maintenance", "instruction": "Call before arrival", "priority_id": 2, "customer": { "id": 501, "name": "Acme Services", "site": { "id": 701, "name": "Main Office" }, "contact": { "id": 901, "name": "Ravi Kumar" } }, "job_types": [ { "job_type_id": 12 } ], "schedule": { "start_at": "2026-09-10T10:00:00+05:30", "end_at": "2026-09-10T11:00:00+05:30", "display_text": "Thursday morning" }}Rules worth restating, because they are where the 422s come from:
- A datetime must carry a timezone offset or
Z. end_atwithoutstart_atis rejected.start_aton its own is fine — the job becomes an hour long.
What comes back
Section titled “What comes back”{ "success": true, "data": { "id": 88001, "external_id": "CRM-JOB-10001", "label": "JOB-10001", "status": "Not Dispatched", "priority": { "id": 2, "name": "medium" }, "customer": { "id": 501, "name": "Acme Services" }, "site": { "id": 701, "name": "Main Office" }, "contact": { "id": 901, "name": "Ravi Kumar", "email": "customer@example.com", "mobile_number": "+919876543210" }, "job_types": [ { "id": 12, "name": "Air Conditioning" } ], "schedule": { "start": "2026-09-10T04:30:00Z", "end": "2026-09-10T05:30:00Z", "display_text": "Thursday morning" }, "created_at": "2026-09-07T12:00:00Z" }, "request_id": "req_abc123", "timestamp": "2026-09-07T12:00:00Z"}The schedule comes back in UTC — the +05:30 you sent is the same moment as the Z you get.
Persist id, external_id and request_id. The first is how EyeOnTask knows the job, the second is how you do, and the third is what support needs if the two ever disagree.
Send the create with an Idempotency-Key so a timeout can be retried without risking a second job.