Skip to content

REST API Contracts

Ed Mozley edited this page Jul 3, 2026 · 1 revision

πŸ“œ REST API: Contracts

The complete usage guide for the Contracts module of the REST API β€” contracts, term tabs, suppliers and their contacts. Mirrors the interactive documentation at System β†’ API β†’ Documentation (with its live "Try it" tester).

Note

New to the API? The basics table on REST API: Tickets covers base URL, authentication, the response envelope, error codes, rate limits and PATCH semantics β€” identical across all modules.

What's distinctive about Contracts:

⏳ Renewal filters built in expiring_within_days, notice_within_days and expired are the module's own dashboard/Watchtower SQL shapes exposed as parameters β€” renewal reports are one call.
πŸ”’ Contract numbers can't collide via the API The UI has no uniqueness check on contract_number; the API refuses duplicates with a 409 pointing at the existing contract.
🏭 Suppliers get their full record The lite picker list stays at GET /suppliers (under reference.read); the full CRUD β€” registration/VAT, address, questionnaire dates, contacts β€” lives under the new suppliers permission. The API can also set supplies_assets (the flag the Assets module filters suppliers by), which the UI never writes.
πŸ“„ Documents are links, not uploads The module stores external DMS URLs (dms_link, dpia_dms_link) β€” plain string fields, no file upload exists or is needed.
πŸšͺ The RFP Builder is not exposed It's internal-only by design (suppliers never log in) and stays UI-only.

πŸš€ Quick start

Register a supplier and its contract, with renewal tracking from day one:

B="https://your-server/api/v1"; K="Authorization: Bearer fitsm_…"

# 1. Supplier (flagged as an asset supplier so it appears in Assets pickers)
SUP=$(curl -s -X POST "$B/suppliers" -H "$K" -H "Content-Type: application/json" -d '{
  "legal_name": "Acme Print Ltd", "trading_name": "Acme Print", "supplies_assets": true
}' | jq -r '.data.id')

# 2. Contract with value, end date and notice date
curl -s -X POST "$B/contracts" -H "$K" -H "Content-Type: application/json" -d "{
  \"contract_number\": \"CN-2026-014\", \"title\": \"Managed print services\",
  \"supplier_id\": $SUP,
  \"contract_start\": \"2026-08-01\", \"contract_end\": \"2028-07-31\",
  \"notice_period_days\": 90, \"notice_date\": \"2028-05-02\",
  \"contract_value\": 24000, \"currency\": \"GBP\"
}"

πŸ“œ Contracts

🟒 GET /contracts β€” list / search Β  πŸ”‘ contracts.read

