# API Integration Workflow — Sales Data **Tutorial**  ·  **Beginner**  ·  ⏱ ~20 min This tutorial walks through a complete API integration workflow using Green Check Access: authenticate, retrieve the CRBs connected to your service provider, and pull sales data for a specific date range. ## What you'll learn - Authenticate using OAuth 2.0 client credentials - Retrieve all CRBs connected to your service provider account - Fetch paginated sales data for a specific CRB and date range - Handle common API errors and rate limits ## Prerequisites - A Green Check Access account with service provider credentials - Your `client_id` and `client_secret` from the developer portal - Basic familiarity with REST APIs and `curl` or an HTTP client ## Step 1 — Authenticate All API requests require a Bearer token. Exchange your client credentials for an access token using the OAuth 2.0 client credentials flow. ```bash curl --location 'https://sandbox-api.greencheckverified.com/auth/token' \ --header 'Content-Type: application/json' \ --data '{ "client_id": "your-client-id", "client_secret": "your-client-secret", "grant_type": "client_credentials" }' ``` | Parameter | Description | | --- | --- | | `client_id` | Your unique client identifier from the developer portal | | `client_secret` | Your confidential client secret — never expose this in client-side code | | `grant_type` | Must be `client_credentials` for server-to-server flows | > 💡 **Tip — Token expiry** The response includes an `expires_at` timestamp. Check this value before each request and re-authenticate when the token is near expiry. ## Step 2 — Retrieve connected CRBs Fetch all CRBs associated with your service provider account. The response gives you the `crb_id` values you'll need to request sales data in Step 3. ```bash curl --location 'https://sandbox-api.greencheckverified.com/service-providers/{sp_id}/crbs' \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' ``` Each CRB in the response includes: - Business identification and contact details - Integration status with connected POS systems - Business type and operational information - Due diligence status (`due_diligence_status`) > ℹ️ **Which CRBs have data?** Only CRBs with an active POS integration will have sales data available. Check the `pos_integration_status` field on each CRB before querying sales. ## Step 3 — Fetch sales data With your `sp_id` and a `crb_id` from Step 2, request sales transactions for a specific date range. ```bash curl --location 'https://sandbox-api.greencheckverified.com/service-providers/{sp_id}/crbs/{crb_id}/sales?start_date=2024-01-01&end_date=2024-01-31&limit=100' \ --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' ``` | Query parameter | Description | | --- | --- | | `start_date` | Start of the date range (`YYYY-MM-DD`) | | `end_date` | End of the date range (`YYYY-MM-DD`) | | `limit` | Maximum records to return per page (default: `1000`, max: `1000`) | | `offset` | Number of records to skip — use for pagination | ### Example response ```json { "data": [ { "id": "transaction-uuid", "crb_id": "crb-uuid", "date": "2024-01-15T14:30:00.000Z", "local_date": "2024-01-15T07:30:00.000Z", "subtotal": 4500, "total_discounts": 500, "tax_paid": 315, "total_paid": 4315, "transaction_type": "sale", "pos_name": "Dutchie", "line_items": [ { "product_name": "Blue Dream - 1g Flower", "num_units": 1, "price_per_unit": 4500, "grams": 1, "product_type": "flower", "cannabis_product": true } ] } ], "metadata": { "total": 1250, "limit": 100, "offset": 0 } } ``` The `metadata` object tells you how to paginate through the full result set: - **`total`** — total number of records matching your query - **`limit`** — the page size used for this request - **`offset`** — the current starting position > 💡 **Tip — Pagination** If `metadata.total` exceeds your `limit`, increment `offset` by `limit` and repeat the request until you've retrieved all records. For example, to get page 2 of a 1000-record dataset with `limit=100`, set `offset=100`. ## Error reference | Status | Meaning | Common cause & fix | | --- | --- | --- | | `401 Unauthorized` | Invalid or missing token | Re-authenticate with `/auth/token` and retry | | `403 Forbidden` | Insufficient permissions | Verify your `sp_id` is correct and the CRB is connected to your org | | `404 Not Found` | Resource doesn't exist | Double-check `sp_id` and `crb_id` UUIDs | | `422 Validation Failed` | Invalid parameters | Check date format (`YYYY-MM-DD`) and other query parameters against the [API reference](/apis/swagger) | > ⚠️ **Rate limiting** The API enforces rate limits. Monitor these response headers and back off when `X-RateLimit-Remaining` reaches zero: | Header | Description | | --- | --- | | `X-RateLimit-Limit` | Maximum requests allowed per window | | `X-RateLimit-Remaining` | Requests left in the current window | | `X-RateLimit-Reset` | Unix timestamp when the window resets | ## Complete Python example The snippet below brings all three steps together using the `requests` library, including a paginated fetch that handles result sets larger than a single page. ```python import requests from datetime import datetime, timedelta class GreenCheckClient: def __init__(self, client_id, client_secret, base_url="https://sandbox-api.greencheckverified.com"): self.client_id = client_id self.client_secret = client_secret self.base_url = base_url self.token = None def authenticate(self): """Exchange credentials for a bearer token.""" response = requests.post(f"{self.base_url}/auth/token", json={ "client_id": self.client_id, "client_secret": self.client_secret, "grant_type": "client_credentials", }) response.raise_for_status() self.token = response.json()["access_token"] def get_crbs(self, sp_id): """Return all CRBs connected to your service provider.""" headers = {"Authorization": f"Bearer {self.token}"} response = requests.get( f"{self.base_url}/service-providers/{sp_id}/crbs", headers=headers, ) response.raise_for_status() return response.json() def get_sales(self, sp_id, crb_id, start_date, end_date, limit=100): """Fetch all sales records for a CRB across a date range, paginating as needed.""" headers = {"Authorization": f"Bearer {self.token}"} all_records = [] offset = 0 while True: response = requests.get( f"{self.base_url}/service-providers/{sp_id}/crbs/{crb_id}/sales", headers=headers, params={ "start_date": start_date, "end_date": end_date, "limit": limit, "offset": offset, }, ) response.raise_for_status() payload = response.json() all_records.extend(payload["data"]) if offset + limit >= payload["metadata"]["total"]: break offset += limit return all_records # Usage client = GreenCheckClient("your-client-id", "your-client-secret") client.authenticate() sp_id = "your-sp-id" crb_id = "target-crb-id" end_date = datetime.now().date() start_date = end_date - timedelta(days=30) crbs = client.get_crbs(sp_id) print(f"Found {len(crbs)} connected CRBs") sales = client.get_sales(sp_id, crb_id, start_date, end_date) print(f"Retrieved {len(sales)} sales transactions") ``` ## What's next - [Integration overview](/guides/integration-overview) — understand all four core integration patterns end to end - [CRB onboarding templates](/tutorials/crb-onboarding-template) — fetch submitted application data and documents for a CRB - [Integration status](/tools/integration-status) — see which POS systems expose which data types - [API reference](/apis/swagger) — full schema and endpoint documentation