Making changes

<!-- order: 9 | Products, packages, verifications and their actions, facilities, clients and brands: how a change is checked, saved and confirmed. -->

Your TA's key can add and change its own records: products, packages, verifications (and put them on hold, release, withdraw, terminate, transfer, cancel or renew them), the facilities on a verification, client applications and brands. Every endpoint is in the reference under Writes.

ScopeFor
verification.writeProducts, packages, verifications and their actions, facilities on a verification
client.writeClient applications and their details, brands

Try it in the sandbox first

Under /sandbox/v2, every change runs every check (your records, the values Salesforce accepts, the rules below) and answers with exactly what would be written, without saving anything:

curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"on": "2026-11-11"}' https://api.foodintegrityproject.org/sandbox/v2/verifications/V-0123456/hold
{ "verification": "V-0123456", "action": "hold", "from": "Verified", "to": "On Hold", "result": "sandbox",
  "would_write": [{ "object": "Verification__c", "operation": "update", "fields": { "Verification_Status__c": "On Hold", "On_Hold_Date__c": "2026-11-11" } }] }

Your own token works in the sandbox. In production, changes are made by a TA's key.

How a change is saved

While Salesforce is still the system of record, each change is saved there first, made by the FIP-OS System Writer on your TA's behalf, and the answer comes from Salesforce's reply: a new verification's V- reference or a new package's number comes back in the answer. The read endpoints show the change within about five minutes. If Salesforce doesn't answer in time, you get 202 with result: queued, and it's sent again within two minutes.

Each kind of change is switched on separately. Until it is, it answers 503 writes_not_switched_on.

Rules that differ from v1

  • Only your records. A product, verification, package or client of another TA is not found (404). v1 let any TA change any record.
  • Statuses change only by action: POST /verifications/{ref}/hold, /release, /withdraw, /terminate, /transfer, /cancel, /renew. A PATCH with a status is refused.
  • Withdrawal keeps two dates: when it was asked for (requested_on) and, 180 days later, when the listing ends, as the Program Rules say.
  • Renew carries the cycle's active facilities to the new one (v1's is_renewal did nothing).
  • Values are checked against the lists Salesforce accepts (package units and types, withdrawal reasons and so on), and unknown fields are refused, never silently dropped.
  • A completed date more than 7 days back is accepted, with a warning in the answer.
  • One change at a time per record: while one is on its way, another answers 409 change_in_progress.

Retrying safely

Send an Idempotency-Key header (a UUID is good) with every POST, PUT, PATCH and DELETE. If the answer is lost and you send the same request with the same key within 24 hours, you get the first answer back (with Idempotent-Replayed: true) instead of a second change. The same key with a different request is refused (422).

Not overwriting someone else's change

Every GET of one record answers with an ETag. Send it back as If-Match with your change: if the record changed since you read it, you get 412 and nothing is saved. Salesforce also refuses a change on top of a newer version of the record (412 changed_since, with the current version in current).

Many changes at once

POST /v2/batch takes up to 500 operations, each {method, path, body, idempotency_key}, runs them in order and answers each separately (207). One failing doesn't stop the others.

Facilities, countries of origin and contacts

  • A new facility: POST /v2/facilities with its name, address and your own facility_id. One with the same name and postal code is reused. Then add it to a verification with PUT /v2/verifications/{ref}/facilities/{facility_id}, using your own ID.
  • Countries of origin: POST /v2/products/{id}/countries-of-origin for each input, with its country (an ISO code such as US), quarter and year; GET lists them, with their eligibility once reviewed.
  • Categories: remove_categories on PATCH /v2/products/{id} removes them.
  • Contacts: PUT or DELETE /v2/clients/{id}/contacts/{role} for the main, secondary or marketing contact.
  • All of a client's products: POST /v2/clients/{id}/hold-all and /withdraw-all, each verification answered separately.