Parameter Description
q Search contract number, title and supplier legal name
supplier_id Β· contract_status_id Β· contract_owner_id Β· payment_schedule_id Id filters
is_active true / false
expiring_within_days ⏳ Active contracts whose contract_end falls within N days (Watchtower's 30/90-day windows as a parameter)
notice_within_days πŸ”” Active contracts whose notice_date falls within N days β€” the "act now or auto-renew" report
expired true = contract_end already passed
ends_before / ends_after YYYY-MM-DD bounds
sort contract_end (default), contract_start, created_at, title, contract_number, contract_value, id β€” prefix - for descending
page / per_page Pagination β€” default 1 / 25, max 100

The contract shape:

{ "id": 4, "contract_number": "CN-2026-014", "title": "Managed print services",
  "description": null,
  "supplier": { "id": 5, "name": "Acme Print" },
  "owner": { "id": 1, "name": "Administrator" },
  "status": { "id": 2, "name": "Active" },
  "payment_schedule": { "id": 1, "name": "Annual" },
  "dates": { "start": "2026-08-01", "end": "2028-07-31",
             "notice_date": "2028-05-02", "notice_period_days": 90 },
  "value": { "amount": 24000.0, "currency": "GBP" },
  "cost_centre": null, "dms_link": null,
  "governance": { "terms_status": null, "personal_data_transferred": false,
                  "dpia_required": false, "dpia_completed_date": null, "dpia_dms_link": null },
  "is_active": true, "created_at": "2026-07-03T15:40:52Z" }

πŸ”΅ POST /contracts β€” create Β  πŸ”‘ contracts.create

contract_number + title required; everything else optional. Lookup ids validated (422 naming the field), dates YYYY-MM-DD, currency a 3-letter code (lower-case is normalised). Duplicate contract_number β†’ 409 with the existing contract's id β€” a deliberate API-only safeguard. Returns 201.

🟒 GET /contracts/{id} Β  πŸ”‘ contracts.read Β· 🟠 PATCH /contracts/{id} Β  πŸ”‘ contracts.update

Everything creatable is patchable; null clears; renaming to a contract_number in use β†’ 409. Set is_active: false to retire a contract without deleting it β€” usually the right move.

πŸ”΄ DELETE /contracts/{id} Β  πŸ”‘ contracts.delete

Warning

Permanent. The contract and its term-tab contents are removed; tasks, calendar events and RFPs that referenced it are unlinked (kept, not deleted). Prefer is_active: false for retirement.


πŸ“‘ Contract terms

Term tabs (Termination, Liability, …) are configured in Contracts β†’ Settings; each contract stores its own content per tab.

🟒 GET /contracts/{id}/terms Β  πŸ”‘ contract_terms.read

Every active tab with this contract's content (null where nothing recorded).

πŸ”΅ POST /contracts/{id}/terms Β  πŸ”‘ contract_terms.update

{ "terms": [ { "term_tab_id": 1, "content": "Termination requires 90 days written notice." } ] }

Per-tab upsert, exactly like the UI β€” only the tabs you send are touched. Returns the full term set.


🏭 Suppliers

🟒 GET /suppliers/{id} Β  πŸ”‘ suppliers.read

The full record β€” registration/VAT numbers, address, questionnaire dates, type/status, supplies_assets, contract + contact counts, and the contact list inline.

πŸ”΅ POST /suppliers Β  πŸ”‘ suppliers.create Β· 🟠 PATCH /suppliers/{id} Β  πŸ”‘ suppliers.update

Only legal_name is required. All identity/address/questionnaire fields accepted, plus supplies_assets β€” set it true for hardware vendors so they appear in the Assets module's supplier pickers (the UI can't set this flag; it's normally only flipped by the asset-import migration).

πŸ”΄ DELETE /suppliers/{id} Β  πŸ”‘ suppliers.delete

Deletes the supplier; its contracts, contacts and assets keep their rows but are unlinked (supplier set to none) β€” the same behaviour as the UI.


πŸ‘€ Supplier contacts

Endpoint Permission Notes
🟒 GET /suppliers/{id}/contacts πŸ”‘ supplier_contacts.read The supplier's people
πŸ”΅ POST /suppliers/{id}/contacts πŸ”‘ supplier_contacts.create first_name + surname required; email/mobile/job title/dial numbers optional
🟠 PATCH /suppliers/{id}/contacts/{contact_id} πŸ”‘ supplier_contacts.update Send only what changes
πŸ”΄ DELETE /suppliers/{id}/contacts/{contact_id} πŸ”‘ supplier_contacts.delete Removes the contact

πŸ“š Reference data

Under the shared πŸ”‘ reference.read permission (all populated via Contracts β†’ Settings β€” empty on a fresh install):

Endpoint Returns
🟒 GET /contract-statuses Contract statuses
🟒 GET /payment-schedules Payment schedules
🟒 GET /supplier-types · GET /supplier-statuses Supplier lookups
🟒 GET /contract-term-tabs The configured term tabs
🟒 GET /suppliers The lite supplier picker list (id, name, supplies_assets)

πŸ§ͺ Worked example: renewal radar

A read-only key (contracts: read) feeds a monthly procurement report:

B="https://your-server/api/v1"; K="Authorization: Bearer fitsm_…"

# Contracts needing a renewal decision in the next 90 days, soonest first
curl -s -H "$K" "$B/contracts?expiring_within_days=90&sort=contract_end&per_page=100" \
  | jq -r '.data[] | [.contract_number, .dates.end, (.supplier.name // "-"),
                      ((.value.amount // 0) | tostring) + " " + (.value.currency // ""), .title] | @tsv'

# The urgent list: notice deadlines in the next 30 days (miss these and it auto-renews)
curl -s -H "$K" "$B/contracts?notice_within_days=30&sort=contract_end" \
  | jq -r '.data[] | "⚠️  \(.dates.notice_date)  \(.contract_number)  \(.title)"'

And the ERP direction β€” syncing supplier master data in:

SUP=$(curl -s -H "$K" "$B/suppliers?" ) # lite list via GET /suppliers (reference.read)
curl -s -X PATCH "$B/suppliers/5" -H "$K" -H "Content-Type: application/json" \
  -d '{"vat_number": "GB123456789", "supplier_status_id": 2}'

Under the hood: REST API β€” How It Works Β· Other modules: Tickets Β· Assets Β· Problems Β· Changes Β· Knowledge Β· Tasks Β· CMDB Β· Keys & permissions: System β†’ API (System module) Β· Module docs: Contracts.

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally