Cheese Store
v2.0.0Welcome to the Cheese Store API reference. This is a live example of how you can use Spectacle to generate beautiful static documentation for your APIs.
The Cheese Store API is organized around REST. It uses resource-oriented URLs, standard HTTP methods, and returns JSON responses. All requests are authenticated using an API key or OAuth2 token.
Getting Started
- Sign up for a developer account at cheesy.sourcey.com
- Generate an API key from your dashboard
- Include the key in your requests as
X-API-Keyheader
Rate Limiting
| Plan | Requests/hour | Burst |
|---|---|---|
| Free | 100 | 10 |
| Pro | 10,000 | 100 |
| Enterprise | Unlimited | 1,000 |
When you exceed the rate limit, the API returns 429 Too Many Requests with a Retry-After header.
Pagination
List endpoints return paginated results. Use cursor and limit query parameters:
{
"items": [...],
"nextCursor": "eyJpZCI6NDJ9",
"hasMore": true
}
Hard cheese gouda say cheese. Ricotta cauliflower cheese cheesecake bocconcini edam bocconcini fromage feta. Who moved my cheese bocconcini cheese and wine cottage cheese cheese on toast who moved my cheese caerphilly stinking bishop. Bocconcini cheesy feet the big cheese macaroni cheese cheesy feet mascarpone.
https://cheesy.sourcey.com/v2Productionhttps://sandbox.cheesy.sourcey.com/v2Sandbox (test data, rate limits relaxed)http://localhost:3000/v2Local development2.0.0Authentication
api_keyapiKeyAPI key authentication. Include your API key in the `X-API-Key` header. Get your key from the [developer dashboard](http://cheesy.sourcey.com/dashboard).
API Key: X-API-Key in header
cheesy_oauthoauth2OAuth2 authentication for full API access
Authorization: https://cheesy.sourcey.com/oauth/authorize
Scopes: read:cheeses — Read cheese data, write:cheeses — Create and update cheeses, read:orders — View order history, write:orders — Place and manage orders
Authorization: https://cheesy.sourcey.com/oauth/authorize
Token: https://cheesy.sourcey.com/oauth/token
Scopes: read:cheeses — Read cheese data, write:cheeses — Create and update cheeses, read:orders — View order history, write:orders — Place and manage orders
bearer_authhttpJWT token obtained from the `/customer/login` endpoint
Scheme: bearer (JWT)
Cheese
Cheese endpoints provide access to information and operations relating to the cheeses available in the store.
Cheeses can be filtered by status, tags, and category. Each cheese has a unique ID, name, category, and availability status.
List all cheeses
Returns a paginated list of cheeses in the store. Results can be filtered by status and sorted by name or creation date.
The response includes cursor-based pagination. Use the nextCursor value from the response as the cursor parameter in subsequent requests.
cursorstringqueryPagination cursor from a previous response
limitinteger[1, 100]20queryMaximum number of cheeses to return
statusstringavailablependingsoldqueryFilter by availability status
sortstringname-namecreatedAt-createdAtnamequerySort order for results
A paginated list of cheeses
Rate limit exceeded
api_keyapiKey in headerAPI key authentication. Include your API key in the `X-API-Key` header. Get your key from the [developer dashboard](http://cheesy.sourcey.com/dashboard).
cheesy_oauthoauth2OAuth2 authentication for full API access
Scopes: read:cheeses
curl -X GET 'https://cheesy.sourcey.com/v2/cheeses'const response = await fetch('https://cheesy.sourcey.com/v2/cheeses', {
method: 'GET',
});
const data = await response.json();import requests
response = requests.get('https://cheesy.sourcey.com/v2/cheeses')
data = response.json(){
"items": [
{
"id": 42,
"name": "Gorgonzola",
"category": {
"id": 1,
"name": "Italian Cheese"
},
"photoUrls": [
"https://wannabechef.com/gorgonzola.jpg"
],
"tags": [
{
"id": 1,
"name": "blue"
}
],
"status": "available",
"origin": "Italy",
"pricePerKg": 28.5,
"ageMonths": 12,
"organic": false,
"createdAt": "2024-01-15T09:30:00Z",
"updatedAt": "2024-01-15T09:30:00Z"
}
],
"nextCursor": "string",
"hasMore": true,
"total": 0
}{
"code": 404,
"type": "Not found",
"message": "Your cheese has already been eaten"
}| Header | Description | Type |
|---|---|---|
X-Total-Count | Total number of cheeses matching the filter | integer |
Link | Pagination links (RFC 8288) | string |
| Header | Description | Type |
|---|---|---|
Retry-After | Seconds to wait before retrying | integer |
X-Rate-Limit | Your rate limit (requests per hour) | integer |
X-Rate-Limit-Remaining | Remaining requests in the current window | integer |
Add a new cheese
Add a new cheese to the store inventory. The cheese name must be unique within its category.
application/jsonrequiredCheese object to add to the store
namestringrequiredcategoryobjectnamestringphotoUrlsArray<string>tagsArray<object>namestringstatusstringavailablependingsoldavailableoriginstring | nullpricePerKgnumber (float)ageMonthsinteger[0, 120]organicbooleanfalseCheese created successfully
A cheese with this name already exists in the category
The request body failed validation
cheesy_oauthoauth2OAuth2 authentication for full API access
Scopes: write:cheeses
curl -X POST 'https://cheesy.sourcey.com/v2/cheeses' \
-H 'Content-Type: application/json' \
-d '{
"name": "Gorgonzola",
"category": {
"name": "Italian Cheese"
},
"status": "available",
"photoUrls": [
"https://wannabechef.com/gorgonzola.jpg"
],
"tags": [
{
"name": "blue"
},
{
"name": "italian"
},
{
"name": "creamy"
}
]
}'const response = await fetch('https://cheesy.sourcey.com/v2/cheeses', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"name": "Gorgonzola",
"category": {
"name": "Italian Cheese"
},
"status": "available",
"photoUrls": [
"https://wannabechef.com/gorgonzola.jpg"
],
"tags": [
{
"name": "blue"
},
{
"name": "italian"
},
{
"name": "creamy"
}
]
}),
});
const data = await response.json();import requests
payload = {
"name": "Gorgonzola",
"category": {
"name": "Italian Cheese"
},
"status": "available",
"photoUrls": [
"https://wannabechef.com/gorgonzola.jpg"
],
"tags": [
{
"name": "blue"
},
{
"name": "italian"
},
{
"name": "creamy"
}
]
}
response = requests.post('https://cheesy.sourcey.com/v2/cheeses', json=payload)
data = response.json(){
"name": "string",
"category": {
"name": "string"
},
"photoUrls": [
"https://example.com"
],
"tags": [
{
"name": "string"
}
],
"status": "available",
"origin": "string",
"pricePerKg": 0,
"ageMonths": 0,
"organic": false
}{
"id": 42,
"name": "Gorgonzola",
"category": {
"id": 1,
"name": "Italian Cheese"
},
"photoUrls": [
"https://wannabechef.com/gorgonzola.jpg"
],
"tags": [
{
"id": 1,
"name": "blue"
}
],
"status": "available",
"origin": "Italy",
"pricePerKg": 28.5,
"ageMonths": 12,
"organic": false,
"createdAt": "2024-01-15T09:30:00Z",
"updatedAt": "2024-01-15T09:30:00Z"
}{
"code": 404,
"type": "Not found",
"message": "Your cheese has already been eaten"
}{
"code": 422,
"type": "Validation error",
"message": "Your cheese is not mouldy enough",
"errors": [
{
"field": "string",
"message": "string"
}
]
}| Header | Description | Type |
|---|---|---|
Location | URL of the newly created cheese | string (uri) |
Update an existing cheese
Replace an existing cheese with updated data. The cheese ID in the body must match an existing cheese.
application/jsonrequiredCheese object with updated fields
idinteger (int64)requiredUnique identifier
namestringrequiredName of the cheese
categoryobjectidinteger (int64)namestringphotoUrlsArray<string>URLs of cheese photos
tagsArray<object>Classification tags
idinteger (int64)namestringstatusstringavailablependingsoldrequiredAvailability status in the store
originstring | nullCountry of origin (null if unknown)
pricePerKgnumber (float)Price per kilogram in USD
ageMonthsinteger[0, 120]Aging period in months
organicbooleanfalseWhether the cheese is certified organic
createdAtstring (date-time)When the cheese was added to the store
updatedAtstring (date-time)When the cheese was last updated
Cheese updated successfully
The requested resource was not found
The request body failed validation
cheesy_oauthoauth2OAuth2 authentication for full API access
Scopes: write:cheeses
curl -X PUT 'https://cheesy.sourcey.com/v2/cheeses' \
-H 'Content-Type: application/json' \
-d '{
"id": 42,
"name": "Gorgonzola",
"category": {
"id": 1,
"name": "Italian Cheese"
},
"photoUrls": [
"https://wannabechef.com/gorgonzola.jpg"
],
"tags": [
{
"id": 1,
"name": "blue"
}
],
"status": "available",
"origin": "Italy",
"pricePerKg": 28.5,
"ageMonths": 12,
"organic": false,
"createdAt": "2024-01-15T09:30:00Z",
"updatedAt": "2024-01-15T09:30:00Z"
}'const response = await fetch('https://cheesy.sourcey.com/v2/cheeses', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"id": 42,
"name": "Gorgonzola",
"category": {
"id": 1,
"name": "Italian Cheese"
},
"photoUrls": [
"https://wannabechef.com/gorgonzola.jpg"
],
"tags": [
{
"id": 1,
"name": "blue"
}
],
"status": "available",
"origin": "Italy",
"pricePerKg": 28.5,
"ageMonths": 12,
"organic": false,
"createdAt": "2024-01-15T09:30:00Z",
"updatedAt": "2024-01-15T09:30:00Z"
}),
});
const data = await response.json();import requests
payload = {
"id": 42,
"name": "Gorgonzola",
"category": {
"id": 1,
"name": "Italian Cheese"
},
"photoUrls": [
"https://wannabechef.com/gorgonzola.jpg"
],
"tags": [
{
"id": 1,
"name": "blue"
}
],
"status": "available",
"origin": "Italy",
"pricePerKg": 28.5,
"ageMonths": 12,
"organic": False,
"createdAt": "2024-01-15T09:30:00Z",
"updatedAt": "2024-01-15T09:30:00Z"
}
response = requests.put('https://cheesy.sourcey.com/v2/cheeses', json=payload)
data = response.json(){
"id": 42,
"name": "Gorgonzola",
"category": {
"id": 1,
"name": "Italian Cheese"
},
"photoUrls": [
"https://wannabechef.com/gorgonzola.jpg"
],
"tags": [
{
"id": 1,
"name": "blue"
}
],
"status": "available",
"origin": "Italy",
"pricePerKg": 28.5,
"ageMonths": 12,
"organic": false,
"createdAt": "2024-01-15T09:30:00Z",
"updatedAt": "2024-01-15T09:30:00Z"
}{
"id": 42,
"name": "Gorgonzola",
"category": {
"id": 1,
"name": "Italian Cheese"
},
"photoUrls": [
"https://wannabechef.com/gorgonzola.jpg"
],
"tags": [
{
"id": 1,
"name": "blue"
}
],
"status": "available",
"origin": "Italy",
"pricePerKg": 28.5,
"ageMonths": 12,
"organic": false,
"createdAt": "2024-01-15T09:30:00Z",
"updatedAt": "2024-01-15T09:30:00Z"
}{
"code": 404,
"type": "Not found",
"message": "Your cheese has already been eaten"
}{
"code": 422,
"type": "Validation error",
"message": "Your cheese is not mouldy enough",
"errors": [
{
"field": "string",
"message": "string"
}
]
}Find cheeses by status
Multiple status values can be provided as comma-separated strings. Returns all cheeses matching any of the given statuses.
statusArray<string>availablependingsoldrequiredqueryStatus values to filter by
Matching cheeses
Invalid status value provided
cheesy_oauthoauth2OAuth2 authentication for full API access
Scopes: read:cheeses
curl -X GET 'https://cheesy.sourcey.com/v2/cheeses/findByStatus'const response = await fetch('https://cheesy.sourcey.com/v2/cheeses/findByStatus', {
method: 'GET',
});
const data = await response.json();import requests
response = requests.get('https://cheesy.sourcey.com/v2/cheeses/findByStatus')
data = response.json()[
{
"id": 42,
"name": "Gorgonzola",
"category": {
"id": 1,
"name": "Italian Cheese"
},
"photoUrls": [
"https://wannabechef.com/gorgonzola.jpg"
],
"tags": [
{
"id": 1,
"name": "blue"
}
],
"status": "available",
"origin": "Italy",
"pricePerKg": 28.5,
"ageMonths": 12,
"organic": false,
"createdAt": "2024-01-15T09:30:00Z",
"updatedAt": "2024-01-15T09:30:00Z"
}
]{
"code": 404,
"type": "Not found",
"message": "Your cheese has already been eaten"
}Get cheese by ID
Returns detailed information about a specific cheese, including its category, tags, and current availability status.
cheeseIdinteger (int64)requiredpathUnique identifier for the cheese
Cheese details
The requested resource was not found
api_keyapiKey in headerAPI key authentication. Include your API key in the `X-API-Key` header. Get your key from the [developer dashboard](http://cheesy.sourcey.com/dashboard).
curl -X GET 'https://cheesy.sourcey.com/v2/cheeses/{cheeseId}'const response = await fetch('https://cheesy.sourcey.com/v2/cheeses/{cheeseId}', {
method: 'GET',
});
const data = await response.json();import requests
response = requests.get('https://cheesy.sourcey.com/v2/cheeses/{cheeseId}')
data = response.json(){
"id": 42,
"name": "Gorgonzola",
"category": {
"id": 1,
"name": "Italian Cheese"
},
"photoUrls": [
"https://wannabechef.com/gorgonzola.jpg"
],
"tags": [
{
"id": 1,
"name": "blue"
}
],
"status": "available",
"origin": "Italy",
"pricePerKg": 28.5,
"ageMonths": 12,
"organic": false,
"createdAt": "2024-01-15T09:30:00Z",
"updatedAt": "2024-01-15T09:30:00Z"
}{
"code": 404,
"type": "Not found",
"message": "Your cheese has already been eaten"
}Update cheese with form data
Updates specific fields of a cheese using form data. Only provided fields are updated.
cheeseIdinteger (int64)requiredpathUnique identifier for the cheese
namestringqueryUpdated name of the cheese
statusstringavailablependingsoldqueryUpdated status of the cheese
Cheese updated
The requested resource was not found
The request body failed validation
cheesy_oauthoauth2OAuth2 authentication for full API access
Scopes: write:cheeses
curl -X POST 'https://cheesy.sourcey.com/v2/cheeses/{cheeseId}'const response = await fetch('https://cheesy.sourcey.com/v2/cheeses/{cheeseId}', {
method: 'POST',
});
const data = await response.json();import requests
response = requests.post('https://cheesy.sourcey.com/v2/cheeses/{cheeseId}')
data = response.json(){
"id": 42,
"name": "Gorgonzola",
"category": {
"id": 1,
"name": "Italian Cheese"
},
"photoUrls": [
"https://wannabechef.com/gorgonzola.jpg"
],
"tags": [
{
"id": 1,
"name": "blue"
}
],
"status": "available",
"origin": "Italy",
"pricePerKg": 28.5,
"ageMonths": 12,
"organic": false,
"createdAt": "2024-01-15T09:30:00Z",
"updatedAt": "2024-01-15T09:30:00Z"
}{
"code": 404,
"type": "Not found",
"message": "Your cheese has already been eaten"
}{
"code": 422,
"type": "Validation error",
"message": "Your cheese is not mouldy enough",
"errors": [
{
"field": "string",
"message": "string"
}
]
}Delete a cheese
Permanently removes a cheese from the store. This action cannot be undone. Any pending orders for this cheese will be cancelled.
cheeseIdinteger (int64)requiredpathUnique identifier for the cheese
X-Confirm-DeletebooleanrequiredheaderMust be set to true to confirm deletion
Cheese deleted successfully
The requested resource was not found
Cheese has active orders and cannot be deleted
cheesy_oauthoauth2OAuth2 authentication for full API access
Scopes: write:cheeses
curl -X DELETE 'https://cheesy.sourcey.com/v2/cheeses/{cheeseId}'const response = await fetch('https://cheesy.sourcey.com/v2/cheeses/{cheeseId}', {
method: 'DELETE',
});
const data = await response.json();import requests
response = requests.delete('https://cheesy.sourcey.com/v2/cheeses/{cheeseId}')
data = response.json(){
"code": 404,
"type": "Not found",
"message": "Your cheese has already been eaten"
}{
"code": 404,
"type": "Not found",
"message": "Your cheese has already been eaten"
}Upload a cheese image
Upload a photo of the cheese. Supports JPEG, PNG, and WebP formats. Maximum file size is 10MB.
multipart/form-datarequiredfilestring (binary)requiredImage file (JPEG, PNG, or WebP, max 10MB)
captionstringOptional caption for the image
isPrimarybooleanfalseSet as the primary display image
cheeseIdinteger (int64)requiredpathID of cheese to upload image for
Image uploaded successfully
File too large (max 10MB)
Unsupported media type
cheesy_oauthoauth2OAuth2 authentication for full API access
Scopes: write:cheeses
curl -X POST 'https://cheesy.sourcey.com/v2/cheeses/{cheeseId}/uploadImage' \
-H 'Content-Type: multipart/form-data' \
-d '{
"file": "<binary>",
"caption": "string",
"isPrimary": false
}'const response = await fetch('https://cheesy.sourcey.com/v2/cheeses/{cheeseId}/uploadImage', {
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data',
},
body: JSON.stringify({
"file": "<binary>",
"caption": "string",
"isPrimary": false
}),
});
const data = await response.json();import requests
payload = {
"file": "<binary>",
"caption": "string",
"isPrimary": False
}
response = requests.post('https://cheesy.sourcey.com/v2/cheeses/{cheeseId}/uploadImage', json=payload)
data = response.json(){
"file": "<binary>",
"caption": "string",
"isPrimary": false
}{
"imageUrl": "https://example.com",
"thumbnailUrl": "https://example.com",
"size": 0
}{
"code": 404,
"type": "Not found",
"message": "Your cheese has already been eaten"
}{
"code": 404,
"type": "Not found",
"message": "Your cheese has already been eaten"
}Store
Store endpoints provide access to cheese store orders and inventory management. Orders track the purchase lifecycle from placement through delivery.
Get store inventory
Returns a map of cheese status codes to quantities, representing the current inventory levels.
Inventory counts by status
api_keyapiKey in headerAPI key authentication. Include your API key in the `X-API-Key` header. Get your key from the [developer dashboard](http://cheesy.sourcey.com/dashboard).
curl -X GET 'https://cheesy.sourcey.com/v2/store/inventory'const response = await fetch('https://cheesy.sourcey.com/v2/store/inventory', {
method: 'GET',
});
const data = await response.json();import requests
response = requests.get('https://cheesy.sourcey.com/v2/store/inventory')
data = response.json(){}Place a cheese order
Place an order to purchase cheese from the store. The cheese must be in available status.
Orders are processed asynchronously. Use the returned order ID to check status via GET /store/order/{orderId}.
application/jsonrequiredOrder details
itemsArray<object>requiredcheeseIdinteger (int64)requiredquantityinteger[1, 100]requiredshippingAddressobjectrequiredstreetstringrequiredcitystringrequiredstatestring | nullzipCodestringcountrystringrequiredISO 3166-1 alpha-2 country code
notesstring | nullOrder placed successfully
The request body failed validation
cheesy_oauthoauth2OAuth2 authentication for full API access
Scopes: write:orders
curl -X POST 'https://cheesy.sourcey.com/v2/store/order' \
-H 'Content-Type: application/json' \
-d '{
"items": [
{
"cheeseId": 42,
"quantity": 3
},
{
"cheeseId": 17,
"quantity": 1
}
],
"shippingAddress": {
"street": "123 Cheese Lane",
"city": "Cheddarville",
"state": "WI",
"zipCode": "53001",
"country": "US"
},
"notes": "Please wrap each wheel separately"
}'const response = await fetch('https://cheesy.sourcey.com/v2/store/order', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"items": [
{
"cheeseId": 42,
"quantity": 3
},
{
"cheeseId": 17,
"quantity": 1
}
],
"shippingAddress": {
"street": "123 Cheese Lane",
"city": "Cheddarville",
"state": "WI",
"zipCode": "53001",
"country": "US"
},
"notes": "Please wrap each wheel separately"
}),
});
const data = await response.json();import requests
payload = {
"items": [
{
"cheeseId": 42,
"quantity": 3
},
{
"cheeseId": 17,
"quantity": 1
}
],
"shippingAddress": {
"street": "123 Cheese Lane",
"city": "Cheddarville",
"state": "WI",
"zipCode": "53001",
"country": "US"
},
"notes": "Please wrap each wheel separately"
}
response = requests.post('https://cheesy.sourcey.com/v2/store/order', json=payload)
data = response.json(){
"items": [
{
"cheeseId": 0,
"quantity": 1
}
],
"shippingAddress": {
"street": "123 Cheese Lane",
"city": "Cheddarville",
"state": "WI",
"zipCode": "53001",
"country": "US"
},
"notes": "string"
}{
"id": 1001,
"items": [
{
"cheeseId": 42,
"cheeseName": "Gorgonzola",
"quantity": 3,
"pricePerKg": 28.5
},
{
"cheeseId": 17,
"cheeseName": "Brie",
"quantity": 1,
"pricePerKg": 22
}
],
"shippingAddress": {
"street": "123 Cheese Lane",
"city": "Cheddarville",
"state": "WI",
"zipCode": "53001",
"country": "US"
},
"status": "placed",
"notes": "Please wrap each wheel separately",
"total": 114.5,
"shipDate": null,
"createdAt": "2026-03-10T14:30:00Z",
"updatedAt": "2026-03-10T14:30:00Z"
}{
"code": 422,
"type": "Validation error",
"message": "Your cheese is not mouldy enough",
"errors": [
{
"field": "string",
"message": "string"
}
]
}| Header | Description | Type |
|---|---|---|
Location | URL of the created order | string (uri) |
Get order by ID
Retrieve details of a specific order. For testing, use order IDs between 1 and 10.
orderIdinteger (int64)requiredpathID of the order
Order details
The requested resource was not found
api_keyapiKey in headerAPI key authentication. Include your API key in the `X-API-Key` header. Get your key from the [developer dashboard](http://cheesy.sourcey.com/dashboard).
curl -X GET 'https://cheesy.sourcey.com/v2/store/order/{orderId}'const response = await fetch('https://cheesy.sourcey.com/v2/store/order/{orderId}', {
method: 'GET',
});
const data = await response.json();import requests
response = requests.get('https://cheesy.sourcey.com/v2/store/order/{orderId}')
data = response.json(){
"id": 1001,
"items": [
{
"cheeseId": 42,
"cheeseName": "Gorgonzola",
"quantity": 3,
"pricePerKg": 28.5
},
{
"cheeseId": 17,
"cheeseName": "Brie",
"quantity": 1,
"pricePerKg": 22
}
],
"shippingAddress": {
"street": "123 Cheese Lane",
"city": "Cheddarville",
"state": "WI",
"zipCode": "53001",
"country": "US"
},
"status": "placed",
"notes": "Please wrap each wheel separately",
"total": 114.5,
"shipDate": null,
"createdAt": "2026-03-10T14:30:00Z",
"updatedAt": "2026-03-10T14:30:00Z"
}{
"code": 404,
"type": "Not found",
"message": "Your cheese has already been eaten"
}Cancel an order
Cancel a pending order. Orders that have already been shipped cannot be cancelled.
orderIdinteger (int64)requiredpathID of the order
Order cancelled successfully
The requested resource was not found
Order has already been shipped
cheesy_oauthoauth2OAuth2 authentication for full API access
Scopes: write:orders
curl -X DELETE 'https://cheesy.sourcey.com/v2/store/order/{orderId}'const response = await fetch('https://cheesy.sourcey.com/v2/store/order/{orderId}', {
method: 'DELETE',
});
const data = await response.json();import requests
response = requests.delete('https://cheesy.sourcey.com/v2/store/order/{orderId}')
data = response.json(){
"code": 404,
"type": "Not found",
"message": "Your cheese has already been eaten"
}{
"code": 404,
"type": "Not found",
"message": "Your cheese has already been eaten"
}Customer
Customer endpoints handle account creation, authentication, and profile management.
Customer accounts support OAuth2 login and API key authentication.
Create a customer account
Register a new customer account. The username and email must be unique.
application/jsonrequiredCustomer registration details
usernamestringrequiredfirstNamestringlastNamestringemailstring (email)requiredpasswordstring (password)requiredphonestring | nullCustomer account created
Username or email already exists
The request body failed validation
curl -X POST 'https://cheesy.sourcey.com/v2/customer' \
-H 'Content-Type: application/json' \
-d '{
"username": "gordo",
"firstName": "Alotta",
"lastName": "Cheese",
"email": "love@cheese.com",
"password": "secret123!",
"phone": "+3344556677"
}'const response = await fetch('https://cheesy.sourcey.com/v2/customer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"username": "gordo",
"firstName": "Alotta",
"lastName": "Cheese",
"email": "love@cheese.com",
"password": "secret123!",
"phone": "+3344556677"
}),
});
const data = await response.json();import requests
payload = {
"username": "gordo",
"firstName": "Alotta",
"lastName": "Cheese",
"email": "love@cheese.com",
"password": "secret123!",
"phone": "+3344556677"
}
response = requests.post('https://cheesy.sourcey.com/v2/customer', json=payload)
data = response.json(){
"username": "string",
"firstName": "string",
"lastName": "string",
"email": "user@example.com",
"password": "********",
"phone": "string"
}{
"id": 1,
"username": "gordo",
"firstName": "Alotta",
"lastName": "Cheese",
"email": "love@cheese.com",
"phone": "+3344556677",
"customerStatus": "gold",
"favouriteCheese": {
"id": 42,
"name": "Gorgonzola",
"category": {
"id": 1,
"name": "Italian Cheese"
},
"photoUrls": [
"https://wannabechef.com/gorgonzola.jpg"
],
"tags": [
{
"id": 1,
"name": "blue"
}
],
"status": "available",
"origin": "Italy",
"pricePerKg": 28.5,
"ageMonths": 12,
"organic": false,
"createdAt": "2024-01-15T09:30:00Z",
"updatedAt": "2024-01-15T09:30:00Z"
},
"createdAt": "2024-01-15T09:30:00Z"
}{
"code": 404,
"type": "Not found",
"message": "Your cheese has already been eaten"
}{
"code": 422,
"type": "Validation error",
"message": "Your cheese is not mouldy enough",
"errors": [
{
"field": "string",
"message": "string"
}
]
}Create multiple customers
Create a batch of customer accounts from an array. Each customer is validated independently - partial success is possible.
Returns the created customers and any validation errors:
{
"created": [...],
"errors": [
{ "index": 2, "message": "Email already exists" }
]
}
application/jsonrequiredArray of customer objects to create
usernamestringrequiredfirstNamestringlastNamestringemailstring (email)requiredpasswordstring (password)requiredphonestring | nullBatch creation result
The request body failed validation
curl -X POST 'https://cheesy.sourcey.com/v2/customer/createMultiple' \
-H 'Content-Type: application/json' \
-d '[
{
"username": "string",
"firstName": "string",
"lastName": "string",
"email": "user@example.com",
"password": "********",
"phone": "string"
}
]'const response = await fetch('https://cheesy.sourcey.com/v2/customer/createMultiple', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify([
{
"username": "string",
"firstName": "string",
"lastName": "string",
"email": "user@example.com",
"password": "********",
"phone": "string"
}
]),
});
const data = await response.json();import requests
payload = [
{
"username": "string",
"firstName": "string",
"lastName": "string",
"email": "user@example.com",
"password": "********",
"phone": "string"
}
]
response = requests.post('https://cheesy.sourcey.com/v2/customer/createMultiple', json=payload)
data = response.json()[
{
"username": "string",
"firstName": "string",
"lastName": "string",
"email": "user@example.com",
"password": "********",
"phone": "string"
}
]{
"created": [
{
"id": 1,
"username": "gordo",
"firstName": "Alotta",
"lastName": "Cheese",
"email": "love@cheese.com",
"phone": "+3344556677",
"customerStatus": "gold",
"favouriteCheese": {
"id": 42,
"name": "Gorgonzola",
"category": {
"id": 1,
"name": "Italian Cheese"
},
"photoUrls": [
"https://wannabechef.com/gorgonzola.jpg"
],
"tags": [
{
"id": 1,
"name": "blue"
}
],
"status": "available",
"origin": "Italy",
"pricePerKg": 28.5,
"ageMonths": 12,
"organic": false,
"createdAt": "2024-01-15T09:30:00Z",
"updatedAt": "2024-01-15T09:30:00Z"
},
"createdAt": "2024-01-15T09:30:00Z"
}
],
"errors": [
{
"index": 0,
"message": "string"
}
]
}{
"code": 422,
"type": "Validation error",
"message": "Your cheese is not mouldy enough",
"errors": [
{
"field": "string",
"message": "string"
}
]
}Customer login
Authenticate a customer and receive a session token. The token expires after 24 hours.
application/jsonrequiredusernamestringrequiredThe customer's username
passwordstring (password)requiredThe customer's password
Login successful
Invalid username or password
curl -X POST 'https://cheesy.sourcey.com/v2/customer/login' \
-H 'Content-Type: application/json' \
-d '{
"username": "string",
"password": "********"
}'const response = await fetch('https://cheesy.sourcey.com/v2/customer/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"username": "string",
"password": "********"
}),
});
const data = await response.json();import requests
payload = {
"username": "string",
"password": "********"
}
response = requests.post('https://cheesy.sourcey.com/v2/customer/login', json=payload)
data = response.json(){
"username": "string",
"password": "********"
}{
"token": "string",
"expiresAt": "2024-01-15T09:30:00Z"
}{
"code": 404,
"type": "Not found",
"message": "Your cheese has already been eaten"
}| Header | Description | Type |
|---|---|---|
X-Rate-Limit | Requests per hour allowed for this customer | integer (int32) |
X-Expires-After | Token expiration date-time (UTC) | string (date-time) |
Customer logout
End the current customer session and invalidate the session token.
Logout successful
bearer_authhttp (bearer)JWT token obtained from the `/customer/login` endpoint
curl -X GET 'https://cheesy.sourcey.com/v2/customer/logout'const response = await fetch('https://cheesy.sourcey.com/v2/customer/logout', {
method: 'GET',
});
const data = await response.json();import requests
response = requests.get('https://cheesy.sourcey.com/v2/customer/logout')
data = response.json()Get customer by username
Retrieve a customer's profile by their username.
usernamestringrequiredpathThe customer's username
Customer profile
The requested resource was not found
api_keyapiKey in headerAPI key authentication. Include your API key in the `X-API-Key` header. Get your key from the [developer dashboard](http://cheesy.sourcey.com/dashboard).
bearer_authhttp (bearer)JWT token obtained from the `/customer/login` endpoint
curl -X GET 'https://cheesy.sourcey.com/v2/customer/{username}'const response = await fetch('https://cheesy.sourcey.com/v2/customer/{username}', {
method: 'GET',
});
const data = await response.json();import requests
response = requests.get('https://cheesy.sourcey.com/v2/customer/{username}')
data = response.json(){
"id": 1,
"username": "gordo",
"firstName": "Alotta",
"lastName": "Cheese",
"email": "love@cheese.com",
"phone": "+3344556677",
"customerStatus": "gold",
"favouriteCheese": {
"id": 42,
"name": "Gorgonzola",
"category": {
"id": 1,
"name": "Italian Cheese"
},
"photoUrls": [
"https://wannabechef.com/gorgonzola.jpg"
],
"tags": [
{
"id": 1,
"name": "blue"
}
],
"status": "available",
"origin": "Italy",
"pricePerKg": 28.5,
"ageMonths": 12,
"organic": false,
"createdAt": "2024-01-15T09:30:00Z",
"updatedAt": "2024-01-15T09:30:00Z"
},
"createdAt": "2024-01-15T09:30:00Z"
}{
"code": 404,
"type": "Not found",
"message": "Your cheese has already been eaten"
}Update customer profile
Update a customer's profile. Can only be performed by the authenticated customer.
application/jsonrequiredUpdated customer fields
usernamestringrequiredfirstNamestringlastNamestringemailstring (email)requiredpasswordstring (password)requiredphonestring | nullusernamestringrequiredpathThe customer's username
Customer updated
The requested resource was not found
The request body failed validation
bearer_authhttp (bearer)JWT token obtained from the `/customer/login` endpoint
curl -X PUT 'https://cheesy.sourcey.com/v2/customer/{username}' \
-H 'Content-Type: application/json' \
-d '{
"username": "string",
"firstName": "string",
"lastName": "string",
"email": "user@example.com",
"password": "********",
"phone": "string"
}'const response = await fetch('https://cheesy.sourcey.com/v2/customer/{username}', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"username": "string",
"firstName": "string",
"lastName": "string",
"email": "user@example.com",
"password": "********",
"phone": "string"
}),
});
const data = await response.json();import requests
payload = {
"username": "string",
"firstName": "string",
"lastName": "string",
"email": "user@example.com",
"password": "********",
"phone": "string"
}
response = requests.put('https://cheesy.sourcey.com/v2/customer/{username}', json=payload)
data = response.json(){
"username": "string",
"firstName": "string",
"lastName": "string",
"email": "user@example.com",
"password": "********",
"phone": "string"
}{
"id": 1,
"username": "gordo",
"firstName": "Alotta",
"lastName": "Cheese",
"email": "love@cheese.com",
"phone": "+3344556677",
"customerStatus": "gold",
"favouriteCheese": {
"id": 42,
"name": "Gorgonzola",
"category": {
"id": 1,
"name": "Italian Cheese"
},
"photoUrls": [
"https://wannabechef.com/gorgonzola.jpg"
],
"tags": [
{
"id": 1,
"name": "blue"
}
],
"status": "available",
"origin": "Italy",
"pricePerKg": 28.5,
"ageMonths": 12,
"organic": false,
"createdAt": "2024-01-15T09:30:00Z",
"updatedAt": "2024-01-15T09:30:00Z"
},
"createdAt": "2024-01-15T09:30:00Z"
}{
"code": 404,
"type": "Not found",
"message": "Your cheese has already been eaten"
}{
"code": 422,
"type": "Validation error",
"message": "Your cheese is not mouldy enough",
"errors": [
{
"field": "string",
"message": "string"
}
]
}Delete customer account
Permanently delete a customer account and all associated data. This action cannot be undone.
usernamestringrequiredpathThe customer's username
Customer deleted
The requested resource was not found
bearer_authhttp (bearer)JWT token obtained from the `/customer/login` endpoint
curl -X DELETE 'https://cheesy.sourcey.com/v2/customer/{username}'const response = await fetch('https://cheesy.sourcey.com/v2/customer/{username}', {
method: 'DELETE',
});
const data = await response.json();import requests
response = requests.delete('https://cheesy.sourcey.com/v2/customer/{username}')
data = response.json(){
"code": 404,
"type": "Not found",
"message": "Your cheese has already been eaten"
}Models
Cheese
objectidinteger (int64)requiredUnique identifier
namestringrequiredName of the cheese
categoryobjectidinteger (int64)namestringphotoUrlsArray<string>URLs of cheese photos
tagsArray<object>Classification tags
idinteger (int64)namestringstatusstringavailablependingsoldrequiredAvailability status in the store
originstring | nullCountry of origin (null if unknown)
pricePerKgnumber (float)Price per kilogram in USD
ageMonthsinteger[0, 120]Aging period in months
organicbooleanfalseWhether the cheese is certified organic
createdAtstring (date-time)When the cheese was added to the store
updatedAtstring (date-time)When the cheese was last updated
{
"id": 42,
"name": "Gorgonzola",
"category": {
"id": 1,
"name": "Italian Cheese"
},
"photoUrls": [
"https://wannabechef.com/gorgonzola.jpg"
],
"tags": [
{
"id": 1,
"name": "blue"
}
],
"status": "available",
"origin": "Italy",
"pricePerKg": 28.5,
"ageMonths": 12,
"organic": false,
"createdAt": "2024-01-15T09:30:00Z",
"updatedAt": "2024-01-15T09:30:00Z"
}CheeseInput
objectnamestringrequiredcategoryobjectnamestringphotoUrlsArray<string>tagsArray<object>namestringstatusstringavailablependingsoldavailableoriginstring | nullpricePerKgnumber (float)ageMonthsinteger[0, 120]organicbooleanfalse{
"name": "string",
"category": {
"name": "string"
},
"photoUrls": [
"https://example.com"
],
"tags": [
{
"name": "string"
}
],
"status": "available",
"origin": "string",
"pricePerKg": 0,
"ageMonths": 0,
"organic": false
}CheeseList
objectPaginated list of cheeses
itemsArray<object>idinteger (int64)requiredUnique identifier
namestringrequiredName of the cheese
categoryobjectidinteger (int64)namestringphotoUrlsArray<string>URLs of cheese photos
tagsArray<object>Classification tags
idinteger (int64)namestringstatusstringavailablependingsoldrequiredAvailability status in the store
originstring | nullCountry of origin (null if unknown)
pricePerKgnumber (float)Price per kilogram in USD
ageMonthsinteger[0, 120]Aging period in months
organicbooleanfalseWhether the cheese is certified organic
createdAtstring (date-time)When the cheese was added to the store
updatedAtstring (date-time)When the cheese was last updated
nextCursorstring | nullCursor for the next page (null if no more results)
hasMorebooleanWhether there are more results
totalintegerTotal number of matching cheeses
{
"items": [
{
"id": 42,
"name": "Gorgonzola",
"category": {
"id": 1,
"name": "Italian Cheese"
},
"photoUrls": [
"https://wannabechef.com/gorgonzola.jpg"
],
"tags": [
{
"id": 1,
"name": "blue"
}
],
"status": "available",
"origin": "Italy",
"pricePerKg": 28.5,
"ageMonths": 12,
"organic": false,
"createdAt": "2024-01-15T09:30:00Z",
"updatedAt": "2024-01-15T09:30:00Z"
}
],
"nextCursor": "string",
"hasMore": true,
"total": 0
}Order
objectidinteger (int64)itemsArray<object>cheeseIdinteger (int64)cheeseNamestringquantityintegerpricePerKgnumber (float)shippingAddressobjectstreetstringrequiredcitystringrequiredstatestring | nullzipCodestringcountrystringrequiredISO 3166-1 alpha-2 country code
statusstringplacedapprovedshippeddeliveredcancelledOrder lifecycle status
notesstring | nullSpecial instructions for the order
totalnumber (float)Order total in USD
shipDatestring (date-time) | nullEstimated ship date (null if not yet scheduled)
createdAtstring (date-time)updatedAtstring (date-time){
"id": 1001,
"items": [
{
"cheeseId": 42,
"cheeseName": "Gorgonzola",
"quantity": 3,
"pricePerKg": 28.5
},
{
"cheeseId": 17,
"cheeseName": "Brie",
"quantity": 1,
"pricePerKg": 22
}
],
"shippingAddress": {
"street": "123 Cheese Lane",
"city": "Cheddarville",
"state": "WI",
"zipCode": "53001",
"country": "US"
},
"status": "placed",
"notes": "Please wrap each wheel separately",
"total": 114.5,
"shipDate": null,
"createdAt": "2026-03-10T14:30:00Z",
"updatedAt": "2026-03-10T14:30:00Z"
}OrderInput
objectitemsArray<object>requiredcheeseIdinteger (int64)requiredquantityinteger[1, 100]requiredshippingAddressobjectrequiredstreetstringrequiredcitystringrequiredstatestring | nullzipCodestringcountrystringrequiredISO 3166-1 alpha-2 country code
notesstring | null{
"items": [
{
"cheeseId": 0,
"quantity": 1
}
],
"shippingAddress": {
"street": "123 Cheese Lane",
"city": "Cheddarville",
"state": "WI",
"zipCode": "53001",
"country": "US"
},
"notes": "string"
}OrderItem
objectcheeseIdinteger (int64)cheeseNamestringquantityintegerpricePerKgnumber (float){
"cheeseId": 0,
"cheeseName": "string",
"quantity": 1,
"pricePerKg": 0
}Address
objectstreetstringrequiredcitystringrequiredstatestring | nullzipCodestringcountrystringrequiredISO 3166-1 alpha-2 country code
{
"street": "123 Cheese Lane",
"city": "Cheddarville",
"state": "WI",
"zipCode": "53001",
"country": "US"
}Customer
objectidinteger (int64)usernamestringfirstNamestringCustomer first name
lastNamestringCustomer last name
emailstring (email)phonestring | nullcustomerStatusstringbronzesilvergoldplatinumCustomer tier based on purchase volume
favouriteCheeseobject | objectThe customer's favourite cheese (by reference or by name)
createdAtstring (date-time){
"id": 1,
"username": "gordo",
"firstName": "Alotta",
"lastName": "Cheese",
"email": "love@cheese.com",
"phone": "+3344556677",
"customerStatus": "gold",
"favouriteCheese": {
"id": 42,
"name": "Gorgonzola",
"category": {
"id": 1,
"name": "Italian Cheese"
},
"photoUrls": [
"https://wannabechef.com/gorgonzola.jpg"
],
"tags": [
{
"id": 1,
"name": "blue"
}
],
"status": "available",
"origin": "Italy",
"pricePerKg": 28.5,
"ageMonths": 12,
"organic": false,
"createdAt": "2024-01-15T09:30:00Z",
"updatedAt": "2024-01-15T09:30:00Z"
},
"createdAt": "2024-01-15T09:30:00Z"
}CustomerInput
objectusernamestringrequiredfirstNamestringlastNamestringemailstring (email)requiredpasswordstring (password)requiredphonestring | null{
"username": "string",
"firstName": "string",
"lastName": "string",
"email": "user@example.com",
"password": "********",
"phone": "string"
}Error
objectcodeinteger (int32)requiredtypestringmessagestringrequired{
"code": 404,
"type": "Not found",
"message": "Your cheese has already been eaten"
}ValidationError
objectcodeinteger (int32)typestringmessagestringerrorsArray<object>Individual field validation errors
fieldstringThe field that failed validation
messagestringHuman-readable error message
{
"code": 422,
"type": "Validation error",
"message": "Your cheese is not mouldy enough",
"errors": [
{
"field": "string",
"message": "string"
}
]
}