Skip to main content

Add a job item

POSThttps://api.fieldtask.example/public/v1/jobs/{job_id}/items
Permissions
job_items:create, job_items:read, jobs:read, products:read
Safe retries
Idempotency-Key
Also available as
fieldtask_create_job_item

Adds one catalog product or service to an existing job as a line item, priced from the catalog.

You choose the product, one of its price options and a quantity. FieldTask copies the product's name, description and type and the option's price onto the job. The item keeps those values even if the catalog changes later. You can't set a custom name or price.

This endpoint only creates. It never matches or updates an existing item, so adding the same product twice gives the job two items. If you send the same body twice without the same Idempotency-Key, you get two items. See Idempotency and safe retries.

To add items while you create a job, use the items field of Create a job instead. It follows the same rules.

Preview

This page is generated from contract 2026-11-03-review.2, a review export that hasn't been released. Job items work only where FieldTask has enabled them.

Before you start​

You need:

  1. 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.
  2. 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.
  3. The ID of an existing job. Find it with GET /public/v1/jobs, or use data.id from POST /public/v1/jobs.
  4. A product ID and, usually, a price ID from GET /public/v1/products. The product must be available, and you may need to choose a price. See Choosing a price.

Permissions​

Every request needs all of these permissions:

PermissionDependency in the permission map
job_items:createDepends on job_items:read, jobs:read, products:read
job_items:readDepends on jobs:read
jobs:readNo dependencies
products:readNo dependencies

No inputs need additional permissions.

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​

Created

Response Headers
    Cache-Control

    Responses must not be cached.

    Idempotency-Key

    The operation key used for this create, including a server-generated key when omitted.

    Idempotency-Replayed
    Location
    Operation-Location
    X-Request-ID

    Server-generated request correlation ID.

Field guide​

The job is identified by job_id in the URL. It must be a job in your organization that hasn't been deleted.

The request body is a single JSON object. Only product_id is required.

FieldRequiredWhat to send
product_idYesID of a product or service from GET /public/v1/products. It must be approved in your catalog and have is_available: true.
price_idSometimesID of one of the product's named price options. Required when the product has more than one eligible option. See Choosing a price.
price_sourceNoprice or legacy. Send legacy to charge the product's legacy unit price.
quantityNoDecimal string, such as "1", "2.5" or "2.50". Defaults to "1.00". See Quantity.

Anything else in the body is rejected with 422 validation_failed, including fields you might expect to set such as name, unit_price, status or allow_evidences.

Body and header rules​

  • Send Content-Type: application/json with an uncompressed body. A Content-Encoding header is rejected with 415.
  • The body can be at most 256 KiB and nest at most 10 levels deep.
  • Duplicate property names, NaN and Infinity are rejected with 400 invalid_json.
  • The URL must have no query string and no trailing slash.

Choosing a price​

FieldTask never picks a price for you. The request must identify exactly one of the product's eligible price options.

A product's eligible options are the entries in its prices array whose currency is your organization's currency. That includes the legacy unit price (source: "legacy"), which is listed only when it's greater than zero, and every named option (source: "price"), including recurring ones and zero-price ones.

FieldTask narrows the eligible options by what you send:

  • price_id keeps only the named option with that ID. Send it exactly as returned by the products endpoint.
  • price_source keeps only options with that source.

If exactly one option is left, it's used. If none or more than one is left, the request is rejected with 422 validation_failed.

The product's eligible optionsWhat to send
One option of any kindNothing. You can still send its price_id or price_source.
Several named optionsThe chosen option's price_id.
The legacy price and one named optionprice_source: "legacy" for the legacy price; price_id or price_source: "price" for the named option.
The legacy price and several named optionsprice_source: "legacy" for the legacy price; price_id for a named option.
None (empty prices, or only other currencies)The product can't be added through the API.

When several options have the same amount, they're still different options. Choose by name and send the matching price_id; don't pick one because the amounts agree. If you're acting for a person, ask them which option they want.

A recurring option is charged once as the item's unit price. It doesn't create a subscription or a recurring job.

Quantity​

  • quantity is a string, not a JSON number.
  • Up to 8 digits before the decimal point and up to 2 after: "1", "2.5" and "99999999.99" are valid.
  • No sign, no exponent and no leading zeros: "-1", "1e2" and "01" are rejected.
  • "0" is accepted.
  • Responses always show two decimal places: "1" comes back as "1.00".

What's saved on the job​

The 201 response returns the new item as Get a job item would:

Item fieldValue
name, description, item_typeCopied from the product.
price_nameThe chosen option's name; null for the legacy price.
unit_priceThe chosen option's amount.
quantityYour quantity, with two decimal places.
amountquantity × unit_price exactly, with four decimal places, such as "185.0000".
currencyYour organization's currency.
statusto do.
allow_evidencesfalse.

The item doesn't keep a link to the product or price. Later changes to the catalog, including price changes or deleting the product, don't change existing items.

Effect on the job​

  • The job's job_value increases by the item's amount. Read it with Get a job.
  • The API doesn't check the job's status. You can add items to a job in any status, including completed and cancelled. Check status first if your workflow shouldn't change finished jobs.
  • Quotes and invoices keep their own line items. Adding a job item doesn't change any quote or invoice.
  • Changes to job_value aren't covered by the job's revision, and job items have no events. The event feed and webhooks don't report added items.

