Create a job
- Permissions
customers:read,jobs:create,jobs:read+ conditional- Safe retries
Idempotency-Key- Also available as
fieldtask_create_job
Creates one field job for an existing customer in the organization your credential is connected to.
A job can be unscheduled or have a start and end time. You can also attach a customer location, assign members and add catalog line items in the same request; these are saved together with the job or not at all.
This endpoint only creates. It never matches or updates an existing job. If you send the same body twice without the same Idempotency-Key, you get two jobs. See Idempotency and safe retries.
This page is generated from contract 2026-11-03-review.2, a review export that hasn't been released. Some optional inputs work only where FieldTask has enabled the feature.
Before you start
You need:
- API access for your organization. An owner or admin requests it in Settings → API connections. Until FieldTask enables it, every request returns
403 api_access_not_enabled. - A REST credential with the permissions below: an API token created in Settings → API connections, or an access token from an OAuth client-credentials connection. Tokens from an MCP sign-in don't work with the REST API.
- The ID of an existing customer. Find it with
GET /public/v1/customers, or create the customer first withPOST /public/v1/customers. Archived customers can be used. - Optional references, only if you use those fields: a location ID from
GET /public/v1/customers/{customer_id}/locations, member IDs fromGET /public/v1/members, and product and price IDs fromGET /public/v1/products.
Permissions
Every request needs all of these permissions:
| Permission | Dependency in the permission map |
|---|---|
customers:read | No dependencies |
jobs:create | Depends on customers:read, jobs:read |
jobs:read | No dependencies |
Some inputs need more:
| When | Additional permission | Including its dependencies |
|---|---|---|
assignee_member_ids is not empty | members:read | members:read |
items is not empty | job_items:create, job_items:read, products:read | job_items:create, job_items:read, jobs:read, products:read |
location_id is not null | customer_locations:read | customer_locations:read, customers:read |
Adding a permission to a connection doesn't happen automatically. An owner or admin edits it in Settings → API connections. API tokens keep the same token string when their permissions change. OAuth integrations must also request the new scope when they get an access token.
Request
Responses
- 201
- 400
- 401
- 403
- 404
- 405
- 409
- 410
- 413
- 415
- 422
- 429
- 503
Created
Response Headers
Responses must not be cached.
The operation key used for this create, including a server-generated key when omitted.
Server-generated request correlation ID.
Bad Request
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Unauthorized
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Forbidden
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Not Found
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Method Not Allowed
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Conflict
Response Headers
Responses must not be cached.
Seconds before retry; present on 429 and an in-progress operation.
Server-generated request correlation ID.
Gone
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Request Entity Too Large
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Unsupported Media Type
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Unprocessable Entity
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Too Many Requests
Response Headers
Responses must not be cached.
Seconds before retry; present on 429 and an in-progress operation.
Server-generated request correlation ID.
Service Unavailable
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Field guide
The request body is a single JSON object. Only name and customer_id are required.
| Field | Required | What to send |
|---|---|---|
name | Yes | The job title. Spaces at either end are removed; what remains must be 1–512 characters with no control characters. |
customer_id | Yes | ID of an active customer in this organization. Archived customers are accepted. |
description | No | Up to 1,024 characters. Tabs and line breaks are allowed; other control characters aren't. Defaults to null. |
location_id | No | ID of an active location that belongs to customer_id. Needs customer_locations:read. Defaults to null. |
schedule | No | Leave unscheduled, or give a start and end time. See Scheduling and time zones. Defaults to {"mode": "unscheduled"}. |
assignee_member_ids | No | Up to 50 distinct member IDs from GET /public/v1/members. See Assignees. Defaults to []. |
items | No | Up to 100 catalog line items saved with the job. See Line items. |
external_reference | No | Your system's identifier for this job. See External references. |
Anything else in the body is rejected with 422 validation_failed, including fields you might expect to set such as status, job_number or organization_id. The organization always comes from your credential.
Body and header rules
- Send
Content-Type: application/jsonwith an uncompressed body. AContent-Encodingheader is rejected with415. - The body can be at most 256 KiB and nest at most 10 levels deep.
- Duplicate property names,
NaNandInfinityare rejected with400 invalid_json. - The URL must have no query string and no trailing slash.
Scheduling and time zones
Omit schedule, or send {"mode": "unscheduled"}, to create an unscheduled job.
For a job with a time slot, send "mode": "timed" with starts_at and ends_at:
"schedule": {
"mode": "timed",
"starts_at": "2026-11-10T09:00:00-05:00",
"ends_at": "2026-11-10T11:30:00-05:00"
}
- Both times must be full timestamps with seconds and an explicit UTC offset, such as
-05:00, orZ. Up to six fractional-second digits are accepted. Timestamps without an offset, and the offset-00:00, are rejected. ends_atmust be later thanstarts_at, and the job can last at most 24 hours.- The API doesn't accept time-zone names. Use the offset in effect at the job site on that date, including any daylight-saving change.
- Jobs may overlap, including jobs for the same assignee.
- Timed scheduling must be enabled by FieldTask. Where it isn't, a timed
scheduleis rejected with422 validation_failed.
A timed job is created with status scheduled; an unscheduled job with status unscheduled.
Responses always return times in UTC with a Z suffix, so 09:00:00-05:00 comes back as 14:00:00Z. Compare instants, not strings. FieldTask uses your organization's time zone only to place the job on calendar days in the app; it doesn't change the times you send.
Assignees
assignee_member_ids takes member IDs: the id values returned by GET /public/v1/members. They aren't user IDs, and the API never falls back to a user ID.
- Each ID must belong to an active member of this organization. An organization owner can be assigned only through their own member ID.
- IDs must be distinct; you can assign up to 50 members.
- A non-empty list needs
members:read, and initial assignment must be enabled by FieldTask. Otherwise the request is rejected. - Assignees can only be set when the job is created. The public API can't change them afterwards.
When the connection has members:read, the response includes assignee_member_ids and, where available, assignees summaries.
Line items
Each entry in items adds a catalog product or service to the job, priced from the catalog when the job is created.
| Item field | Required | What to send |
|---|---|---|
product_id | Yes | ID of a product or service from GET /public/v1/products. |
price_id | Sometimes | ID of one of the product's named prices. Required when more than one price option applies. |
price_source | No | price or legacy. Send legacy to use the product's legacy unit price explicitly. |
quantity | No | Decimal string with up to 8 whole digits and 2 decimal places, such as "1" or "2.50". Defaults to "1.00". |
- Items are saved together with the job. If any item is invalid, no job is created.
- A non-empty
itemsarray needsjob_items:create,job_items:readandproducts:read, and job items must be enabled by FieldTask. - The job's
job_valueis the sum of quantity × unit price over its items, as a decimal string whose number of decimal places isn't fixed. It isn't an amount paid.
External references
external_reference stores your own system's identifier for the job, so you can find it again without keeping FieldTask IDs.
- It's 1–128 characters, can't be only whitespace and can't contain control characters.
- It's stored exactly as sent: case-sensitive and never trimmed.
- It's unique per organization and client application. A second job with the same reference is rejected with
409 external_reference_conflict; the existing job isn't changed. - It isn't returned in the job body. Look a job up with
GET /public/v1/jobs?external_reference=…. That parameter can't be combined with any other.
An external reference is not an idempotency key. Use both: the reference identifies the record in your system, and the key makes one request safe to retry.
Idempotency and safe retries
Networks fail after a request is sent. The Idempotency-Key header lets you retry without creating a duplicate job.
- Before sending, generate a new key for the job, such as a UUID, and save it with your own record.
- Send the request with that key.
- If you get no response, a timeout or a
5xx, send the same body with the same key again, with backoff.
How the server treats a key:
| Situation | Result |
|---|---|
| New key | The job is created. Response 201, Idempotency-Replayed: false. |
| Same key, same body, within 7 days | The original result is returned again. No second job is created and it isn't counted against your daily create allowance. Response 201, Idempotency-Replayed: true. |
| Same key, different body | 409 idempotency_key_reused. Nothing is created. |
| Same key while the first request is still running | 409 operation_in_progress with Retry-After. Retry with the same key and body. |
| Same key more than 7 days later | 409 idempotency_window_expired. A key can never be reused for a different job. |
Details that matter:
- Keys are 16–128 printable ASCII characters.
- "Same body" means the same JSON value. Property order and whitespace don't matter, but leaving a field out is different from sending its default: a body without
descriptiondoesn't match one with"description": null. - Keys are scoped to the organization, the connection and the kind of request. Rotating a token within a connection keeps the scope. Sending the same key through a different connection is treated as a new request and creates another job.
- Replays are authorized again. If the connection has lost a permission since the first request, the replay is rejected rather than returned.
- Without a key, the server generates one and returns it in the
Idempotency-Keyresponse header. That only helps if you receive the response, so always send your own key.
Local creation and external synchronization
A 201 response means the job is saved in FieldTask. Nothing further is needed for the job to exist, and later follow-up work never removes it.
Some work can run separately after the job is saved, such as geocoding the job's location address, or sending the job to an external system your organization has connected. Its progress is reported on the operation, not on the job.
To check it, read the Operation-Location URL from the 201 response with the same credential (needs jobs:read):
curl "$FIELDTASK_API_ORIGIN/public/v1/operations/OPERATION_ID" \
--header "Authorization: Bearer $FIELDTASK_API_TOKEN"
data.local_status is always committed. data.integration.status summarizes the follow-up work:
integration.status | Meaning |
|---|---|
not_applicable | There's no follow-up work for this job. |
pending | Follow-up work is queued or running. |
succeeded | All follow-up work finished. |
partial | Some follow-up work finished; the rest was blocked or failed. |
blocked | Follow-up work can't run until something is configured or reconciled. |
failed | Follow-up work failed. |
outcome_unknown | FieldTask couldn't confirm whether follow-up work took effect. Check the external system before doing anything that could duplicate it. |
Each entry in integration.targets has its own status and, when it didn't succeed, an error_code: provider_unavailable, authorization_required, invalid_upstream_data, reconciliation_required or configuration_required. A target doesn't say what kind of work it is, so don't infer geocoding or CRM synchronization from its id.
In the review contract, jobs created through the API aren't sent to a connected CRM. If your organization has external job synchronization turned on, the operation reports a blocked target with configuration_required. The job itself is still created.
Operation results are kept for seven days. After that, GET /public/v1/operations/{id} returns 410 operation_result_expired. That doesn't mean the job wasn't created.
Recovering from failures
| What happened | What to do |
|---|---|
Timeout, connection error or 5xx | Retry the same body with the same key, with exponential backoff. |
409 operation_in_progress | Wait for Retry-After seconds and retry with the same key. |
429 | Wait for Retry-After seconds and retry with the same key. |
201 received | Store data.id and the Location and Operation-Location headers. Stop retrying. |
| Key older than 7 days, or the result expired | Don't create again yet. Look for the job with GET /public/v1/jobs?external_reference=… or your saved job ID. |
Never switch to a new key just because a response was lost. A new key means a new job.
Errors
Errors use application/problem+json. The top-level code is the stable machine-readable value; error adds a readable message, a suggested fix and a documentation link:
{
"type": "about:blank",
"title": "Unprocessable Entity",
"status": 422,
"code": "validation_failed",
"detail": "The request contains invalid fields.",
"instance": "urn:uuid:99999999-9999-4999-8999-999999999993",
"request_id": "99999999-9999-4999-8999-999999999993",
"error": {
"code": "validation_failed",
"message": "The request contains invalid fields.",
"fix": "Check the documented request fields.",
"docs_url": "https://api.fieldtask.example/public/docs/"
}
}
error.code usually equals code. For a missing permission, code is insufficient_scope and error.code is missing_permission. Validation errors don't say which field failed, and error responses never echo submitted values.
| Status | code | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_query | The URL has a query string. Create requests accept none. | Send the request to /public/v1/jobs with no query parameters. |
| 400 | invalid_json | The body isn't one valid JSON value, repeats a property name, uses NaN/Infinity or nests deeper than 10 levels. | Send a single JSON object encoded as UTF-8. |
| 400 | invalid_operation_key | Idempotency-Key isn't 16–128 printable ASCII characters. | Use a UUID or another 16–128 character key without spaces at either end. |
| 401 | invalid_token | The bearer token is missing, malformed, expired, revoked or issued for a different resource (for example an MCP sign-in). | Send a current REST API token or REST OAuth access token as Authorization: Bearer …. |
| 403 | api_access_not_enabled | FieldTask hasn't enabled API access for this organization, or has disabled it. | An owner or admin requests access in Settings → API connections. |
| 403 | insufficient_scope | The connection lacks a permission this request needs, including permissions triggered by location_id, assignee_member_ids or items. error.code is missing_permission. | Add the permission named in error.message to the connection, then retry with the same key. |
| 403 | operation_forbidden | The connection's access changed while the request was being processed, or a replay would return fields the connection can no longer read. | Check the connection's permissions, then retry with the same key and body. |
| 404 | resource_not_found | The public API or job creation isn't available in this environment, or the URL is wrong. | Check the API origin and path (no trailing slash). |
| 405 | method_not_allowed | Job creation isn't enabled in this environment. The Allow header lists the methods that are. | Contact FieldTask if you expected job creation to be available. |
| 409 | external_reference_conflict | Another job created through this client application already uses this external_reference. Nothing was updated. | Find the existing job with GET /public/v1/jobs?external_reference=…. Use a new reference only for a genuinely new job. |
| 409 | idempotency_key_reused | This key was already used with a different body. | Resend the original body with this key, or use a new key for a different job. |
| 409 | operation_in_progress | A request with the same key is still being processed. | Wait for Retry-After seconds, then retry with the same key and body. |
| 409 | idempotency_window_expired | This key was used more than seven days ago. It can't be replayed or reused. This doesn't mean the original request failed. | Reconcile using your saved job ID or external_reference before creating anything new. |
| 410 | resource_gone | You replayed an idempotency key, but the job it created has since been deleted. | Don't recreate the job automatically. Check in FieldTask whether it was removed on purpose. |
| 413 | payload_too_large | The body is larger than 256 KiB. | Reduce the body, for example by sending fewer line items. |
| 415 | unsupported_media_type | Content-Type isn't application/json, or the body is compressed. | Send an uncompressed body with Content-Type: application/json. |
| 422 | validation_failed | A field is missing, unknown or invalid; a referenced customer, location, member or product isn't eligible; or the input uses a capability that isn't enabled for your organization (such as timed scheduling). | Check the request against the field guide. The response doesn't name the field. |
| 429 | rate_limit_exceeded | Too many requests in the current minute, or too many concurrent requests, for the organization or connection. | Wait for Retry-After seconds. Retry creates with the same key. |
| 429 | daily_quota_exceeded | The organization or connection has used its daily create allowance. Allowances reset at 00:00 UTC. | Wait for Retry-After seconds, or check remaining allowance in Settings → API connections. |
| 503 | temporarily_unavailable | The service couldn't complete the request. The job may or may not have been created. | Retry with the same key and body, with exponential backoff. |
Every response, including errors, has an X-Request-ID header. Include it, with the time and the status code, when you contact support. Never send tokens or secrets.
Limits
Requests are limited per minute and by concurrency, for both the organization and each connection, and both have a daily create allowance that resets at 00:00 UTC. FieldTask sets these limits per organization, and a connection can have a lower allowance than its organization. Owners and admins can see remaining allowances in Settings → API connections.
When you hit a limit you get 429 with a Retry-After header in seconds. Replaying an already-created job with its original key doesn't count as another create.
Response headers
A successful create returns 201 Created with these headers:
| Header | Always present | Value |
|---|---|---|
Cache-Control | Yes | Always no-store |
Idempotency-Key | Yes | string |
Idempotency-Replayed | Yes | true or false |
Location | Yes | uri-reference |
Operation-Location | Yes | uri-reference |
X-Request-ID | Yes | uuid |
The server also returns the Idempotency-Key that was used, including one it generated.