Establish your partnership in GNet Connect and get your API credentials before go-live. The prompts leave every credential as a placeholder — fill in the values we issue you.
How it works
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.
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.
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.
Verify, then go live
Run the Farm In conformance test below against your endpoint, and use the GBOOK tool to exercise trips end-to-end, before switching on production credentials.
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 |
The prompts
- Farm In — receive trips
- Farm Out — send 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.Copy the full Farm In prompt
Copy the full Farm In prompt
# 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/gnet-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.
Builds authentication (
getToken2), sendTrip, your callback-webhook receiver for provider updates, and UPDATE / cancelTripByTransactionId. Expand, copy, fill the Variables block, and paste.Copy the full Farm Out prompt
Copy the full Farm Out prompt
# 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
- 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.
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. Purebash + curl, with 0/1 exit codes for CI.
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
GEThealth →200 {"success": true}- Basic auth enforced — missing and wrong credentials are rejected with
401 - REGULAR
NEW→success+reservationId+ thetransactionIdechoed back - Idempotency — the same
transactionIdreturns the samereservationId(no duplicate booking) QUOTE→ returns atotalAmount- Malformed JSON → rejected with
400, not a 5xx crash
Copy the Farm In conformance test (bash)
Copy the Farm In conformance test (bash)
#!/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
Keep going
Quick Start
The full technical integration guide behind these prompts.
API Reference
Complete endpoint reference on Apiary.
Test with GBOOK
Fire test trips into your integration end-to-end. Use “SHOW JSON” to see exact payloads.
Need a hand?
Our team can walk through the integration with you.