Idempotency and safe retries​

Send an Idempotency-Key header with every create, and reuse the same key and body when you retry after a timeout, a connection error, a 5xx, 409 operation_in_progress or 429. A replay within seven days returns the original item with Idempotency-Replayed: true and adds nothing.

The full rules, including key format, scoping and the seven-day window, are in Idempotency and recovery. Two points are specific to job items:

  • The job is part of the request. Sending the same key and body to a different job's URL returns 409 idempotency_key_reused.
  • Removed items aren't replayed. If the item or its job has since been deleted, replaying the key returns 410 resource_gone.

Job items have no external reference, so the key is your only protection against duplicates. Save it with your own record before you send.

Local creation and external synchronization​

A 201 response means the item is saved on the job. Adding a job item doesn't queue any follow-up work: the operation at Operation-Location always reports integration.status: "not_applicable". Reading it needs job_items:read and jobs:read. See Get an operation.

Operation results are kept for seven days. After that, GET /public/v1/operations/{id} returns 410 operation_result_expired. That doesn't mean the item wasn't added.

Recovering from failures​

What happenedWhat to do
Timeout, connection error or 5xxRetry the same body with the same key, with exponential backoff.
409 operation_in_progress or 429Wait for Retry-After seconds and retry with the same key.
201 receivedStore data.id and the Location and Operation-Location headers. Stop retrying.
422 validation_failedRead the product again with GET /public/v1/products/{id}. Check that it's available and choose a price as described above.
Key older than 7 days, or the result expiredDon't add the item again yet. List the job's items with GET /public/v1/jobs/{job_id}/items and compare.

Never switch to a new key just because a response was lost. A new key means a new item.

Errors​

Errors use application/problem+json. The top-level code is the stable machine-readable value. 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. See Errors.

StatuscodeMeaningWhat to do
400invalid_queryThe URL has a query string. Create requests accept none.Send the request to /public/v1/jobs/{job_id}/items with no query parameters.
400invalid_jsonThe 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.
400invalid_operation_keyIdempotency-Key isn't 16–128 printable ASCII characters.Use a UUID or another 16–128 character key without spaces at either end.
401invalid_tokenThe 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 ….
403api_access_not_enabledFieldTask hasn't enabled API access for this organization, or has disabled it.An owner or admin requests access in Settings → API connections.
403insufficient_scopeThe connection lacks job_items:create, job_items:read, jobs:read or products:read. error.code is missing_permission.Add the permission named in error.message to the connection, then retry with the same key.
403operation_forbiddenThe connection's access changed while the request was processed, product details aren't readable in this environment, 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.
404resource_not_foundNo job with this ID exists in your organization, the job has been deleted, job item creation isn't available in this environment, or the URL is wrong.Check the job ID with GET /public/v1/jobs, and check the API origin and path (no trailing slash).
405method_not_allowedJob item creation isn't enabled in this environment. The Allow header lists the methods that are.Contact FieldTask if you expected job item creation to be available.
409idempotency_key_reusedThis key was already used with a different body or for a different job.Resend the original body to the original job with this key, or use a new key for a different item.
409operation_in_progressA request with the same key is still being processed.Wait for Retry-After seconds, then retry with the same key and body.
409idempotency_window_expiredThis key was used more than seven days ago. It can't be replayed or reused. This doesn't mean the original request failed.List the job's items with GET /public/v1/jobs/{job_id}/items before adding anything new.
410resource_goneThis key added an item that has since been removed, or whose job has been deleted.Don't add it again automatically. Check in FieldTask whether it was removed on purpose.
413payload_too_largeThe body is larger than 256 KiB.Send only the documented fields.
415unsupported_media_typeContent-Type isn't application/json, or the body is compressed.Send an uncompressed body with Content-Type: application/json.
422validation_failedA field is missing, unknown or invalid; the product doesn't exist, isn't approved or isn't available; or price_id and price_source don't identify exactly one eligible price option.Read the product with GET /public/v1/products/{id} and choose a price as described in Choosing a price. The response doesn't name the field.
429rate_limit_exceededToo many requests in the current minute, or too many concurrent requests, for the organization or connection.Wait for Retry-After seconds, then retry.
429daily_quota_exceededThe organization or connection has used its daily allowance for this kind of request. Allowances reset at 00:00 UTC.Wait for Retry-After seconds, or check remaining allowance in Settings → API connections.
503temporarily_unavailableThe service couldn't complete the request. The item may or may not have been added.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​

Adding a job item counts against the same per-minute, concurrency and daily create allowances as other creates. A replay with the original key doesn't use another create. See Rate limits and quotas.

Response headers​

A successful create returns 201 Created with these headers:

HeaderAlways presentValue
Cache-ControlYesAlways no-store
Idempotency-KeyYesstring
Idempotency-ReplayedYesstring
LocationYesstring
Operation-LocationYesstring
X-Request-IDYesuuid

Location is the item's path, /public/v1/jobs/{job_id}/items/{id}. The server also returns the Idempotency-Key that was used, including one it generated.