Create a product
- Permissions
products:create,products:read- Safe retries
Idempotency-Key- Also available as
fieldtask_create_product
Creates one catalog product or service, with 1–20 one-time prices, in the organization your credential is connected to.
The product and all of its prices are saved together or not at all. The new product is ready to use straight away: it appears in List products, and you can add it to jobs with its price IDs.
This endpoint only creates. It never matches or updates an existing product, and it doesn't check whether a product with the same name already exists. If you send the same body twice without the same Idempotency-Key, you get two products. Search with GET /public/v1/products?search=… first if you're not sure. See Idempotency and safe retries.
This page is generated from contract 2026-11-03-review.2, a review export that hasn't been released. Product creation works only where FieldTask has enabled it.
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.
No other records are needed. Prices are created with the product, so you don't send any IDs.
Permissions
Every request needs all of these permissions:
| Permission | Dependency in the permission map |
|---|---|
products:create | Depends on products:read |
products:read | No 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
- 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. name, item_type and prices are required.
| Field | Required | What to send |
|---|---|---|
name | Yes | The product or service name, 1–256 characters. It can't be only spaces. Spaces at either end are removed before saving. |
item_type | Yes | product, service or other. |
prices | Yes | 1–20 price options. See Prices. |
description | No | Up to 4,096 characters, saved exactly as sent. Omit it to leave the description empty (null); sending null is rejected. |
is_available | No | true or false. Defaults to true. Products with is_available: false stay in the catalog but can't be added to jobs through the API. |
Anything else in the body is rejected with 422 validation_failed, including fields such as id, unit_price, organization_id, images or stock levels. The organization always comes from your credential.
Prices
Each entry in prices becomes one named price option with its own ID.
| Price field | Required | What to send |
|---|---|---|
name | Yes | The option's label, such as Standard or After hours, 1–256 characters. It can't be only spaces. Spaces at either end are removed. |
amount | Yes | A decimal string. See Amounts and currency. |
currency | No | Three uppercase letters, such as USD. Defaults to your organization's currency. |
price_type | No | Only one_time is accepted, and it's the default. You can't create recurring prices. |
- Give each option a name your users will recognize. When a product has more than one price, anyone adding it to a job has to choose one by ID, and an AI client asks the user to choose by name.
- You can't add, change or remove prices later through the API. Plan the full set of options in this request.
Amounts and currency
amount is a string, not a JSON number, so amounts are never rounded in transit.
- Up to 13 digits before the decimal point and up to 2 after:
"0","185","185.5"and"185.50"are all valid. - No sign, no exponent, no thousands separators and no leading zeros:
"-5","1e3","1,250.00"and"0185"are rejected. "0"is accepted and creates a zero-price option.- Responses always show two decimal places:
"185"comes back as"185.00".
FieldTask stores the currency you send, or your organization's currency if you omit it, and never converts amounts. Only prices in your organization's currency can be added to jobs through the API, so leave currency out unless you have a reason to set it.
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.
The created product
The 201 response returns the product as Get a product would:
priceslists every option with its newid,source: "price"andcurrency_source: "price". Options are listed newest first, so they can come back in a different order from your request. Match them byname.- Products created through the API have no legacy unit price, so there's no
source: "legacy"option. - The product is approved in your catalog immediately. Products that reach FieldTask in other ways may need approval before the API shows them.
To add it to a job, use Add a job item or the items field of Create a job with the product's id and the id of the price to charge.
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 product with Idempotency-Replayed: true and creates nothing. A key is tied to this kind of request, so the same key on Create a job is a separate request.
The full rules, including key format, scoping and the seven-day window, are in Idempotency and recovery.
If the product has since been deleted or is no longer approved in the catalog, replaying its key returns 410 resource_gone instead of the original result.
Local creation and external synchronization
A 201 response means the product and its prices are saved in FieldTask and can be used straight away. Nothing further is needed, and later follow-up work never removes them.
If your organization has a connected CRM set up to receive products created in FieldTask, the product is also queued for external synchronization. That work runs separately and is reported on the operation, not on the product. Read the Operation-Location URL from the 201 response with the same credential (needs products:read):
curl "$FIELDTASK_API_ORIGIN/public/v1/operations/OPERATION_ID" \
--header "Authorization: Bearer $FIELDTASK_API_TOKEN"
- Without a connected CRM receiving products,
data.integration.statusisnot_applicable. - With one, the operation has a target for the product. Once the product has been sent, a further target is added for each price, so
integration.targetscan grow between reads. - Synchronization is attempted once. If its outcome can't be confirmed, the target reports
outcome_unknownwithreconciliation_requiredand isn't retried automatically. Check the connected CRM before doing anything else; creating the product again in FieldTask creates a second product.
See Get an operation for every status and error code.
In the review contract, FieldTask hasn't enabled product synchronization. If your organization's connected CRM is set to receive products, the operation reports a blocked target with configuration_required. The product 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 product 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 or 429 | Wait for Retry-After seconds and retry with the same key. |
201 received | Store data.id, each price id, and the Location and Operation-Location headers. Stop retrying. |
| Key older than 7 days, or the result expired | Don't create again yet. Search with GET /public/v1/products?search=… for a product with the same name. |
Never switch to a new key just because a response was lost. A new key means a new product.
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.
| 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/products 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 products:create or products:read. 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 processed, or the result would include data the connection can no longer read. | Check the connection's permissions, then retry. Retry creates with the same key and body. |
| 404 | resource_not_found | Product 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 | Product creation isn't enabled in this environment. The Allow header lists the methods that are. | Contact FieldTask if you expected product creation to be available. |
| 409 | idempotency_key_reused | This key was already used to create a product with a different body. | Resend the original body with this key, or use a new key for a different product. |
| 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. | Search with GET /public/v1/products?search=… before creating anything new. |
| 410 | resource_gone | This key created a product that has since been deleted or is no longer approved in the catalog. | Don't create it again automatically. Check in FieldTask whether it was removed on purpose. |
| 413 | payload_too_large | The body is larger than 256 KiB. | Shorten the description or price names. |
| 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 required field is missing; a field is unknown; a name is empty or only spaces; item_type isn't product, service or other; prices has fewer than 1 or more than 20 entries; or an amount, currency or price_type isn't valid. | 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, then retry. |
| 429 | daily_quota_exceeded | The 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. |
| 503 | temporarily_unavailable | The service couldn't complete the request. The product 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
Product creates count 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:
| Header | Always present | Value |
|---|---|---|
Cache-Control | Yes | Always no-store |
Idempotency-Key | Yes | string |
Idempotency-Replayed | Yes | string |
Location | Yes | string |
Operation-Location | Yes | string |
X-Request-ID | Yes | uuid |
Location is the product's path, /public/v1/products/{id}. The server also returns the Idempotency-Key that was used, including one it generated.