跳转到主要内容

Custom API Integration

This guide provides technical details for integrating Eaternity Forecast with your custom POS, ERP, or kitchen management system via our REST API.

API Overview​

Base URL​

Production: https://api.eaternity.org/v1/forecast
Sandbox: https://sandbox-api.eaternity.org/v1/forecast

Recommendation: Develop and test in sandbox environment before production deployment.

Authentication​

Supported Methods:

  1. OAuth 2.0 (recommended for user-facing applications)
  2. API Keys (recommended for server-to-server integration)

Security Requirements:

  • All requests must use HTTPS
  • TLS 1.2 or higher required
  • API keys must be stored securely (environment variables, secret managers)

See Eaternity API Documentation →

Rate Limits​

Standard Limits:

  • 100 requests per minute
  • 10,000 requests per day
  • Burst allowance: 150 requests in 60 seconds

Custom Limits:

  • Available for high-volume integrations
  • Contact support for rate limit increase requests

Rate Limit Headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1705752000

Quick Start​

1. Obtain API Credentials​

Step 1: Contact Eaternity for API access

Step 2: Receive API credentials

  • API key for authentication
  • Kitchen ID for your location
  • Sandbox environment access

Step 3: Test authentication

curl -X GET "https://sandbox-api.eaternity.org/v1/forecast/ping" \
-H "Authorization: Bearer your_api_key_here"

# Success response:
{
"status": "success",
"message": "Authentication successful",
"kitchen_id": "your_kitchen_id"
}

2. Submit Historical Data​

Endpoint: POST /v1/forecast/sales/bulk

Request Example:

curl -X POST "https://api.eaternity.org/v1/forecast/sales/bulk" \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"kitchen_id": "your_kitchen_id",
"start_date": "2023-10-01",
"end_date": "2024-01-15",
"sales": [
{
"date": "2023-10-01",
"service_period": "lunch",
"items": [
{
"item_id": "pasta_carbonara",
"name": "Pasta Carbonara",
"quantity_sold": 52,
"price": 14.50,
"category": "Main Course"
},
{
"item_id": "caesar_salad",
"name": "Caesar Salad",
"quantity_sold": 31,
"price": 9.00,
"category": "Starter"
}
]
}
]
}'

Response:

{
"status": "success",
"message": "Historical data import queued",
"import_id": "imp_1234567890",
"total_records": 92,
"estimated_processing_time": "15 minutes",
"status_url": "/v1/forecast/imports/imp_1234567890/status"
}

3. Check Import Status​

Endpoint: GET /v1/forecast/imports/{import_id}/status

curl -X GET "https://api.eaternity.org/v1/forecast/imports/imp_1234567890/status" \
-H "Authorization: Bearer your_api_key"

Response:

{
"import_id": "imp_1234567890",
"status": "processing",
"progress": 65,
"records_processed": 60,
"records_total": 92,
"records_failed": 0,
"errors": [],
"estimated_completion": "2024-01-20T10:35:00Z"
}

Status Values:

  • queued — Waiting to process
  • processing — Currently importing
  • validating — Checking data quality
  • completed — Import successful
  • failed — Import encountered errors

4. Retrieve Predictions​

Endpoint: GET /v1/forecast/predictions

curl -X GET "https://api.eaternity.org/v1/forecast/predictions?date=2024-01-20" \
-H "Authorization: Bearer your_api_key"

Response:

{
"kitchen_id": "your_kitchen_id",
"generated_at": "2024-01-20T03:15:42Z",
"predictions": [
{
"date": "2024-01-20",
"day_of_week": "Saturday",
"items": [
{
"item_id": "pasta_carbonara",
"name": "Pasta Carbonara",
"predicted_quantity": 52,
"confidence_interval": {
"lower": 48,
"upper": 56
},
"confidence_score": 0.92,
"accuracy_last_30_days": 94.2,
"factors": {
"day_of_week_effect": 1.05,
"weather_effect": 1.02,
"trend": "stable"
}
}
]
}
]
}

