TIMB · Contractor Integration

Contracting API

A REST API that lets TIMB-contracted tobacco buyers create, update, confirm, and cancel grower contracts programmatically — the same data that TBGMS's desktop contracting form writes, exposed for contractor systems to integrate against directly. It sits alongside the biometric verification API that confirms a grower was physically present before a contract completes.

Base URL: https://tgbms.devx.ndawana.tech/api/contracting Contracting auth: System creds + Bearer JWT Biometric auth: System creds only Content-Type: application/json
Try it

Test Portal

Every endpoint below is exercisable from a browser, no code required. It captures a real fingerprint from a SecuGen scanner attached to your own machine, walks it through verification, and creates a contract end to end — the fastest way to see the whole flow work before writing any integration code.

Open the Test Portal →

Log in with your own TBGMS credentials and the system credential TIMB issued you (see Authentication). The portal talks to this same API over HTTPS — it never sends your credentials anywhere else.

Try it

Test credentials

A shared, low-privilege credential pair for exercising this API and the Test Portal above — not a production contractor account.

System credential (every request)

X-System-Identifier: H263-TEST-1
X-Auth-Key: fc2fed9edb02e57294a89ba191db50970a20930c8ac95351

Contractor login (for /auth/token)

{
  "username": "test@contractor.co.zw",
  "password": "#Pass12345"
}

Test grower

V000000A

A real, active grower ("TEST GROWER") not yet contracted by anyone — safe to use for enrolling, verifying, and creating a contract against.

Section 1

Authentication

Two credentials, on every contracting request: a system credential identifying which software is calling, and a Bearer token identifying which contractor. Biometric requests carry only the system credential — there's no contractor identity involved in a fingerprint match.

  1. Get a system credential from TIMB A X-System-Identifier/X-Auth-Key pair, issued to your software once during onboarding — not something you request per call. Send both headers on every request to this service, biometric or contracting.
  2. Request a token With the system headers attached, POST your TBGMS username (email) and password to /auth/token. No separate credential to issue for this part — it's the same login your contractor already has on the TBGMS desktop app.
  3. Attach both to every contracting call Send the system headers and Authorization: Bearer <accessToken> on every other contracting request. The token already identifies your contractor — no contractor ID is passed separately.
  4. Re-authenticate on expiry Tokens expire after expiresInMinutes (60 by default). There is no refresh endpoint yet — call /auth/token again to mint a new one.

If your TBGMS account is a sub-user linked to a super-user (ContractorSuperUser), authenticate with your own sub-user credentials — the API resolves your contractor identity through the super-user chain automatically, matching how the desktop app already authorizes you.

Section 2

XAF setup checklist

Before a contractor can integrate, TIMB's TBGMS administrator needs to set up the following in the desktop app. This is data entry, not code — but a contract-creation call will fail with a clear error at whichever step is missing, so it's worth doing all five before a contractor starts testing.

  1. Create the Contractor record

    One row per buying company. The email must exactly match (case-insensitively) the TBGMS login the contractor's system will authenticate with.

    contractor_name email contractor_code
  2. Confirm the TBGMS login is active

    The contractor's PermissionPolicyUser account must be active and not locked out. If they'll use a sub-user account instead of the contractor's own login, that sub-user's super-user link must point back to this Contractor's email.

  3. Price the contractor's items

    Add a ContractorItemPricing row for every item this contractor sells — an item with no pricing row can't be added to a contract line.

    Item custom_code unit_price
  4. Set up the crop's season

    Every crop needs at least one CropSeason row — the most recently created one is what a new contract is written against. No season configured means contract creation fails with 422.

  5. (Optional) Build pricing templates

    If the contractor's system wants to build a contract from a reusable bundle (e.g. "standard seed pack") rather than raw pricing IDs, add a ContractorItemPricingTemplate with its item mappings. Purely a convenience — contract creation only ever needs pricing IDs.

Section 3

Contracting flow, end to end

Biometric verification and contract creation are two separate APIs, each requiring a system credential — this is the sequence that ties them together, and the one most integrators ask about first.

