ForestTrace EUDR Compliance Checker
Pricing
$0.05 / actor start
ForestTrace EUDR Compliance Checker
Batch-check suppliers, products, and locations for EUDR compliance. This Actor runs automated EUDR checks via ForestTrace APIs and generates structured results and compliance summaries for audits, reporting, and automation workflows.
Pricing
$0.05 / actor start
Rating
0.0
(0)
Developer

Hayder Al-Khalissi
Actor stats
0
Bookmarked
2
Total users
1
Monthly active users
8 days ago
Last modified
Categories
Share
ForestTrace EUDR API & Apify Actor
A production-ready API for EUDR (EU Deforestation Regulation) compliance validation, due diligence preparation, risk scoring, and reporting. This repo also provides an Apify Actor that runs many API jobs with concurrency, retries, and structured output.
Apify Actor
The Actor runs on Apify with no external API required. You can either:
- In-Actor mode (default): Omit
backendin the input. Jobs run entirely inside the Actor (geo validate, due-diligence prepare, risk score, report generate). No external API calls. - External API mode: Set
backend.baseUrlto your own HTTPS API to call it for each job instead.
Input: jobs (required), and optionally backend, execution, output, webhook. Results go to the Dataset and a summary + report to the default Key-Value store.
Input
| Field | Description |
|---|---|
| backend | Optional. Leave empty to run in-Actor (no external API). Or baseUrl (https), optional auth. |
| jobs | Array of { id?, endpoint, method, body?, query? } (max 10,000). Endpoints: /v1/geo/validate, /v1/due-diligence/prepare, /v1/risk/score, /v1/reports/generate |
| execution | Optional concurrency (default 5), maxRetries (default 3) |
| output | Optional writeDataset (default true), writeSummary (default true) |
| webhook | Optional url (https), method (POST/GET) to notify at end |
Output
- Dataset: One item per job with
{ index, jobId, endpoint, ok, status, data, durationMs, error? }. - Key-Value store:
summary.json(counts, duration, by-endpoint stats),report.md(markdown report),OUTPUT(same as summary).
Security
- Secrets: Never logged. Prefer Actor environment variables for auth (e.g.
WORKER_TOKEN). Do not put secrets in input when possible. - Headers: Only the single allowed auth header from config is sent; no arbitrary headers from user input.
- baseUrl: Must be https.
- Job body: Max 1MB per job.
Run locally
npm installnpm start# or: apify run
No API keys needed for in-Actor mode: omit backend in input and jobs run inside the Actor. Input is read from Apify (e.g. default key-value store or apify run with stored input).
Dependencies
apify– Actor SDKp-limit– Concurrencytsx– Run TypeScript
Publishing to the Apify Store
-
Push the latest build
apify push(or use a connected Git repo so builds stay up to date). -
Open Publication
In Apify Console go to your Actor → Publication tab. -
Display information
Under Publication > Display information fill in:- Icon/logo – Upload a square image (e.g. 120×120 px). Avoid copyrighted or branded art.
- Actor name – e.g. "ForestTrace EUDR Actor" (short, ~40–50 chars for SEO).
- Description – One clear paragraph; 140–156 characters is ideal for search.
- Categories – Pick relevant ones (e.g. "Data extraction", "Compliance", "Automation").
- SEO title & description – Optional but recommended for Store search.
-
README
The Store uses the Actor’s README. Yours is in the repo; it’s synced when you push. You can also edit the README in the Console under the Actor’s Source tab if needed. -
Publish
When all required fields are done, Publish to Store becomes available. Click it to make the Actor public. -
Verify
Search for your Actor on apify.com/store and check its page.
API (standalone server)
To run the API server locally (not the Actor):
$npm run start:server
📚 Documentation
- API_DOCUMENTATION.md - Complete API guide with examples
Features
- ✅ Geo-validation with polygon validation and bbox calculation
- ✅ Due diligence preparation with data normalization
- ✅ Risk scoring with configurable rules
- ✅ Report generation in JSON and CSV formats
- ✅ Async job processing with in-memory storage
- ✅ In-memory caching with SHA-256 content hashing (24-hour TTL)
- ✅ Full CORS support
- ✅ Request tracking with unique request IDs
- ✅ Comprehensive error handling with validation details
Quick Start
Prerequisites
- Node.js 18+ and npm
Installation
# Install dependenciesnpm install
Configure environment (optional)
For optional proxy secret validation, set:
PROXY_SECRET_REQUIRED– set totrueto require the proxy secret headerPROXY_SECRET– secret value for server-to-server auth
Copy .env.example to .env and edit, or set in your environment.
Run the API
# Start the standalone API server (default port 8787, or set PORT)npm run start:server
The API will be available at http://localhost:8787. To run the Apify Actor instead (in-Actor or external API mode), use npm start or apify run.
API Endpoints
Base URL (local): http://localhost:8787
1. Health Check
GET /health
Check API health and version.
$curl http://localhost:8787/health
Response:
{"ok": true,"version": "1.0.0","request_id": "req_1234567890_abc123xyz"}
2. Geo Validation
POST /v1/geo/validate
Validate GeoJSON geometry (Polygon or MultiPolygon). Returns bounding box and warnings for high vertex counts.
curl -X POST http://localhost:8787/v1/geo/validate \-H "Content-Type: application/json" \-d '{"geometry": {"type": "Polygon","coordinates": [[[102.0, 0.0],[103.0, 0.0],[103.0, 1.0],[102.0, 1.0],[102.0, 0.0]]]}}'
Response:
{"request_id": "req_1234567890_abc123xyz","valid": true,"bbox": [102.0, 0.0, 103.0, 1.0]}
Error Example (invalid polygon):
{"error": "Polygon ring must be closed (first coordinate must equal last)","request_id": "req_1234567890_abc123xyz"}
3. Due Diligence Preparation
POST /v1/due-diligence/prepare
Normalize and prepare due diligence data. Returns normalized payload with input hash.
curl -X POST http://localhost:8787/v1/due-diligence/prepare \-H "Content-Type: application/json" \-d '{"operator": {"name": "Acme Forest Products","country": "us","contact": "contact@acme.com"},"product": {"commodity": "TIMBER","quantity": 50000,"unit": "KG","hs_code": "440710"},"supply_chain": {"supplier_name": "Forest Supplier Ltd","supplier_country": "ca","origin_country": "us"},"geo": {"geometry": {"type": "Polygon","coordinates": [[[102.0, 0.0],[103.0, 0.0],[103.0, 1.0],[102.0, 1.0],[102.0, 0.0]]]}}}'
Response:
{"request_id": "req_1234567890_abc123xyz","prepared_at": "2024-01-15T10:30:00.000Z","input_hash": "sha256:a1b2c3d4...","normalized": {"operator": {"name": "Acme Forest Products","country": "US","contact": "contact@acme.com"},"product": {"commodity": "timber","quantity": 50000,"unit": "kg","hs_code": "440710"},"supply_chain": {"supplier_name": "Forest Supplier Ltd","supplier_country": "CA","origin_country": "US"},"geo": {"geometry": {"type": "Polygon","coordinates": [[[102.0, 0.0],[103.0, 0.0],[103.0, 1.0],[102.0, 1.0],[102.0, 0.0]]]}}}}
Caching: Subsequent identical requests return cached data with X-Cache: HIT header.
4. Risk Scoring
POST /v1/risk/score
Calculate risk score based on normalized data. Returns score (0-1), level (low/medium/high), and flags.
curl -X POST http://localhost:8787/v1/risk/score \-H "Content-Type: application/json" \-d '{"normalized": {"operator": {"name": "Acme Forest Products","country": "US"},"product": {"commodity": "timber","quantity": 150000,"unit": "kg"},"supply_chain": {"supplier_name": "Forest Supplier Ltd"},"geo": {"geometry": {"type": "Polygon","coordinates": [[[102.0, 0.0], [103.0, 0.0], [103.0, 1.0], [102.0, 1.0], [102.0, 0.0]]]}}}}'
Response:
{"request_id": "req_1234567890_abc123xyz","score": 0.5,"level": "medium","flags": ["missing_hs_code","high_quantity","missing_supplier_country"]}
Risk Calculation:
- Base score: 0.1 per flag
- +0.2 if quantity > 100,000
- Level: low (<0.3), medium (<0.7), high (≥0.7)
5. Report Generation
POST /v1/reports/generate?format=json|csv
Generate compliance report in JSON or CSV format.
JSON Format
curl -X POST "http://localhost:8787/v1/reports/generate?format=json" \-H "Content-Type: application/json" \-d '{"normalized": {"operator": {"name": "Acme Forest Products","country": "US","contact": "contact@acme.com"},"product": {"commodity": "timber","quantity": 50000,"unit": "kg","hs_code": "440710"},"supply_chain": {"supplier_name": "Forest Supplier Ltd","supplier_country": "CA"},"geo": {"geometry": {"type": "Polygon","coordinates": [[[102.0, 0.0], [103.0, 0.0], [103.0, 1.0], [102.0, 1.0], [102.0, 0.0]]]}}}}'
Response:
{"request_id": "req_1234567890_abc123xyz","format": "json","operator": {"name": "Acme Forest Products","country": "US","contact": "contact@acme.com"},"product": {"commodity": "timber","quantity": 50000,"unit": "kg","hs_code": "440710"},"supply_chain": {"supplier_name": "Forest Supplier Ltd","supplier_country": "CA","origin_country": "N/A"},"geo_summary": {"geometry_type": "Polygon","has_coordinates": true},"risk_placeholder": {"note": "Risk assessment can be obtained via /v1/risk/score endpoint"}}
CSV Format
curl -X POST "http://localhost:8787/v1/reports/generate?format=csv" \-H "Content-Type: application/json" \-d '{"normalized": {"operator": {"name": "Acme Forest Products","country": "US"},"product": {"commodity": "timber","quantity": 50000,"unit": "kg"},"supply_chain": {"supplier_name": "Forest Supplier Ltd","supplier_country": "CA"},"geo": {"geometry": {"type": "Polygon","coordinates": [[[102.0, 0.0], [103.0, 0.0], [103.0, 1.0], [102.0, 1.0], [102.0, 0.0]]]}}}}'
Response:
{"request_id": "req_1234567890_abc123xyz","format": "csv","content": "field,value\noperator_name,Acme Forest Products\noperator_country,US\n..."}
6. Async Job Submission
POST /v1/jobs/submit
Submit batch processing job. Returns immediately with job ID for status polling.
curl -X POST http://localhost:8787/v1/jobs/submit \-H "Content-Type: application/json" \-d '{"items": [{"operator": {"name": "Company A", "country": "US"},"product": {"commodity": "timber", "quantity": 1000, "unit": "kg"},"supply_chain": {"supplier_name": "Supplier A"},"geo": {"geometry": {"type": "Polygon", "coordinates": [[[0,0],[1,0],[1,1],[0,1],[0,0]]]}}},{"operator": {"name": "Company B", "country": "CA"},"product": {"commodity": "wood", "quantity": 2000, "unit": "kg"},"supply_chain": {"supplier_name": "Supplier B"},"geo": {"geometry": {"type": "Polygon", "coordinates": [[[0,0],[1,0],[1,1],[0,1],[0,0]]]}}}]}'
Response (202 Accepted):
{"request_id": "req_1234567890_abc123xyz","job_id": "job_1234567890_xyz789abc","status": "queued"}
7. Job Status Check
GET /v1/jobs/{job_id}
Check status of submitted job.
$curl http://localhost:8787/v1/jobs/job_1234567890_xyz789abc
Response (completed):
{"job_id": "job_1234567890_xyz789abc","status": "done","created_at": "2024-01-15T10:30:00.000Z","completed_at": "2024-01-15T10:30:05.000Z","items_count": 2,"result": {"processed": 2,"results": [{"item_index": 0,"operator": "Company A","commodity": "timber","status": "processed"},{"item_index": 1,"operator": "Company B","commodity": "wood","status": "processed"}]},"request_id": "req_1234567890_def456ghi"}
Response (not found):
{"error": "Job not found: job_invalid_id","request_id": "req_1234567890_abc123xyz"}
Optional proxy authentication
When PROXY_SECRET_REQUIRED is set to true, requests must include the X-Proxy-Secret header with the value from PROXY_SECRET.
Error Handling
Error Response Format
All errors follow a consistent format:
{"error": "Error message","request_id": "req_1234567890_abc123xyz","details": [{"path": "field.name","message": "Field 'field.name' is required"}]}
HTTP Status Codes
200- Success202- Accepted (async job)400- Bad Request (invalid JSON, invalid Content-Type)403- Forbidden (invalid proxy secret)404- Not Found (route or resource not found)422- Unprocessable Entity (validation errors)500- Internal Server Error
Validation Errors
Field validation errors include path and message:
curl -X POST http://localhost:8787/v1/due-diligence/prepare \-H "Content-Type: application/json" \-d '{}'
Response (422):
{"error": "Validation failed","request_id": "req_1234567890_abc123xyz","details": [{"path": "operator.name", "message": "Field 'operator.name' is required"},{"path": "operator.country", "message": "Field 'operator.country' is required"},{"path": "product.commodity", "message": "Field 'product.commodity' is required"}]}
Caching
Cache Strategy
- Cache key format:
v1:<route>:<input_hash> - Input hash: SHA-256 of canonical JSON (sorted keys)
- TTL: 24 hours (86,400 seconds)
- Storage: in-memory cache
Cache Headers
Responses include cache status:
X-Cache: HIT- Response served from cacheX-Cache: MISS- Response computed and cached
Cached Endpoints
The following endpoints use caching:
/v1/due-diligence/prepare/v1/risk/score/v1/reports/generate
Cache entries expire automatically after 24 hours. Restart the server to clear the in-memory cache.
Project Structure
foresttrace-eudr-api/├── src/│ └── index.ts # API server and handlers├── package.json # Dependencies and scripts├── tsconfig.json # TypeScript configuration└── README.md # This file
Development
Type Checking
$npm run typecheck
Testing Locally
Start the server and test endpoints:
npm start# In another terminalcurl http://localhost:8787/health
Environment Variables
Optional (for proxy validation):
PROXY_SECRET_REQUIRED– set totrueto require proxy secretPROXY_SECRET– secret for theX-Proxy-Secretheader
Use a .env file (gitignored) or set in your environment.
Security Best Practices
- Never commit secrets – Keep
.envout of version control - Validate all inputs – Strict JSON schema validation
- Rate limiting – Implement per-IP rate limits if needed
- CORS configuration – Restrict origins in production if needed
- Audit logging – Log all requests with request IDs
- Rotate secrets – Regularly update proxy secret if used
Troubleshooting
Common Issues
Issue: 403 Forbidden: invalid proxy secret
- Solution: Check
PROXY_SECRET_REQUIREDandX-Proxy-Secretheader
Issue: 400 Bad Request: Content-Type must be application/json
- Solution: Add
-H "Content-Type: application/json"to curl commands
Issue: Cache not working
- Solution: Cache is in-memory; restart the server to clear it
License
MIT
Version History
- 1.0.0 - Initial release
- Geo validation
- Due diligence preparation
- Risk scoring
- Report generation (JSON/CSV)
- Async job processing
- In-memory caching
- Optional proxy secret auth