API Endpoints​

Sales Data Endpoints​

Submit Daily Sales​

POST /v1/forecast/sales

Submit sales data for a single day.

Request Body:

{
"kitchen_id": "your_kitchen_id",
"date": "2024-01-19",
"service_periods": [
{
"period": "lunch",
"items": [
{
"item_id": "pasta_carbonara",
"name": "Pasta Carbonara",
"quantity_sold": 52,
"price": 14.50,
"category": "Main Course"
}
]
},
{
"period": "dinner",
"items": [
{
"item_id": "grilled_salmon",
"name": "Grilled Salmon",
"quantity_sold": 38,
"price": 18.50,
"category": "Main Course"
}
]
}
]
}

Response:

{
"status": "success",
"date": "2024-01-19",
"items_processed": 12,
"next_prediction_update": "2024-01-20T03:00:00Z"
}

Status Codes:

  • 200 — Success
  • 400 — Invalid request data
  • 401 — Authentication failed
  • 422 — Data validation failed
  • 429 — Rate limit exceeded

Bulk Historical Import​

POST /v1/forecast/sales/bulk

Import large volumes of historical data.

Request Body:

{
"kitchen_id": "your_kitchen_id",
"start_date": "2023-10-01",
"end_date": "2024-01-15",
"sales": [
{
"date": "2023-10-01",
"service_period": "lunch",
"items": [...]
}
]
}

Response:

{
"status": "success",
"import_id": "imp_1234567890",
"total_records": 92,
"status_url": "/v1/forecast/imports/imp_1234567890/status"
}

Best Practices:

  • Maximum 365 days per bulk import
  • Maximum 10,000 records per request
  • For larger datasets, split into multiple requests
  • Monitor import status via status endpoint

Update Sales Data​

PATCH /v1/forecast/sales/{date}

Correct previously submitted sales data.

Request Body:

{
"kitchen_id": "your_kitchen_id",
"items": [
{
"item_id": "pasta_carbonara",
"quantity_sold": 54
}
]
}

Use Cases:

  • Correct data entry errors
  • Update with final end-of-day counts
  • Add missing service periods

Prediction Endpoints​

Get Predictions for Date Range​

GET /v1/forecast/predictions

Query Parameters:

  • date (required) — Date or start date (YYYY-MM-DD)
  • end_date (optional) — End date for range (YYYY-MM-DD)
  • items (optional) — Comma-separated item IDs to filter

Examples:

# Single date
GET /v1/forecast/predictions?date=2024-01-20

# Date range (next 7 days)
GET /v1/forecast/predictions?date=2024-01-20&end_date=2024-01-27

# Specific items only
GET /v1/forecast/predictions?date=2024-01-20&items=pasta_carbonara,grilled_salmon

Response:

{
"kitchen_id": "your_kitchen_id",
"generated_at": "2024-01-20T03:15:42Z",
"predictions": [
{
"date": "2024-01-20",
"day_of_week": "Saturday",
"items": [...]
},
{
"date": "2024-01-21",
"day_of_week": "Sunday",
"items": [...]
}
]
}

Get Prediction for Single Item​

GET /v1/forecast/predictions/{item_id}

Query Parameters:

  • date (required) — Prediction date
  • days_ahead (optional) — Number of days to forecast (default: 7, max: 14)

Example:

GET /v1/forecast/predictions/pasta_carbonara?date=2024-01-20&days_ahead=7

Response:

{
"item_id": "pasta_carbonara",
"name": "Pasta Carbonara",
"predictions": [
{
"date": "2024-01-20",
"predicted_quantity": 52,
"confidence_interval": {
"lower": 48,
"upper": 56
},
"confidence_score": 0.92
}
],
"historical_accuracy": {
"last_7_days": 95.2,
"last_30_days": 94.2,
"all_time": 93.8
}
}