Every request carries a system credential; contracting requests carry a second credential on top of it. /api/biometric/* authenticates with only X-System-Identifier/ X-Auth-Key — there is no user identity involved, just the device making the call. /api/contracting/* requires that same header pair plus the Bearer JWT from /auth/token, which identifies the contractor. A field agent's device and your back-office contracting system are usually different pieces of software, so TIMB typically issues them separate system credentials — ask for both.

  1. Check eligibility GET /growers/{growerNumber} — confirm the grower isn't already contracted by someone else before doing anything else.
  2. Enroll, if this grower has never been captured before POST /api/biometric/enroll — a one-time capture per grower. Skip this if they're already enrolled.
  3. Verify the grower's fingerprint at the point of contracting POST /api/biometric/verify — returns a txnReference. Hold onto it; it's what "biometrically confirms" the contract.
  4. Create the contract POST /contracts, passing that txnReference as verificationReference. A matched verification completes the contract in the same call — no separate confirm step needed.
  5. (Only if verification didn't match, or wasn't available) Either pass an explicit override and written reason on that same create call to complete it anyway, or omit verificationReference entirely to create it OPEN and finalize later with POST /contracts/{id}/confirm — using a fresh verification, since each one only works once.
Section 4

Endpoints

Every request/response body below is the exact JSON shape the API sends and expects — field names, types, and all.

Biometric endpoints

Authenticate these two with X-System-Identifier / X-Auth-Key headers — not the contracting Bearer token.

POST /api/biometric/enroll X-System-Identifier + X-Auth-Key

Capture a grower's biometric template for the first time. Safe to call again later — it becomes an update rather than a duplicate.

Request body

{
  "growerNumber": "GRW-000123",
  "contractorReference": "FIELD-TXN-1",
  "verificationType": "SellingPointEntry",
  "biometricPayload": [
    {
      "type": "FINGERPRINT",
      "indicator": "RT",
      "biometricStandard": "ISO_19794_2",
      "biometricSignature": "Rk1SACAyMAAA..."
    }
  ]
}

Response — 200 OK

{
  "txnReference": "a1b2c3d4-...",
  "contractorReference": "FIELD-TXN-1",
  "growerNumber": "GRW-000123",
  "status": "PENDING",
  "matched": null,
  "message": "Fingerprint template recorded...",
  "results": [ ... ]
}

indicator is one of UK/RT/RI/RM/LT/LI/LM (unknown / right thumb / right index / right middle / left thumb / left index / left middle) — required for FINGERPRINT items, omitted for FACE.

StatusMeaning
200Recorded — always PENDING, since enrollment doesn't itself match anything.
401Missing or invalid X-System-Identifier / X-Auth-Key.
400Grower not found, or a FINGERPRINT item is missing its indicator.
POST /api/biometric/verify X-System-Identifier + X-Auth-Key

Match a freshly-captured fingerprint or face against the grower's enrolled template. The txnReference this returns is what you feed into contract creation or confirm as verificationReference.

Request body

{
  "growerNumber": "GRW-000123",
  "contractorReference": "FIELD-TXN-2",
  "verificationType": "ContractingConfirmation",
  "biometricPayload": [
    {
      "type": "FINGERPRINT",
      "indicator": "RT",
      "biometricStandard": "ISO_19794_2",
      "biometricSignature": "Rk1SACAyMAAA..."
    }
  ]
}

Response — 200 OK

{
  "txnReference": "e5f6a7b8-...",
  "contractorReference": "FIELD-TXN-2",
  "growerNumber": "GRW-000123",
  "status": "SUCCESS",
  "matched": true,
  "message": "Verification completed successfully",
  "results": [
    {
      "type": "FINGERPRINT",
      "indicator": "RT",
      "status": "SUCCESS",
      "matched": true,
      "matchScore": 199.0,
      "qualityScore": 92.0,
      "livenessProb": 99.0,
      "message": "Match found"
    }
  ]
}

txnReference is the value to use as verificationReference downstream — it's what gets looked up when a contract is created or confirmed. It's valid indefinitely (it's a permanent verification-log record, not a short-lived token), but use it promptly — it represents "this grower was physically present a moment ago."

StatusMeaning
200Always returned once the grower is found — check status/matched for the outcome, a non-match is not an HTTP error.
401Missing or invalid X-System-Identifier / X-Auth-Key.
400Grower not found, or the payload failed validation.

Contracting endpoints

Every one of these, including the login call itself, also requires the X-System-Identifier/X-Auth-Key pair identifying your system — see Authentication.

POST /api/contracting/auth/token X-System-Identifier + X-Auth-Key

Exchange TBGMS credentials for an access token. Requires your system credential (see Authentication) even though no Bearer token exists yet at this point.

Request body

{
  "username": "buyer@acmetobacco.co.zw",
  "password": "••••••••••"
}

Response — 200 OK

{
  "accessToken": "eyJhbGciOi...",
  "tokenType": "Bearer",
  "expiresInMinutes": 60
}
StatusMeaning
200Token issued.
401Missing/invalid system credentials, unknown username, wrong password, inactive account, or account temporarily locked out. The last four share one message, deliberately, to avoid revealing which applies.
400username or password missing/blank.
GET /api/contracting/growers/{growerNumber} X-System-Identifier + X-Auth-Key + Bearer

Check whether a grower can be contracted by you before building a create request. Also called internally on every create, so this is advisory — but calling it first avoids a wasted round trip.

Response — 200 OK

{
  "eligible": true,
  "growerNumber": "GRW-000123",
  "message": "Grower is not yet contracted",
  "grower": {
    "growerNumber": "GRW-000123",
    "firstName": "Tendai",
    "lastName": "Moyo",
    "nationalId": "63-...",
    "growerStatus": "Active"
  }
}

When contracted by another contractor, grower is null — you never see that grower's details:

{
  "eligible": false,
  "growerNumber": "GRW-000123",
  "message": "Grower already contracted by another contractor",
  "grower": null
}
StatusMeaning
200Always returned when the grower exists — check eligible, not the status code, for the outcome.
404No grower with that growerNumber.
POST /api/contracting/contracts X-System-Identifier + X-Auth-Key + Bearer

Create a contract — normally the last step of the end-to-end flow. Every rule in Business rules is enforced here, not just on the eligibility check.

Request body

{
  "growerNumber": "GRW-000123",
  "cropId": 1,
  "lineItems": [
    { "contractorItemPricingId": 200, "quantity": 6 }
  ],
  "verificationReference": "a1b2c3d4-...",
  "override": null,
  "overrideReason": null
}

Response — 200 OK

{
  "contractId": 4821,
  "growerNumber": "GRW-000123",
  "contractorName": "Acme Tobacco Buyers",
  "cropName": "Flue-Cured Tobacco",
  "seasonName": "2025/26",
  "contractStatus": "OPEN",
  "confirmedByGrower": false,
  "total": 90.00,
  "lineItems": [ ... ],
  "createdAt": "2026-08-21T09:14:02"
}

verificationReference is the txnReference from a prior call to the biometric /api/biometric/verify endpoint. Omit it entirely to create an OPEN contract and finalize later via confirm — but if you do supply one, it must either have matched or carry a valid override; a non-matched reference with no override is rejected outright, not silently downgraded to OPEN.

StatusMeaning
200Contract created — check contractStatus for OPEN / COMPLETED / COMPLETED_WITH_BIO_OVERRIDE.
404Unknown growerNumber or cropId.
409Grower already contracted by another contractor; a single-contracting crop already has a contract for this grower+season; the supplied verification didn't match and no valid override was given; or the verification reference was already used to complete a different contract.
422The crop has no season configured — see XAF setup, step 4.
400A line item's contractorItemPricingId doesn't belong to you, or verificationReference was supplied but doesn't resolve to a real verification.
PUT /api/contracting/contracts/{id} X-System-Identifier + X-Auth-Key + Bearer

Replace a contract's line items entirely — only while it's still OPEN.

Request body

{ "lineItems": [ { "contractorItemPricingId": 200, "quantity": 6 } ] }
StatusMeaning
200Line items replaced; response shape matches create's.
404Contract doesn't exist, or doesn't belong to you.
409Contract is no longer OPEN.
400A line item's pricing ID doesn't belong to you.
POST /api/contracting/contracts/{id}/confirm X-System-Identifier + X-Auth-Key + Bearer

Finalize an OPEN contract using a biometric verification reference. Unlike create, a failed match here is an error, not a silent no-op — confirm means "finalize now."

Request body

{
  "verificationReference": "a1b2c3d4-...",
  "override": true,
  "overrideReason": "Farmer accepted verbally, fingerprint reader offline"
}
StatusMeaning
200Contract moved to COMPLETED or COMPLETED_WITH_BIO_OVERRIDE.
404Contract doesn't exist, or doesn't belong to you.
409Contract isn't OPEN; the verification didn't match and no valid override was given; or the verification reference was already used to complete a different contract.
400verificationReference doesn't resolve to a real verification.
DELETE /api/contracting/contracts/{id} X-System-Identifier + X-Auth-Key + Bearer

Soft-cancel an OPEN contract. This sets its status to CANCELLED — nothing is deleted.

StatusMeaning
204Cancelled — no response body.
404Contract doesn't exist, or doesn't belong to you.
409Contract isn't OPEN — only open contracts can be cancelled.
GET /api/contracting/contracts/{id} X-System-Identifier + X-Auth-Key + Bearer

Fetch a single contract belonging to you. Response shape matches create's.

StatusMeaning
200Found.
404Doesn't exist, or belongs to another contractor.
GET /api/contracting/contracts X-System-Identifier + X-Auth-Key + Bearer

List every contract belonging to you. Returns a JSON array of the same object create returns — no pagination yet.

GET /api/contracting/item-pricing X-System-Identifier + X-Auth-Key + Bearer

Your priced item catalogue — the source of every contractorItemPricingId you'll reference when creating contracts. Also where the minimum seed pack requirement for each item comes from.

[
  {
    "contractorItemPricingId": 200,
    "customCode": "ACME-SEED-A",
    "itemName": "Seed Pack A",
    "unit": "kg",
    "unitPrice": 15.00,
    "seedPack": true,
    "seedPackQuantity": 6
  },
  {
    "contractorItemPricingId": 201,
    "customCode": "ACME-FERT-X",
    "itemName": "Fertilizer X",
    "unit": "kg",
    "unitPrice": 5.00,
    "seedPack": false,
    "seedPackQuantity": 0
  }
]

When seedPack is true, seedPackQuantity is the minimum quantity of that exact item a contract must include for grower ownership to be claimed on completion — see Business rules. For non-seed-pack items seedPackQuantity is always 0 and has no effect.

GET /api/contracting/item-pricing/templates X-System-Identifier + X-Auth-Key + Bearer

Your reusable line-item bundles, if any were set up (see XAF setup, step 5). Optional — skip straight to item pricing if you don't use templates.

[
  {
    "templateId": 12,
    "templateName": "Standard Seed Pack",
    "items": [
      { "contractorItemPricingId": 200, "itemName": "Seed Pack A", "quantity": 6 }
    ]
  }
]
GET /api/contracting/crops X-System-Identifier + X-Auth-Key + Bearer

Every crop TIMB has configured, with its current season — the source of the cropId a contract needs.

[
  {
    "cropId": 1,
    "name": "Flue-Cured Tobacco",
    "singleContracting": true,
    "currentSeasonName": "2025/26"
  }
]

currentSeasonName is null if nobody has configured a season for that crop yet — creating a contract against it will 422 until they do.

No separate seasons endpoint exists. Contract creation takes no seasonId — only cropId. The season is resolved server-side, to that crop's most recently created CropSeason. currentSeasonName above is exactly that value, surfaced for display or sanity-checking — it is not a request parameter.

Section 5

Business rules

The same rules TBGMS's desktop contracting form enforces, applied identically here.

One contractor per grower

A grower already contracted by another contractor is invisible to you — the eligibility check returns grower: null, and create returns 409 if you try anyway. A grower you've already contracted yourself is fine to contract again.

Single-contracting crops

If a crop's singleContracting is true, a grower can only have one contract for that crop within the same season, regardless of contractor.

Minimum seed pack

For every item flagged as a seed pack, the contract must include at least that item's required quantity. Only when every seed-pack minimum is met does a completing contract actually claim the grower for you — an under-quantity contract still completes, it just doesn't assign ownership. Check which items are seed-pack items and their required quantities via item pricing's seedPack/seedPackQuantity fields before building line items.

Biometric verification

A verificationReference must come from a real, prior call to /api/biometric/verify. If it matched, the contract completes immediately. If it didn't match, you can still complete with an explicit override and a written overrideReason — otherwise the request is rejected outright with 409, on both create and confirm. The only way to get an OPEN contract is to omit verificationReference entirely.

A verification is used once

Each biometric verification (txnReference) can complete at most one contract. Citing the same reference again — for the same grower or a different one — is rejected with 409. Capture and verify fresh for every contract.

Section 6

Error format

Every non-2xx response, from every contracting endpoint, has this exact shape:

{ "message": "Grower already contracted by another contractor" }
StatusWhen
401Missing/invalid system credentials, missing/expired/tampered bearer token, or bad login credentials.
400Request failed validation, or referenced something that must belong to you but doesn't.
404The thing you asked for doesn't exist (or isn't visible to you).
409The request conflicts with the current state of the data.
422The request is well-formed but TIMB's setup data isn't ready for it yet (e.g. no season configured).
Section 7

Hosting

Hosted at tgbms.devx.ndawana.tech, alongside the Test Portal. This document is served at the site's root as a living reference — bookmark the domain itself rather than a copy of this file.