Developers
Public API
Gripp matches excess inventory to buyer requirements worldwide. A buyer key can search listings and post requirements. A supplier key can publish stock. Buyer responses never include the supplier's identity or floor price.
Base URL https://grippai-production.up.railway.app/v1. Interactive reference at /v1/docs. Send Authorization: Bearer or X-Api-Key. Repeat a POST safely with Idempotency-Key.
Endpoints
POST /requirementsBuyer creates a requirementGET /requirementsBuyer lists their requirementsPOST /listingsSupplier creates an inventory listingPOST /listings/bulkSupplier imports JSON or CSVGET /listingsSearch listings. Buyers receive a blind viewGET /matchesMatches for the caller's requirements or listingsPOST /quotesBuyer asks for a quote on a listingGET /quotesQuote requests visible to the callerPOST /webhooksRegister an endpointPOST /webhooks/{id}/pingSend a signed test eventPOST /webhooks/{id}/replayReplay a logged deliveryPOST /mcpMCP endpoint for buyer agents
Webhooks
Register match.created, requirement.matched, listing.created, and quote.updated. Gripp signs the raw body with HMAC-SHA256 in X-Gripp-Signature. Failed deliveries retry with exponential backoff. The delivery log supports replay and a test ping.
Agents
Point an MCP client at https://grippai-production.up.railway.app/v1/mcp. Tools: search_inventory, post_requirement, list_matches, request_quote.
TypeScript
export class Gripp {
constructor(
private apiKey: string,
private baseUrl = "https://grippai-production.up.railway.app/v1",
) {}
private async request(method: string, path: string, body?: unknown, idempotencyKey?: string) {
const headers: Record<string, string> = {
Authorization: `Bearer ${this.apiKey}`,
Accept: "application/json",
}
if (body !== undefined) headers["Content-Type"] = "application/json"
if (idempotencyKey) headers["Idempotency-Key"] = idempotencyKey
const response = await fetch(`${this.baseUrl}${path}`, {
method,
headers,
body: body === undefined ? undefined : JSON.stringify(body),
})
const payload = await response.json()
if (!response.ok) throw new Error(payload?.error?.message || response.statusText)
return payload
}
searchInventory(query: string, category?: string) {
const params = new URLSearchParams({ q: query })
if (category) params.set("category", category)
return this.request("GET", `/listings?${params}`)
}
postRequirement(
requirement: { title: string; category: string; quantity: number },
idempotencyKey?: string,
) {
return this.request("POST", "/requirements", requirement, idempotencyKey)
}
}
Python
import json
from urllib.request import Request, urlopen
class GrippClient:
def __init__(self, api_key: str, base_url: str = "https://grippai-production.up.railway.app/v1"):
self.api_key = api_key
self.base_url = base_url.rstrip("/")
def _request(self, method: str, path: str, payload=None, idempotency_key: str | None = None):
headers = {
"Authorization": f"Bearer {self.api_key}",
"Accept": "application/json",
}
data = None
if payload is not None:
data = json.dumps(payload).encode()
headers["Content-Type"] = "application/json"
if idempotency_key:
headers["Idempotency-Key"] = idempotency_key
request = Request(self.base_url + path, data=data, headers=headers, method=method)
with urlopen(request) as response:
return json.load(response)
def search_inventory(self, query: str, category: str | None = None):
params = f"q={query}"
if category:
params += f"&category={category}"
return self._request("GET", f"/listings?{params}")
def post_requirement(self, requirement: dict, idempotency_key: str | None = None):
return self._request("POST", "/requirements", requirement, idempotency_key)