Override Prediction​

POST /v1/forecast/predictions/override

Manually override a prediction for specific circumstances.

Request Body:

{
"kitchen_id": "your_kitchen_id",
"date": "2024-01-25",
"item_id": "pasta_carbonara",
"override_quantity": 75,
"reason": "Conference group booking (50 pax confirmed)",
"preserve_ratios": true
}

Parameters:

  • override_quantity (required) — New predicted quantity
  • reason (required) — Explanation for override (used for learning)
  • preserve_ratios (optional) — Adjust related items proportionally

Response:

{
"status": "success",
"original_prediction": 52,
"override_quantity": 75,
"affected_items": [
{
"item_id": "caesar_salad",
"original": 31,
"adjusted": 45
}
]
}

Analytics Endpoints​

Get Accuracy Report​

GET /v1/forecast/analytics/accuracy

Query Parameters:

  • start_date (required) — Report start date
  • end_date (required) — Report end date
  • group_by (optional) — day, week, month, or item

Example:

GET /v1/forecast/analytics/accuracy?start_date=2024-01-01&end_date=2024-01-31&group_by=week

Response:

{
"period": {
"start": "2024-01-01",
"end": "2024-01-31"
},
"overall_mape": 12.3,
"by_week": [
{
"week": 1,
"start_date": "2024-01-01",
"mape": 13.5,
"items_within_10_percent": 52,
"total_items": 65
}
],
"top_performers": [
{
"item_id": "pasta_carbonara",
"mape": 8.2
}
],
"needs_attention": [
{
"item_id": "daily_special",
"mape": 22.1,
"reason": "High variance, new items frequently"
}
]
}

Get Waste Reduction Report​

GET /v1/forecast/analytics/waste-reduction

Track food waste savings since using Forecast.

Query Parameters:

  • baseline_start (required) — Baseline period start (pre-Forecast)
  • baseline_end (required) — Baseline period end
  • comparison_start (required) — Forecast usage period start
  • comparison_end (required) — Forecast usage period end

Response:

{
"baseline": {
"period": "2023-10-01 to 2023-12-31",
"waste_rate": 12.8,
"total_waste_portions": 3845,
"estimated_cost": 21148.50
},
"comparison": {
"period": "2024-01-01 to 2024-03-31",
"waste_rate": 7.2,
"total_waste_portions": 2156,
"estimated_cost": 11858.00
},
"improvement": {
"waste_rate_reduction": 43.8,
"portions_saved": 1689,
"cost_savings": 9290.50,
"annualized_savings": 11748.60
}
}

Webhooks​

Overview​

Webhooks allow Forecast to push notifications to your system instead of requiring polling.

Use Cases:

  • Receive notifications when new predictions are ready
  • Get alerts for large variance between prediction and actual
  • Monitor data quality issues
  • Track model retraining events

Setup​

Configure Webhook Endpoint:

POST /v1/forecast/webhooks

{
"kitchen_id": "your_kitchen_id",
"url": "https://your-system.com/webhooks/forecast",
"events": [
"predictions.generated",
"variance.large",
"data.quality_issue",
"model.retrained"
],
"secret": "your_webhook_secret_for_signature_validation"
}

Response:

{
"webhook_id": "wh_1234567890",
"status": "active",
"events": ["predictions.generated", "variance.large"],
"created_at": "2024-01-20T10:00:00Z"
}

Webhook Events​

predictions.generated​

Triggered when new predictions are generated (daily at 3:00 AM).

Payload:

{
"event": "predictions.generated",
"timestamp": "2024-01-20T03:15:42Z",
"kitchen_id": "your_kitchen_id",
"data": {
"date_range": {
"start": "2024-01-20",
"end": "2024-01-27"
},
"total_items": 65,
"prediction_url": "/v1/forecast/predictions?date=2024-01-20"
}
}

variance.large​

Triggered when actual sales differ significantly from prediction (>20% by default).

