> ## Documentation Index
> Fetch the complete documentation index at: https://docs.grdd.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Build With AI

> Scaffold and test a working GNet Platform integration in minutes with one-shot AI prompts.

Every GNet Platform integration comes down to two directions — **Farm In** (you *receive* trips) and **Farm Out** (you *send* trips). Each prompt below is a complete, self-contained build spec: paste it into your AI coding assistant and it generates a working integration — authentication, validation, idempotency, retries, status callbacks, and tests included. Then verify your Farm In endpoint with the black-box test at the bottom.

<Note>
  Establish your partnership in [GNet Connect](https://connect.grdd.net) and get your API credentials before go-live. The prompts leave every credential as a placeholder — fill in the values we issue you.
</Note>

## How it works

<Steps>
  <Step title="Pick your direction">
    **Farm In** if other operators send trips to you. **Farm Out** if you send trips to partners. Most full integrations need both.
  </Step>

  <Step title="Fill the Variables block">
    Set your language, your griddID, and route paths at the top of the prompt. Leave credentials as placeholders — your assistant will wire them to environment variables.
  </Step>

  <Step title="Paste into your AI assistant">
    Drop the whole prompt into Cursor, Claude, ChatGPT, or Copilot. It returns a runnable project with tests and a README.
  </Step>

  <Step title="Verify, then go live">
    Run the [Farm In conformance test](#test-your-farm-in-endpoint) below against your endpoint, and use the GBOOK tool to exercise trips end-to-end, before switching on production credentials.
  </Step>
</Steps>

## Choose your language

The prompts are language-agnostic. Set one variable — `TARGET_LANGUAGE_AND_FRAMEWORK` — to your stack and the assistant generates idiomatic code for it. Verified targets:

| Stack             | Example frameworks      |
| ----------------- | ----------------------- |
| TypeScript / Node | Express, NestJS, NextJS |
| PHP               | Laravel, plain PHP      |
| Java              | Spring Boot             |
| Python            | FastAPI, Flask          |

Any other stack works too — just name it in the slot.

## The prompts

<Tabs>
  <Tab title="Farm In — receive trips">
    Builds your inbound `POST` endpoint (Basic auth + reservation JSON to success/error shape), the `GET` health check, `QUOTE` handling, and the outbound status callbacks to `providerUpdateStatusByResNo`. Expand, copy, fill the Variables block, and paste.

    <Accordion title="Copy the full Farm In prompt" icon="copy">
      ````markdown theme={null}
      # GNet Platform — Farm In Integration (One-Shot Build Prompt)

      > **How to use:** Fill in the `<<...>>` placeholders in the **Variables** block below, then paste this entire file into your AI coding assistant (Cursor, Claude, ChatGPT, Copilot, etc.). It will generate a complete, runnable Farm In integration in the language you specify. This block is the only thing you edit.

      ---

      ## Variables (fill these in)

      ```
      TARGET_LANGUAGE_AND_FRAMEWORK = <<e.g. "TypeScript + Express + Zod" | "PHP + Laravel" | "Java + Spring Boot" | "Python + FastAPI">>
      FARM_IN_ROUTE_PATH            = <<e.g. "/api/gnet-farmin">>
      YOUR_GRIDDID                  = <<your GNet provider griddID, e.g. "acmelimo">>
      INBOUND_BASIC_AUTH_USER       = <<api_key GNet issued you — you VERIFY this on inbound requests>>
      INBOUND_BASIC_AUTH_SECRET     = <<api_secret GNet issued you — you VERIFY this on inbound requests>>
      GATEWAY_UID                   = <<your API Gateway user id, for outbound getToken2>>
      GATEWAY_PW                    = <<your API Gateway password, for outbound getToken2>>
      ```

      > Treat every value above as configuration. Read them from environment variables / a secrets manager. Never hardcode secrets in source.

      ---

      ## Role & Context

      You are a senior backend engineer. Build a **GNet Platform "Farm In" integration** in **`TARGET_LANGUAGE_AND_FRAMEWORK`**.

      GNet is a B2B platform that connects ground-transportation operators and dispatch/reservation systems. In a Farm In flow, **another operator (the requester) sends a reservation to us (the provider)**. Our system must expose an inbound REST endpoint that **receives** these reservations from GNet, respond synchronously in GNet's expected shape, and then **push status updates back** to GNet as the trip progresses.

      This integration has two halves:
      1. **Inbound** — an HTTP endpoint GNet calls to deliver reservations (and a GET health check).
      2. **Outbound** — calls *we* make to GNet to report status changes (and optionally GPS).

      Produce production-quality, fully runnable code with tests. Where the spec is silent, make the safe, conventional choice and **list your assumptions explicitly** at the end. Do not invent credentials or URLs beyond those given here.

      ---

      ## Part 1 — Inbound endpoint (receive reservations)

      Expose **`POST FARM_IN_ROUTE_PATH`**.

      **Request GNet will send you**
      - Headers: `Content-Type: application/json`, `Authorization: Basic <base64(INBOUND_BASIC_AUTH_USER:INBOUND_BASIC_AUTH_SECRET)>`.
      - Body: a GNet reservation object. Representative payload (use this to model your types/DTOs):

      ```json
      {
        "reservationDate": "-07:00",
        "affiliateReservation": {
          "action": "NEW",
          "status": "BOOKING_REQUEST",
          "requesterId": "gnettest",
          "requesterResNo": "1715016686",
          "providerId": "LATtest"
        },
        "reservationType": "REGULAR",
        "status": "BOOKING_REQUEST",
        "runType": "Airport",
        "passengerCount": "2",
        "eventName": "LA Convention Center",
        "passengers": [
          {
            "firstName": "Alex",
            "lastName": "Smith",
            "email": "alex@example.com",
            "phoneNumber": "+155550044444",
            "phones": [{ "number": "+155550044444", "type": "mobile" }]
          }
        ],
        "namesignInstructions": "Mr. Adam",
        "locations": {
          "pickup": {
            "locationType": "address",
            "address": "123 S Beverly Dr, Beverly Hills, CA 90212, USA",
            "city": "Beverly Hills", "state": "CA", "zipCode": "90212",
            "lat": 34.0664703, "lon": -118.3993243, "country": "US",
            "time": "2024-08-03T10:27:22"
          },
          "dropOff": {
            "locationType": "airport", "address": "LAX",
            "flightInfo": { "flightNumber": "123", "airlineCode": "UA" },
            "meetAndGreet": "False"
          },
          "stops": [
            {
              "locationType": "address",
              "address": "333 Santa Monica Blvd, Santa Monica, CA 90401, USA",
              "lat": 34.0164474, "lon": -118.4954529,
              "stopNumber": 1, "specialInstructions": "See Mr. Jones"
            }
          ]
        },
        "preferredVehicleType": "SEDAN",
        "specialInstructions": "- Carseat needed ",
        "sourceVendor": "GRIDD",
        "origination": "gBook",
        "notes": [
          { "message": "VIP Client", "context": "AFFILIATE" },
          { "message": "Drop off at Terminal 4 ", "context": "DRIVER" }
        ],
        "submitMode": "WAIT",
        "transactionId": "935a1056-644c-4e82-9a1a-6099efdd056b"
      }
      ```

      **Required handling**
      1. **Authenticate** every request with HTTP Basic against `INBOUND_BASIC_AUTH_USER` / `INBOUND_BASIC_AUTH_SECRET`. Use a constant-time comparison. Reject bad/missing credentials with `401`.
      2. **Validate** `Content-Type: application/json` and parse defensively. On malformed JSON or missing required fields, return the documented error shape (below).
      3. **Idempotency:** `transactionId` is the unique key for the transaction. Persist a record of processed `transactionId`s. If you receive a `transactionId` you have already processed, return the **same** prior result instead of creating a duplicate.
      4. **NEW vs UPDATE:** branch on `affiliateReservation.action`. `"NEW"` creates a reservation; `"UPDATE"` modifies the existing one (match on `transactionId` / `requesterResNo`). Handle an unknown `action` gracefully with a clear error.
      5. **QUOTE handling:** if `reservationType == "QUOTE"`, **do not** persist a reservation. Compute and return a price in `totalAmount` using the same success shape below. (Pricing logic is a stub/hook for the operator to implement.)
      6. Map GNet `preferredVehicleType` to your internal fleet types, and GNet `status` codes to your internal statuses, in a single dedicated mapping module (see Reference links).
      7. Preserve timestamps as received. They are ISO-8601 and may carry a UTC offset (`reservationDate` may be just an offset like `"-07:00"`; `locations.*.time` looks like `"2024-08-03T10:27:22"`). Do not silently coerce to server-local time.

      **Success response** — HTTP `200`:
      ```json
      { "success": true, "reservationId": "11545-001", "totalAmount": "130.67", "transactionId": "<echo back the request transactionId>" }
      ```

      **Error response:**
      ```json
      { "success": false, "message": "Unable to process the request due to <reason>.", "transactionId": "<echo back if present>" }
      ```
      > GNet keys off the `success` boolean. Return `401` for auth failures and `400` for unparseable requests; for business failures return the `success:false` body. (If your GNet onboarding contact specifies exact HTTP codes for business errors, follow those — flag this as an assumption.)

      ## Part 2 — Health check

      `GET FARM_IN_ROUTE_PATH` must return HTTP `200` with:
      ```json
      { "success": true }
      ```
      GNet uses this GET as a liveness probe. It must not require auth-protected resources to answer.

      ## Part 3 — Outbound status updates (report progress back to GNet)

      As the trip progresses, push status changes to GNet. First obtain a token, then call the update endpoint.

      **Get a token** (cache it; refresh on expiry or on a `401`):
      ```
      POST https://api.grdd.net/Platform.svc/getToken2
      Content-Type: application/json

      { "uid": "GATEWAY_UID", "pw": "GATEWAY_PW" }
      ```
      Response contains a `token` string used as a header on subsequent calls.

      **Update status by reservation number:**
      ```
      POST https://api.grdd.net/Platform.svc/providerUpdateStatusByResNo/{YOUR_GRIDDID}/{RESNO}/{VERSION}
      token: <token from getToken2>
      Content-Type: application/json

      { "status": "CONFIRMED", "totalAmount": "120.00", "resNo": "<reservation_number>", "griddID": "YOUR_GRIDDID" }
      ```
      - Version segment: the reference specifies `V1` (the Quick Start shows it lower-case as `v1`); use what your onboarding contact confirms.
      - Common statuses: `CONFIRMED`, `ASSIGNED`, `EN_ROUTE`, `ON_LOCATION` (full list at the Status Codes reference). A `CONFIRMED` reply with `totalAmount` is how you accept a booking request.
      - A by-Transaction-ID variant also exists (`providerUpdateStatusByTransactionId`) — expose both behind one `reportStatus(...)` function.

      ## Part 4 — Optional: GPS location updates

      If the operator wants live tracking, implement:
      ```
      POST https://location.grdd.net/api/GGPS.svc/saveGPScache
      token: <token from getToken2>
      Content-Type: application/json

      { "chfName": "Jane Doe", "driverId": "123456", "griddid": "YOUR_GRIDDID",
        "internalBookingId": "123456", "latitude": "40.74", "longitude": "-100.56",
        "locationDateTime": "2024-01-23T18:52:34", "phoneNo": "+15555555555", "transactionId": "<>" }
      ```
      Make this an opt-in module, off by default.

      ---

      ## Non-functional requirements (apply to all of the above)

      - **Config & secrets:** all URLs, IDs, and credentials come from environment/config. Nothing secret in source or logs.
      - **Outbound resilience:** every outbound call (token, status, GPS) has a timeout and retries with exponential backoff + jitter, capped attempts, and treats only `2xx` as success. A `401` triggers one token refresh + retry.
      - **Idempotency store:** pluggable interface (in-memory default + a note on swapping for Redis/DB). The platform stack here is Next.js + Azure + Redis, so make Redis the obvious production choice.
      - **Logging:** structured logs correlated by `transactionId`. Never log `Authorization`, tokens, or passenger PII (names, phones, emails) above debug level.
      - **Validation & mapping:** isolate request validation, status mapping, and vehicle-type mapping into their own modules so they can evolve with the spec.
      - **Concurrency-safe** under parallel requests for the same `transactionId`.
      - **Tests** (use the idiomatic framework for the language): happy-path `NEW`, `UPDATE`, `QUOTE` (no persistence, returns `totalAmount`), bad/missing Basic auth → `401`, malformed JSON → `400`, duplicate `transactionId` is idempotent, `GET` health check returns `200 {success:true}`, and an outbound `reportStatus` test with the token-refresh-on-401 path mocked.

      ## Deliverables

      1. A complete, runnable project in `TARGET_LANGUAGE_AND_FRAMEWORK` with a clear file layout.
      2. The inbound POST + GET handlers, the outbound GNet client (token caching, status, GPS), validation, and mapping modules.
      3. `.env.example` listing every variable from the Variables block.
      4. Tests covering the cases above.
      5. A `README` with run instructions and this verification snippet:
         ```bash
         # health check
         curl -X GET "http://localhost:PORT/FARM_IN_ROUTE_PATH" -i
         # simulate an inbound reservation (Basic auth = INBOUND_BASIC_AUTH_USER:INBOUND_BASIC_AUTH_SECRET)
         curl -X POST "http://localhost:PORT/FARM_IN_ROUTE_PATH" \
           -u "USER:SECRET" -H "Content-Type: application/json" \
           -d @sample-reservation.json
         ```
      6. A short list of **assumptions** you made.

      ## Acceptance criteria (self-check before finishing)

      - [ ] `GET` health check returns `200 {"success": true}` with no auth.
      - [ ] `POST` rejects missing/incorrect Basic auth with `401` (constant-time compare).
      - [ ] Valid `NEW` reservation returns `{success:true, reservationId, totalAmount, transactionId}`.
      - [ ] Re-sending the same `transactionId` does not create a duplicate.
      - [ ] `reservationType:"QUOTE"` returns `totalAmount` and persists nothing.
      - [ ] Outbound `reportStatus` obtains/caches a token and posts to `providerUpdateStatusByResNo`, refreshing the token on `401`.
      - [ ] No secrets or PII in logs; all config externalized.

      ## Reference (consult for field-level detail; do not hardcode anything beyond the Variables block)

      - Quick Start: https://docs.grdd.dev/gnet-platform/quick-start
      - Receiving Reservations: https://docs.grdd.dev/platform-api/receiving-reservations
      - Reservation Object Model: https://docs.grdd.dev/platform-api/reference/object-model
      - Status Codes: https://docs.grdd.dev/platform-api/reference/status-codes
      - Vehicle Types: https://docs.grdd.dev/platform-api/reference/vehicle-types
      - Update Status by ResNo: https://docs.grdd.dev/platform-api/provider-updates/update-by-resno
      - Update Status by Transaction ID: https://docs.grdd.dev/platform-api/provider-updates/update-by-transaction-id
      - Get Token: https://docs.grdd.dev/platform-api/authentication/get-token
      - Save GPS: https://docs.grdd.dev/connect-api/location/save-gps
      - Full sample reservation: https://raw.githubusercontent.com/GRiDD/GRiDD.github.io/main/gnet-sample-reservation.json
      - API reference (Apiary): https://gnet.docs.apiary.io/
      - Test inbound trips with GBOOK: https://api.grdd.net/gbook  (BOOK → pick your griddID → "SHOW JSON" reveals the exact payload)

      Now build it.
      ````
    </Accordion>
  </Tab>

  <Tab title="Farm Out — send trips">
    Builds authentication (`getToken2`), `sendTrip`, your callback-webhook receiver for provider updates, and `UPDATE` / `cancelTripByTransactionId`. Expand, copy, fill the Variables block, and paste.

    <Accordion title="Copy the full Farm Out prompt" icon="copy">
      ````markdown theme={null}
      # GNet Platform — Farm Out Integration (One-Shot Build Prompt)

      > **How to use:** Fill in the `<<...>>` placeholders in the **Variables** block below, then paste this entire file into your AI coding assistant (Cursor, Claude, ChatGPT, Copilot, etc.). It will generate a complete, runnable Farm Out integration in the language you specify. This block is the only thing you edit.

      ---

      ## Variables (fill these in)

      ```
      TARGET_LANGUAGE_AND_FRAMEWORK = <<e.g. "TypeScript + Express" | "PHP + Laravel" | "Java + Spring Boot" | "Python + FastAPI">>
      YOUR_GRIDDID                  = <<your GNet requester griddID, e.g. "acmelimo">>
      DEFAULT_PROVIDER_GRIDDID      = <<griddID of the partner you farm out to (optional default)>>
      GATEWAY_UID                   = <<your API Gateway user id, for getToken2>>
      GATEWAY_PW                    = <<your API Gateway password, for getToken2>>
      CALLBACK_WEBHOOK_ROUTE_PATH   = <<e.g. "/api/gnet-callback" — endpoint GNet posts provider updates to>>
      CALLBACK_BASIC_AUTH_USER      = <<credential you require on your callback endpoint (confirm scheme with GNet)>>
      CALLBACK_BASIC_AUTH_SECRET    = <<secret you require on your callback endpoint>>
      ```

      > Treat every value above as configuration. Read them from environment variables / a secrets manager. Never hardcode secrets in source.

      ---

      ## Role & Context

      You are a senior backend engineer. Build a **GNet Platform "Farm Out" integration** in **`TARGET_LANGUAGE_AND_FRAMEWORK`**.

      GNet is a B2B platform that connects ground-transportation operators and dispatch/reservation systems. In a Farm Out flow, **we (the requester) send a reservation to GNet, which routes it to a partner operator (the provider)**. Our system must authenticate, submit trips, receive asynchronous status updates back from the provider via a callback webhook, and support updates and cancellations.

      This integration has two halves:
      1. **Outbound** — calls *we* make to GNet: get token, send trip, update, cancel.
      2. **Inbound** — a callback webhook GNet posts to with provider status updates.

      > Partnerships are established in GNet Connect (https://connect.grdd.net) **before** trips flow. Assume the partner `griddID` is already an approved partner.

      Produce production-quality, fully runnable code with tests. Where the spec is silent, make the safe, conventional choice and **list your assumptions explicitly** at the end. Do not invent credentials or URLs beyond those given here.

      ---

      ## Part 1 — Authenticate

      Get a token, cache it, and refresh on expiry or on any `401`:
      ```
      POST https://api.grdd.net/Platform.svc/getToken2
      Content-Type: application/json

      { "uid": "GATEWAY_UID", "pw": "GATEWAY_PW" }
      ```
      The response contains a `token` string used as a `token:` header on all subsequent Platform calls.

      ## Part 2 — Send a trip (sendTrip)

      ```
      POST https://api.grdd.net/Platform.svc/sendTrip/v1
      token: <token from getToken2>
      Content-Type: application/json

      <reservation JSON>
      ```

      Build the reservation from the GNet object model. You are the **requester**; set `affiliateReservation.requesterId = YOUR_GRIDDID`, `affiliateReservation.providerId = DEFAULT_PROVIDER_GRIDDID` (or a per-call override), `affiliateReservation.action = "NEW"`, and a unique `affiliateReservation.requesterResNo` (your internal reservation number). Representative payload to model your builder on:

      ```json
      {
        "reservationDate": "-07:00",
        "affiliateReservation": {
          "action": "NEW",
          "status": "BOOKING_REQUEST",
          "requesterId": "YOUR_GRIDDID",
          "requesterResNo": "1715016686",
          "providerId": "DEFAULT_PROVIDER_GRIDDID"
        },
        "reservationType": "REGULAR",
        "runType": "Airport",
        "passengerCount": "2",
        "passengers": [
          { "firstName": "Alex", "lastName": "Smith", "email": "alex@example.com",
            "phones": [{ "number": "+155550044444", "type": "mobile" }] }
        ],
        "locations": {
          "pickup": { "locationType": "address", "address": "123 S Beverly Dr, Beverly Hills, CA 90212, USA",
            "lat": 34.0664703, "lon": -118.3993243, "time": "2024-08-03T10:27:22" },
          "dropOff": { "locationType": "airport", "address": "LAX",
            "flightInfo": { "flightNumber": "123", "airlineCode": "UA" }, "meetAndGreet": "False" },
          "stops": []
        },
        "preferredVehicleType": "SEDAN",
        "specialInstructions": "- Carseat needed ",
        "notes": [{ "message": "VIP Client", "context": "AFFILIATE" }],
        "submitMode": "WAIT"
      }
      ```
      > A `NEW` send omits `transactionId` — GNet generates and returns it (see Notes).

      Notes:
      - **`transactionId` lifecycle:** a `NEW` sendTrip leaves `transactionId` **blank**. GNet returns the `transactionId` in the response (and a `reservationId` when `submitMode = "WAIT"`). Persist both against your internal reservation — the `transactionId` is the correlation key for every later `UPDATE`, cancel, and callback.
      - **`submitMode`:** `sendTrip` is asynchronous by default. `submitMode = "WAIT"` makes it synchronous (slower) and also returns a `reservationId`; otherwise final pricing and identifiers arrive later via the callback webhook.
      - **Response shape (`200`):** `{ "transactionId", "reservationId" (WAIT only), "fees": [...], "totalAmount" }`. Store the `transactionId` immediately; treat a rules-engine error in the response as a failed send.
      - `preferredVehicleType` must be a standardized GNet vehicle type (see Reference). Map your internal fleet types to GNet types in one module.
      - Recent additions to the sendTrip payload include `namesignURL` and `tripAttributes` — support them if relevant (see the changelog link in Reference).
      - **Version segment:** the reference specifies `V1` (the Quick Start shows it lower-case as `v1`); use what your onboarding contact confirms.

      ## Part 3 — Receive provider updates (callback webhook)

      Expose **`POST CALLBACK_WEBHOOK_ROUTE_PATH`**. GNet posts provider status updates here in real time — status changes, chauffeur and vehicle info, fees, and the final `totalAmount` at trip close.

      > You do **not** self-register this endpoint. Build the listener, then tell the GRiDD team your URL and they configure delivery on their side.

      Representative payload to model your receiver on:

      ```json
      {
        "transactionId": "11111111-2e7f-47a7-acb8-fc9136916c32",
        "status": "ASSIGNED",
        "totalAmount": "120.00",
        "affiliateReservation": {
          "requesterId": "YOUR_GRIDDID",
          "requesterResNo": "1723840",
          "providerId": "somepartner",
          "providerResNo": "58689",
          "action": "UPDATE",
          "status": "BOOKING_REQUEST"
        },
        "fees": [
          { "type": "EXPENSE", "currency": "USD", "code": "fixedcharge", "description": "Meet & Greet", "fee": "25.00" },
          { "type": "EXPENSE", "currency": "USD", "code": "percentagecharge", "description": "Driver Gratuity", "fee": "3.00" }
        ],
        "vehicle": { "vehicleId": "223", "vehicleType": { "type": "SEDAN" }, "make": "Sedan", "model": "Chrysler" },
        "chauffeur": {
          "griddID": "somepartner", "firstName": "Joey", "lastName": "ZaZa",
          "phones": [{ "number": "323.222.1111", "type": "mobile" }], "chauffeurId": "zaza@grdd.net"
        }
      }
      ```

      **Required handling**
      1. **Authenticate** the inbound request (Basic auth against `CALLBACK_BASIC_AUTH_USER`/`CALLBACK_BASIC_AUTH_SECRET`, or whatever scheme your GNet onboarding contact confirms — flag as an assumption). Reject bad credentials with `401`.
      2. **Return `200` fast.** Acknowledge immediately, then process asynchronously (queue/worker or background task). Do not block the response on heavy work.
      3. **Correlate** by `transactionId` (fall back to `affiliateReservation.requesterResNo`) to find the original trip. Persist `providerResNo`, `totalAmount`, `fees`, `vehicle`, and `chauffeur`.
      4. **Idempotency:** the same update may be delivered more than once. Dedupe so reprocessing a delivered update is a no-op.
      5. **Map** the provider `status` (e.g. `ASSIGNED`, `EN_ROUTE`, `ON_LOCATION`, `CONFIRMED`) to your internal status in one mapping module (see Status Codes reference).
      6. **Handle `FAILED`:** if `status == "FAILED"`, the reason is in `affiliateReservation.notes`. Surface it and mark the trip failed — don't silently drop it.
      7. **`totalAmount` is an estimate** until the trip reaches `CLOSE`; treat the close-time amount as final.

      ## Part 4 — Update and cancel

      **Update** an existing trip: re-`POST sendTrip/V1` with the **same** `transactionId` returned by the original `NEW`, `affiliateReservation.action = "UPDATE"`, and the changed fields. `transactionId` is **required** on every `UPDATE`.

      **Cancel** a trip:
      ```
      POST https://api.grdd.net/Platform.svc/cancelTripByTransactionId/{TRANSACTIONID}/{VERSION}
      token: <token from getToken2>
      ```
      Use the documented version segment (`V1`; the Quick Start shows `v1`) and the `transactionId` returned by the original `NEW`.

      > Optional but recommended helpers: `getBookingByTransactionId` (reconcile state) and `retryTransaction` (re-drive a failed send). See Reference.

      ---

      ## Non-functional requirements (apply to all of the above)

      - **Config & secrets:** all URLs, IDs, and credentials come from environment/config. Nothing secret in source or logs.
      - **Outbound resilience:** every outbound call (token, sendTrip, cancel) has a timeout and retries with exponential backoff + jitter, capped attempts, treating only `2xx` as success. A `401` triggers one token refresh + retry. Note a `NEW` send has no `transactionId` yet, so a blind retry can duplicate a trip — guard `NEW` with your own idempotency key (e.g. `requesterResNo`); `UPDATE`/cancel carry the `transactionId` and are safe to retry.
      - **Callback durability:** persist received callbacks before processing so none are lost on a crash; process via a queue/worker. In-memory default is fine, but note the production swap (the platform stack here is Next.js + Azure + Redis — make Redis/queue the obvious choice).
      - **Idempotency:** dedupe inbound callbacks by `transactionId` + `status`; make outbound sends safe to retry.
      - **Logging:** structured logs correlated by `transactionId`. Never log `token`, `Authorization`, or passenger/chauffeur PII (names, phones, emails) above debug level.
      - **Tests** (idiomatic framework for the language): build-and-send a `NEW` trip with no `transactionId` and persist the one returned (token mocked), token-refresh-on-`401`, callback happy path updates internal state, duplicate callback is idempotent, callback with bad auth → `401`, a `FAILED` callback surfaces `affiliateReservation.notes`, `UPDATE` reuses the `transactionId`, and `cancel` hits `cancelTripByTransactionId`.

      ## Deliverables

      1. A complete, runnable project in `TARGET_LANGUAGE_AND_FRAMEWORK` with a clear file layout.
      2. A GNet client (token caching, `sendTrip`, `cancel`, optional get/retry), the callback receiver, and validation + status/vehicle mapping modules.
      3. `.env.example` listing every variable from the Variables block.
      4. Tests covering the cases above.
      5. A `README` with run instructions and this verification snippet:
         ```bash
         # 1) get a token
         curl -X POST "https://api.grdd.net/Platform.svc/getToken2" \
           -H "Content-Type: application/json" \
           -d '{"uid":"GATEWAY_UID","pw":"GATEWAY_PW"}'
         # 2) send a trip
         curl -X POST "https://api.grdd.net/Platform.svc/sendTrip/v1" \
           -H "token: $TOKEN" -H "Content-Type: application/json" \
           -d @reservation.json
         ```
      6. A short list of **assumptions** you made.

      ## Acceptance criteria (self-check before finishing)

      - [ ] Token is obtained, cached, and refreshed on `401`.
      - [ ] `sendTrip` builds a valid `NEW` payload with `requesterId = YOUR_GRIDDID`, **no** `transactionId`, and a standardized `preferredVehicleType`.
      - [ ] The `transactionId` from the `sendTrip` response is persisted and reused on `UPDATE` and cancel.
      - [ ] Callback endpoint authenticates, returns `200` immediately, and processes asynchronously.
      - [ ] A `FAILED` callback is surfaced with its `affiliateReservation.notes` reason (not silently dropped).
      - [ ] Re-delivered callback (same `transactionId` + `status`) is a no-op.
      - [ ] `UPDATE` reuses the original `transactionId` with `action:"UPDATE"`.
      - [ ] `cancelTripByTransactionId` is wired and tested.
      - [ ] No secrets or PII in logs; all config externalized.

      ## Reference (consult for field-level detail; do not hardcode anything beyond the Variables block)

      - Quick Start: https://docs.grdd.dev/gnet-platform/quick-start
      - Sending Reservations: https://docs.grdd.dev/platform-api/sending-reservations
      - Send Reservation (sendTrip): https://docs.grdd.dev/platform-api/reservations/send-trip
      - sendTrip payload update (namesignURL, tripAttributes): https://docs.grdd.dev/changelog/gnet-platform/2026-05-sendtrip-payload-update
      - Webhook Callbacks: https://docs.grdd.dev/platform-api/reference/webhook-callbacks
      - Reservation Object Model: https://docs.grdd.dev/platform-api/reference/object-model
      - Status Codes: https://docs.grdd.dev/platform-api/reference/status-codes
      - Vehicle Types: https://docs.grdd.dev/platform-api/reference/vehicle-types
      - Cancel Reservation: https://docs.grdd.dev/platform-api/reservations/cancel-by-transaction-id
      - Get by Transaction ID: https://docs.grdd.dev/platform-api/reservations/get-by-transaction-id
      - Retry Transaction: https://docs.grdd.dev/platform-api/reservations/retry
      - Get Token: https://docs.grdd.dev/platform-api/authentication/get-token
      - Full sample sendTrip / webhook payloads: https://raw.githubusercontent.com/GRiDD/GRiDD.github.io/main/gnet-sample-reservation.json · https://raw.githubusercontent.com/GRiDD/GRiDD.github.io/main/gnet-sample-webhook.json
      - API reference (Apiary): https://gnet.docs.apiary.io/
      - Postman collection: https://raw.githubusercontent.com/GRiDD/GRiDD.github.io/refs/heads/main/GNET.postman_collection.json

      Now build it.
      ````
    </Accordion>
  </Tab>
</Tabs>

## Test your Farm In endpoint

Once your Farm In service is built and deployed, verify it against the contract with this black-box script. It plays the role of GNet — sending representative reservations to your URL and checking the responses — so you need no access to your own code to run it. Pure `bash` + `curl`, with `0`/`1` exit codes for CI.

```bash theme={null}
FARMIN_URL=https://api.example.com/api/gnet-farmin \
API_KEY=your_api_key API_SECRET=your_api_secret \
PROVIDER_GRIDDID=yourgriddid \
./gnet-farmin-blackbox-test.sh
```

It checks:

* `GET` health → `200 {"success": true}`
* Basic auth enforced — missing **and** wrong credentials are rejected with `401`
* REGULAR `NEW` → `success` + `reservationId` + the `transactionId` echoed back
* Idempotency — the same `transactionId` returns the same `reservationId` (no duplicate booking)
* `QUOTE` → returns a `totalAmount`
* Malformed JSON → rejected with `400`, not a 5xx crash

<Accordion title="Copy the Farm In conformance test (bash)" icon="vial">
  ```bash theme={null}
  #!/usr/bin/env bash
  #
  # GNet Platform — Farm In black-box conformance test
  # -----------------------------------------------------
  # Points at a developer's Farm In endpoint and verifies it conforms to the
  # GNet contract: it plays the role of GNet sending reservations, then checks
  # the responses. No code access required — pure HTTP black-box testing.
  #
  # Contract reference: https://docs.grdd.dev/gnet-platform/quick-start
  #
  # USAGE
  #   FARMIN_URL=https://api.example.com/api/gnet-farmin \
  #   API_KEY=your_api_key API_SECRET=your_api_secret \
  #   PROVIDER_GRIDDID=acmelimo \
  #   ./gnet-farmin-blackbox-test.sh
  #
  #   # or pass the URL as the first argument:
  #   ./gnet-farmin-blackbox-test.sh https://api.example.com/api/gnet-farmin
  #
  # ENV VARS
  #   FARMIN_URL         (required) the Farm In endpoint under test
  #   API_KEY/API_SECRET (required) Basic-auth creds the endpoint expects (the ones GNet issued)
  #   PROVIDER_GRIDDID   (required) griddID of the operator under test -> used as providerId
  #   REQUESTER_GRIDDID  (optional) simulated sender, default "gnettest"
  #   VERBOSE=1          (optional) print full response bodies
  #
  # EXIT CODE: 0 if all checks PASS (WARN allowed), 1 if any FAIL. CI-friendly.
  #
  # Dependencies: bash + curl. Uses jq if present, otherwise a grep fallback.

  set -uo pipefail

  FARMIN_URL="${1:-${FARMIN_URL:-}}"
  API_KEY="${API_KEY:-}"
  API_SECRET="${API_SECRET:-}"
  PROVIDER_GRIDDID="${PROVIDER_GRIDDID:-}"
  REQUESTER_GRIDDID="${REQUESTER_GRIDDID:-gnettest}"
  VERBOSE="${VERBOSE:-0}"
  TIMEOUT=20

  # ---- pretty output (color only on a TTY) ------------------------------------
  if [ -t 1 ]; then C_G=$'\033[32m'; C_R=$'\033[31m'; C_Y=$'\033[33m'; C_B=$'\033[1m'; C_0=$'\033[0m'
  else C_G=""; C_R=""; C_Y=""; C_B=""; C_0=""; fi

  PASS=0; FAIL=0; WARN=0
  pass() { PASS=$((PASS+1)); printf "  %s[PASS]%s %s\n" "$C_G" "$C_0" "$1"; }
  fail() { FAIL=$((FAIL+1)); printf "  %s[FAIL]%s %s\n" "$C_R" "$C_0" "$1"; [ -n "${2:-}" ] && printf "         %s\n" "$2"; }
  warn() { WARN=$((WARN+1)); printf "  %s[WARN]%s %s\n" "$C_Y" "$C_0" "$1"; [ -n "${2:-}" ] && printf "         %s\n" "$2"; }
  hdr()  { printf "\n%s%s%s\n" "$C_B" "$1" "$C_0"; }

  # ---- prerequisites ----------------------------------------------------------
  [ -z "$FARMIN_URL" ] && { echo "ERROR: FARMIN_URL is required (env var or first arg)."; exit 2; }
  command -v curl >/dev/null 2>&1 || { echo "ERROR: curl is required."; exit 2; }
  HAVE_JQ=0; command -v jq >/dev/null 2>&1 && HAVE_JQ=1
  [ -z "$PROVIDER_GRIDDID" ] && warn "PROVIDER_GRIDDID not set — using placeholder 'PROVIDER' in payloads."
  PROVIDER_GRIDDID="${PROVIDER_GRIDDID:-PROVIDER}"
  if [ -z "$API_KEY" ] || [ -z "$API_SECRET" ]; then
    warn "API_KEY/API_SECRET not set — authenticated checks will likely fail until you supply them."
  fi

  # ---- helpers ----------------------------------------------------------------
  gen_uuid() {
    if command -v uuidgen >/dev/null 2>&1; then uuidgen | tr 'A-Z' 'a-z'
    elif [ -r /proc/sys/kernel/random/uuid ]; then cat /proc/sys/kernel/random/uuid
    else python3 - <<'PY' 2>/dev/null || echo "00000000-0000-4000-8000-$(date +%s%N | cut -c1-12)"
  import uuid; print(uuid.uuid4())
  PY
    fi
  }

  # json_field <body> <key> -> value (string unquoted, or bare true/false/number)
  json_field() {
    if [ "$HAVE_JQ" = "1" ]; then
      printf '%s' "$1" | jq -r --arg k "$2" '.[$k] // empty' 2>/dev/null
    else
      printf '%s' "$1" | grep -oE "\"$2\"[[:space:]]*:[[:space:]]*(\"[^\"]*\"|true|false|[0-9.]+)" \
        | head -n1 | sed -E "s/^[^:]*:[[:space:]]*//; s/^\"//; s/\"$//"
    fi
  }

  # CODE and BODY are set by request()
  CODE=""; BODY=""
  request() { # request <METHOD> <auth: none|good|bad> [payload]
    local method="$1" auth="$2" payload="${3:-}"
    local tmp; tmp="$(mktemp)"
    local -a args=(-sS -m "$TIMEOUT" -o "$tmp" -w "%{http_code}" -X "$method" "$FARMIN_URL" -H "Content-Type: application/json")
    case "$auth" in
      good) args+=(-u "${API_KEY}:${API_SECRET}") ;;
      bad)  args+=(-u "${API_KEY}:wrong-secret-$(date +%s)") ;;
      none) : ;;
    esac
    [ -n "$payload" ] && args+=(--data "$payload")
    CODE="$(curl "${args[@]}" 2>/dev/null)"; local rc=$?
    BODY="$(cat "$tmp")"; rm -f "$tmp"
    if [ $rc -ne 0 ]; then CODE="000"; fi
    [ "$VERBOSE" = "1" ] && printf "      -> HTTP %s | %s\n" "$CODE" "$(printf '%s' "$BODY" | tr '\n' ' ' | cut -c1-300)"
  }

  # reservation payload builder
  reservation() { # reservation <txid> <reservationType> <action> <resno>
    cat <<JSON
  {
    "reservationDate": "-07:00",
    "affiliateReservation": { "action": "$3", "status": "BOOKING_REQUEST", "requesterId": "${REQUESTER_GRIDDID}", "requesterResNo": "$4", "providerId": "${PROVIDER_GRIDDID}" },
    "reservationType": "$2",
    "status": "BOOKING_REQUEST",
    "runType": "Airport",
    "passengerCount": "2",
    "passengers": [ { "firstName": "Test", "lastName": "Passenger", "email": "test@example.com", "phones": [ { "number": "+15555550144", "type": "mobile" } ] } ],
    "locations": {
      "pickup": { "locationType": "address", "address": "123 S Beverly Dr, Beverly Hills, CA 90212, USA", "lat": 34.0664703, "lon": -118.3993243, "time": "2030-01-15T10:27:22" },
      "dropOff": { "locationType": "airport", "address": "LAX", "flightInfo": { "flightNumber": "123", "airlineCode": "UA" }, "meetAndGreet": "False" },
      "stops": []
    },
    "preferredVehicleType": "SEDAN",
    "specialInstructions": "GNet Farm In conformance test — safe to ignore",
    "submitMode": "WAIT",
    "transactionId": "$1"
  }
  JSON
  }

  is_success_true() { [ "$(json_field "$1" success)" = "true" ]; }

  printf "%sGNet Farm In — black-box conformance test%s\n" "$C_B" "$C_0"
  printf "Target: %s\n" "$FARMIN_URL"
  printf "Requester: %s  ->  Provider: %s  | jq: %s\n" "$REQUESTER_GRIDDID" "$PROVIDER_GRIDDID" "$([ "$HAVE_JQ" = 1 ] && echo yes || echo 'no (grep fallback)')"

  # ---- 1. Health check (GET) --------------------------------------------------
  hdr "1. Health check — GET should return 200 {\"success\": true}"
  request GET none
  if [ "$CODE" = "200" ] && is_success_true "$BODY"; then pass "GET returns 200 with success:true"
  elif [ "$CODE" = "200" ]; then warn "GET returned 200 but body is not {\"success\": true}" "body: $(printf '%s' "$BODY" | cut -c1-160)"
  elif [ "$CODE" = "000" ]; then fail "Could not reach the endpoint (connection error / timeout)."
  else fail "GET expected 200, got $CODE" "GNet uses this GET as a liveness probe; it should be public."; fi

  # ---- 2. Auth: missing credentials ------------------------------------------
  hdr "2. Security — POST without credentials should be rejected"
  request POST none "$(reservation "$(gen_uuid)" REGULAR NEW "rt-noauth-$(date +%s)")"
  if [ "$CODE" = "401" ] || [ "$CODE" = "403" ]; then pass "Unauthenticated POST rejected with $CODE"
  elif is_success_true "$BODY"; then fail "Unauthenticated POST was ACCEPTED (success:true) — endpoint is not enforcing Basic auth." "Expected 401."
  else warn "Unauthenticated POST not a clean 401 (got $CODE) but did not succeed."; fi

  # ---- 3. Auth: wrong credentials --------------------------------------------
  hdr "3. Security — POST with wrong credentials should be rejected"
  request POST bad "$(reservation "$(gen_uuid)" REGULAR NEW "rt-badauth-$(date +%s)")"
  if [ "$CODE" = "401" ] || [ "$CODE" = "403" ]; then pass "Wrong-credential POST rejected with $CODE"
  elif is_success_true "$BODY"; then fail "Wrong-credential POST was ACCEPTED — Basic auth is not validated." "Expected 401."
  else warn "Wrong-credential POST not a clean 401 (got $CODE) but did not succeed."; fi

  # ---- 4. REGULAR NEW reservation --------------------------------------------
  hdr "4. REGULAR NEW — valid reservation should return success + reservationId"
  TXID_NEW="$(gen_uuid)"; RESNO_NEW="rt-new-$(date +%s)"
  request POST good "$(reservation "$TXID_NEW" REGULAR NEW "$RESNO_NEW")"
  RID_FIRST="$(json_field "$BODY" reservationId)"
  if [ "$CODE" = "200" ] && is_success_true "$BODY"; then
    pass "NEW returns 200 with success:true"
    [ -n "$RID_FIRST" ] && pass "Response includes reservationId ($RID_FIRST)" || fail "Response missing reservationId" "Per the contract the success body must include reservationId."
    ECHO_TX="$(json_field "$BODY" transactionId)"
    if [ -n "$ECHO_TX" ]; then
      [ "$ECHO_TX" = "$TXID_NEW" ] && pass "transactionId echoed back correctly" || warn "transactionId echoed but does not match what was sent" "sent $TXID_NEW, got $ECHO_TX"
    else warn "Response did not echo transactionId" "Recommended so the sender can correlate."; fi
  else
    fail "NEW expected 200 success:true, got HTTP $CODE" "body: $(printf '%s' "$BODY" | cut -c1-200)"
  fi

  # ---- 5. Idempotency — resend same transactionId ----------------------------
  hdr "5. Idempotency — resending the same transactionId must not duplicate"
  request POST good "$(reservation "$TXID_NEW" REGULAR NEW "$RESNO_NEW")"
  RID_SECOND="$(json_field "$BODY" reservationId)"
  if [ "$CODE" = "200" ] && is_success_true "$BODY"; then
    if [ -n "$RID_FIRST" ] && [ "$RID_SECOND" = "$RID_FIRST" ]; then pass "Same transactionId returns the same reservationId ($RID_SECOND) — idempotent"
    elif [ -z "$RID_FIRST" ]; then warn "Could not compare reservationIds (first response had none)."
    else fail "Resend produced a DIFFERENT reservationId ($RID_FIRST -> $RID_SECOND) — possible duplicate booking." "Dedupe on transactionId."; fi
  else fail "Idempotent resend expected 200 success:true, got HTTP $CODE"; fi

  # ---- 6. QUOTE ---------------------------------------------------------------
  hdr "6. QUOTE — reservationType QUOTE should return a totalAmount"
  request POST good "$(reservation "$(gen_uuid)" QUOTE NEW "rt-quote-$(date +%s)")"
  if [ "$CODE" = "200" ] && is_success_true "$BODY"; then
    AMT="$(json_field "$BODY" totalAmount)"
    [ -n "$AMT" ] && pass "QUOTE returns success with totalAmount ($AMT)" || fail "QUOTE returned success but no totalAmount" "QUOTE responses must include totalAmount."
  else fail "QUOTE expected 200 success:true, got HTTP $CODE" "body: $(printf '%s' "$BODY" | cut -c1-200)"; fi

  # ---- 7. Malformed JSON ------------------------------------------------------
  hdr "7. Robustness — malformed JSON should be rejected (not 5xx-crash)"
  request POST good '{ "this is": not valid json, }'
  if [ "$CODE" = "400" ] || [ "$CODE" = "422" ]; then pass "Malformed JSON rejected with $CODE"
  elif [ "${CODE:0:1}" = "4" ]; then pass "Malformed JSON rejected with $CODE (4xx)"
  elif is_success_true "$BODY"; then fail "Malformed JSON was accepted as success — input is not validated."
  elif [ "${CODE:0:1}" = "5" ]; then warn "Malformed JSON caused a $CODE server error — prefer a clean 400."
  else warn "Malformed JSON returned $CODE (expected 400)."; fi

  # ---- 8. UPDATE (informational) ---------------------------------------------
  hdr "8. UPDATE — action UPDATE on the known trip (informational)"
  request POST good "$(reservation "$TXID_NEW" REGULAR UPDATE "$RESNO_NEW")"
  if [ "$CODE" = "200" ] && is_success_true "$BODY"; then pass "UPDATE accepted with success:true"
  else warn "UPDATE returned HTTP $CODE (UPDATE handling varies by operator)" "body: $(printf '%s' "$BODY" | cut -c1-160)"; fi

  # ---- summary ----------------------------------------------------------------
  printf "\n%s──────── Summary ────────%s\n" "$C_B" "$C_0"
  printf "  %sPASS %d%s   %sWARN %d%s   %sFAIL %d%s\n" "$C_G" "$PASS" "$C_0" "$C_Y" "$WARN" "$C_0" "$C_R" "$FAIL" "$C_0"
  if [ "$FAIL" -eq 0 ]; then printf "  %sFarm In endpoint looks conformant.%s\n" "$C_G" "$C_0"; exit 0
  else printf "  %s%d check(s) failed — see above.%s\n" "$C_R" "$FAIL" "$C_0"; exit 1; fi
  ```
</Accordion>

## Keep going

<CardGroup cols={2}>
  <Card title="Quick Start" icon="rocket" href="https://docs.grdd.dev/gnet-platform/quick-start">
    The full technical integration guide behind these prompts.
  </Card>

  <Card title="API Reference" icon="book" href="https://gnet.docs.apiary.io/">
    Complete endpoint reference on Apiary.
  </Card>

  <Card title="Test with GBOOK" icon="vial" href="https://api.grdd.net/gbook">
    Fire test trips into your integration end-to-end. Use "SHOW JSON" to see exact payloads.
  </Card>

  <Card title="Need a hand?" icon="headphones" href="mailto:support@grdd.net">
    Our team can walk through the integration with you.
  </Card>
</CardGroup>