Payload:

{
"event": "variance.large",
"timestamp": "2024-01-19T21:30:00Z",
"kitchen_id": "your_kitchen_id",
"data": {
"date": "2024-01-19",
"item_id": "grilled_salmon",
"predicted": 28,
"actual": 42,
"variance_percent": 50.0,
"possible_causes": ["weather_warmer_than_forecast", "event_nearby"]
}
}

data.quality_issue​

Triggered when data quality problems detected.

Payload:

{
"event": "data.quality_issue",
"timestamp": "2024-01-20T04:00:00Z",
"kitchen_id": "your_kitchen_id",
"data": {
"issue_type": "missing_data",
"severity": "warning",
"description": "No sales data received for 2024-01-19",
"recommendation": "Submit sales data for 2024-01-19 to maintain prediction accuracy"
}
}

model.retrained​

Triggered when model is retrained with new data (weekly).

Payload:

{
"event": "model.retrained",
"timestamp": "2024-01-20T04:30:00Z",
"kitchen_id": "your_kitchen_id",
"data": {
"training_data_range": {
"start": "2023-10-01",
"end": "2024-01-19"
},
"accuracy_improvement": 1.2,
"new_mape": 12.1,
"previous_mape": 13.3
}
}

Webhook Security​

Signature Validation:

All webhook requests include X-Forecast-Signature header for verification.

Verification Example (Python):

import hmac
import hashlib

def verify_webhook_signature(payload, signature, secret):
expected_signature = hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()

return hmac.compare_digest(signature, expected_signature)

# In your webhook handler:
payload = request.body
signature = request.headers['X-Forecast-Signature']
secret = 'your_webhook_secret'

if verify_webhook_signature(payload, signature, secret):
# Process webhook
pass
else:
# Reject request (potential security issue)
return 401

Data Formats and Validation​

Item ID Requirements​

Format:

  • Alphanumeric characters, underscores, hyphens only
  • Maximum 100 characters
  • Case-sensitive
  • Must be consistent across all requests

Good Examples:

  • pasta_carbonara
  • grilled-salmon-lemon
  • ITEM_12345

Bad Examples:

  • Pasta Carbonara (contains spaces)
  • item#12345 (contains special character)
  • Different casing: Pasta_Carbonara vs pasta_carbonara (inconsistent)

Date Format​

Required Format: YYYY-MM-DD (ISO 8601)

Valid:

  • 2024-01-20
  • 2024-12-31

Invalid:

  • 20-01-2024 (wrong order)
  • 2024/01/20 (slashes instead of hyphens)
  • 2024-1-20 (not zero-padded)

Quantity Validation​

Requirements:

  • Integer values only
  • Minimum: 0
  • Maximum: 10,000 (contact support for higher limits)
  • Negative values rejected

Price Validation​

Requirements:

  • Decimal values (2 decimal places)
  • Minimum: 0.00
  • Maximum: 999.99
  • Currency not specified (use consistent currency across all data)

Error Handling​

Error Response Format​

{
"status": "error",
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid date format",
"details": {
"field": "date",
"value": "20-01-2024",
"expected": "YYYY-MM-DD"
}
}
}

Common Error Codes​

CodeHTTP StatusDescriptionSolution
AUTHENTICATION_FAILED401Invalid API keyVerify API key is correct
RATE_LIMIT_EXCEEDED429Too many requestsWait and retry, or request limit increase
VALIDATION_ERROR422Invalid data formatCheck request format against docs
NOT_FOUND404Resource not foundVerify kitchen_id or item_id
INTERNAL_ERROR500Server errorRetry with exponential backoff

Retry Logic​

Recommended Retry Strategy:

import time
import requests

def make_request_with_retry(url, max_retries=3):
for attempt in range(max_retries):
try:
response = requests.get(url)

if response.status_code == 200:
return response.json()
elif response.status_code == 429:
# Rate limited - wait and retry
retry_after = int(response.headers.get('Retry-After', 60))
time.sleep(retry_after)
elif response.status_code >= 500:
# Server error - exponential backoff
wait_time = 2 ** attempt
time.sleep(wait_time)
else:
# Client error - don't retry
raise Exception(f"Error {response.status_code}: {response.text}")

except requests.exceptions.RequestException as e:
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt)

raise Exception("Max retries exceeded")

SDK and Client Libraries​

Official SDKs​

Python SDK (Recommended):

pip install eaternity-forecast

Usage:

from eaternity_forecast import ForecastClient

client = ForecastClient(api_key='your_api_key')

# Submit daily sales
client.sales.submit(
date='2024-01-19',
items=[
{'item_id': 'pasta_carbonara', 'quantity_sold': 52}
]
)

# Get predictions
predictions = client.predictions.get(date='2024-01-20')

JavaScript/TypeScript SDK:

npm install @eaternity/forecast-sdk

Usage:

import { ForecastClient } from '@eaternity/forecast-sdk';

const client = new ForecastClient({ apiKey: 'your_api_key' });

// Get predictions
const predictions = await client.predictions.get({ date: '2024-01-20' });

Community SDKs:

  • Ruby: gem install eaternity-forecast
  • PHP: composer require eaternity/forecast-sdk
  • Go: go get github.com/eaternity/forecast-go

View SDK Documentation →

Testing​

Sandbox Environment​

Purpose: Test integration without affecting production data

Base URL: https://sandbox-api.eaternity.org/v1/forecast

Features:

  • Separate authentication credentials
  • Test data can be reset
  • Same API as production
  • Faster prediction generation (for testing)

Limitations:

  • Predictions may be less accurate (using synthetic data)
  • No SLA guarantees
  • Data reset weekly

Example Integration Test​

import unittest
from eaternity_forecast import ForecastClient

class TestForecastIntegration(unittest.TestCase):
def setUp(self):
self.client = ForecastClient(
api_key='sandbox_api_key',
base_url='https://sandbox-api.eaternity.org/v1/forecast'
)

def test_submit_sales_and_get_predictions(self):
# Submit historical data
response = self.client.sales.submit(
date='2024-01-19',
items=[
{'item_id': 'test_item_1', 'quantity_sold': 50},
{'item_id': 'test_item_2', 'quantity_sold': 30}
]
)
self.assertEqual(response['status'], 'success')

# Wait for processing (sandbox is faster)
time.sleep(10)

# Get predictions
predictions = self.client.predictions.get(date='2024-01-20')
self.assertIsNotNone(predictions)
self.assertGreater(len(predictions['predictions']), 0)

Production Deployment Checklist​

Pre-Launch​

  • Tested all endpoints in sandbox environment
  • Implemented error handling and retry logic
  • Configured webhook endpoints (if using)
  • Verified webhook signature validation
  • Set up monitoring and logging
  • Documented integration for team
  • Obtained production API credentials
  • Imported historical data successfully
  • Verified data quality score >80%
  • Completed model training

Go-Live​

  • Switch from sandbox to production URLs
  • Update API credentials to production
  • Monitor first 24 hours of predictions
  • Verify daily sales submission working
  • Confirm prediction retrieval working
  • Check webhook delivery (if configured)

Post-Launch​

  • Track accuracy metrics weekly
  • Review and respond to large variance alerts
  • Provide feedback on prediction quality
  • Optimize preparation workflows based on confidence
  • Schedule monthly performance reviews

Support​

Technical Support​

Email: forecast-api@eaternity.org

Response Times:

  • Critical (production down): 4 hours
  • High (degraded performance): 24 hours
  • Medium (questions, bugs): 48 hours
  • Low (enhancements): 1 week

Include in Support Requests:

  • Kitchen ID
  • API endpoint and method
  • Request/response examples
  • Error messages
  • Timestamp of issue

Documentation​

See Also​