Products, categories and carts for building shop screens before the real backend exists.
The Fake Store API gives you realistic shop data to build product lists, detail pages, search, filters and a cart. It has 69 products in 10 categories, from smartphones and laptops to Kampot pepper, krama scarves and Khmer crafts, each with an English and a Khmer name (titleKm), a USD price, a discount, a rating, stock and image URLs.
Images are PNG placeholders served by the API itself, so they load in Flutter Image.network, Android Glide/Coil and iOS without extra libraries. The data never changes between requests, so your screenshots and tests stay stable. Nothing is saved: the response shows what a real API would return, and the data stays the same for everyone.
const res = await fetch('https://toolorca.com/api/v1/products?limit=2');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
console.log(data);
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> main() async {
final res = await http.get(
Uri.parse('https://toolorca.com/api/v1/products?limit=2'),
);
if (res.statusCode >= 400) throw Exception('HTTP ${res.statusCode}');
final data = jsonDecode(utf8.decode(res.bodyBytes));
print(data);
}
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
// Call from a background thread or coroutine (Dispatchers.IO), never the main thread.
val client = OkHttpClient()
val request = Request.Builder()
.url("https://toolorca.com/api/v1/products?limit=2")
.build()
client.newCall(request).execute().use { res ->
if (!res.isSuccessful) error("HTTP ${res.code}")
println(res.body?.string())
}
import Foundation
var request = URLRequest(url: URL(string: "https://toolorca.com/api/v1/products?limit=2")!)
let (data, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse, http.statusCode < 400 else {
throw URLError(.badServerResponse)
}
print(String(decoding: data, as: UTF8.self))
GET /products?limit=2
Example response
{
"data": [
{
"id":1,
"title":"Orca X1 Smartphone 128GB",
"titleKm":"ស្មាតហ្វូន Orca X1 128GB",
"description":"Orca X1 Smartphone 128GB by Orcatech. A bright display and all-day battery for work, study and social media. Takes sharp photos in daylight and decent shots at night.",
"category":"smartphones",
"brand":"Orcatech",
"price":280.99,
"currency":"USD",
"discountPercentage":18.1,
"rating": {
"rate":5,
"count":588
},
"stock":123,
"sku":"TO-SMA-0001",
"tags": [
"smartphones",
"orcatech"
],
"thumbnail":"https://toolorca.com/api/v1/images/placeholder/300x300.png?text=ORCA%20X1&bg=0f6fde&fg=ffffff",
"images": [
"https://toolorca.com/api/v1/images/placeholder/800x800.png?text=ORCA%20X1&bg=0f6fde&fg=ffffff",
"https://toolorca.com/api/v1/images/placeholder/800x800.png?text=ORCATECH&bg=0f6fde&fg=ffffff"
],
"createdAt":"2025-07-26T00:00:00Z"
},
{
"id":2,
"title":"Orca X1 Pro 256GB",
"titleKm":"ស្មាតហ្វូន Orca X1 Pro 256GB",
"description":"Orca X1 Pro 256GB by Orcatech. A bright display and all-day battery for work, study and social media. Dual SIM, so you can keep a work number and a personal number on one phone.",
"category":"smartphones",
"brand":"Orcatech",
"price":515.99,
"currency":"USD",
"discountPercentage":0,
"rating": {
"rate":4.8,
"count":667
},
"stock":83,
"sku":"TO-SMA-0002",
"tags": [
"smartphones",
"orcatech"
],
"thumbnail":"https://toolorca.com/api/v1/images/placeholder/300x300.png?text=X1%20PRO&bg=0f6fde&fg=ffffff",
"images": [
"https://toolorca.com/api/v1/images/placeholder/800x800.png?text=X1%20PRO&bg=0f6fde&fg=ffffff",
"https://toolorca.com/api/v1/images/placeholder/800x800.png?text=ORCATECH&bg=0f6fde&fg=ffffff"
],
"createdAt":"2025-02-15T00:00:00Z"
}
],
"meta": {
"total":69,
"limit":2,
"skip":0,
"page":1,
"pages":35
}
}
const res = await fetch('https://toolorca.com/api/v1/products?q=krama&select=title,titleKm,price');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
console.log(data);
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> main() async {
final res = await http.get(
Uri.parse('https://toolorca.com/api/v1/products?q=krama&select=title,titleKm,price'),
);
if (res.statusCode >= 400) throw Exception('HTTP ${res.statusCode}');
final data = jsonDecode(utf8.decode(res.bodyBytes));
print(data);
}
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
// Call from a background thread or coroutine (Dispatchers.IO), never the main thread.
val client = OkHttpClient()
val request = Request.Builder()
.url("https://toolorca.com/api/v1/products?q=krama&select=title,titleKm,price")
.build()
client.newCall(request).execute().use { res ->
if (!res.isSuccessful) error("HTTP ${res.code}")
println(res.body?.string())
}
import Foundation
var request = URLRequest(url: URL(string: "https://toolorca.com/api/v1/products?q=krama&select=title,titleKm,price")!)
let (data, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse, http.statusCode < 400 else {
throw URLError(.badServerResponse)
}
print(String(decoding: data, as: UTF8.self))
const res = await fetch('https://toolorca.com/api/v1/products/41');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
console.log(data);
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> main() async {
final res = await http.get(
Uri.parse('https://toolorca.com/api/v1/products/41'),
);
if (res.statusCode >= 400) throw Exception('HTTP ${res.statusCode}');
final data = jsonDecode(utf8.decode(res.bodyBytes));
print(data);
}
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
// Call from a background thread or coroutine (Dispatchers.IO), never the main thread.
val client = OkHttpClient()
val request = Request.Builder()
.url("https://toolorca.com/api/v1/products/41")
.build()
client.newCall(request).execute().use { res ->
if (!res.isSuccessful) error("HTTP ${res.code}")
println(res.body?.string())
}
import Foundation
var request = URLRequest(url: URL(string: "https://toolorca.com/api/v1/products/41")!)
let (data, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse, http.statusCode < 400 else {
throw URLError(.badServerResponse)
}
print(String(decoding: data, as: UTF8.self))
GET /products/41
Example response
{
"id":41,
"title":"Kampot Black Pepper 100g",
"titleKm":"ម្រេចខ្មៅកំពត 100g",
"description":"Kampot Black Pepper 100g by Srey Farm. Packed fresh and sealed to keep its flavour. Sourced from small farms in Cambodia.",
"category":"groceries",
"brand":"Srey Farm",
"price":8.99,
"currency":"USD",
"discountPercentage":19.8,
"rating": {
"rate":4.7,
"count":502
},
"stock":3,
"sku":"TO-GRO-0041",
"tags": [
"groceries",
"sreyfarm",
"made-in-cambodia"
],
"thumbnail":"https://toolorca.com/api/v1/images/placeholder/300x300.png?text=PEPPER&bg=16a34a&fg=ffffff",
"images": [
"https://toolorca.com/api/v1/images/placeholder/800x800.png?text=PEPPER&bg=16a34a&fg=ffffff",
"https://toolorca.com/api/v1/images/placeholder/800x800.png?text=SREY%20FARM&bg=16a34a&fg=ffffff"
],
"createdAt":"2025-11-16T00:00:00Z"
}
const res = await fetch('https://toolorca.com/api/v1/products/categories');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
console.log(data);
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> main() async {
final res = await http.get(
Uri.parse('https://toolorca.com/api/v1/products/categories'),
);
if (res.statusCode >= 400) throw Exception('HTTP ${res.statusCode}');
final data = jsonDecode(utf8.decode(res.bodyBytes));
print(data);
}
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
// Call from a background thread or coroutine (Dispatchers.IO), never the main thread.
val client = OkHttpClient()
val request = Request.Builder()
.url("https://toolorca.com/api/v1/products/categories")
.build()
client.newCall(request).execute().use { res ->
if (!res.isSuccessful) error("HTTP ${res.code}")
println(res.body?.string())
}
import Foundation
var request = URLRequest(url: URL(string: "https://toolorca.com/api/v1/products/categories")!)
let (data, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse, http.statusCode < 400 else {
throw URLError(.badServerResponse)
}
print(String(decoding: data, as: UTF8.self))
const res = await fetch('https://toolorca.com/api/v1/products/category/khmer-crafts?limit=2&select=title,titleKm,price,thumbnail');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
console.log(data);
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> main() async {
final res = await http.get(
Uri.parse('https://toolorca.com/api/v1/products/category/khmer-crafts?limit=2&select=title,titleKm,price,thumbnail'),
);
if (res.statusCode >= 400) throw Exception('HTTP ${res.statusCode}');
final data = jsonDecode(utf8.decode(res.bodyBytes));
print(data);
}
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
// Call from a background thread or coroutine (Dispatchers.IO), never the main thread.
val client = OkHttpClient()
val request = Request.Builder()
.url("https://toolorca.com/api/v1/products/category/khmer-crafts?limit=2&select=title,titleKm,price,thumbnail")
.build()
client.newCall(request).execute().use { res ->
if (!res.isSuccessful) error("HTTP ${res.code}")
println(res.body?.string())
}
import Foundation
var request = URLRequest(url: URL(string: "https://toolorca.com/api/v1/products/category/khmer-crafts?limit=2&select=title,titleKm,price,thumbnail")!)
let (data, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse, http.statusCode < 400 else {
throw URLError(.badServerResponse)
}
print(String(decoding: data, as: UTF8.self))
GET /products/category/khmer-crafts?limit=2&select=title,titl…
Create a product. Returns 201 with the new id. Nothing is saved: the response shows what a real API would return, and the data stays the same for everyone.
Send title (string) and price (number); anything else is echoed back. Missing fields return 422, so you can test your form validation too.
Parameter
In
Type
Description
title required
body
string
Product name.
price required
body
number
Price in USD.
…
body
any
Any other fields (category, description, …).
curl -X POST 'https://toolorca.com/api/v1/products' \
-H 'Content-Type: application/json' \
-d '{"title":"Kampot Pepper Gift Box","titleKm":"ប្រអប់កាដូម្រេចកំពត","price":15.5,"category":"groceries"}'
const res = await fetch('https://toolorca.com/api/v1/products', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
"title": "Kampot Pepper Gift Box",
"titleKm": "ប្រអប់កាដូម្រេចកំពត",
"price": 15.5,
"category": "groceries"
}),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
console.log(data);
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> main() async {
final res = await http.post(
Uri.parse('https://toolorca.com/api/v1/products'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'title': 'Kampot Pepper Gift Box',
'titleKm': 'ប្រអប់កាដូម្រេចកំពត',
'price': 15.5,
'category': 'groceries',
}),
);
if (res.statusCode >= 400) throw Exception('HTTP ${res.statusCode}');
final data = jsonDecode(utf8.decode(res.bodyBytes));
print(data);
}
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
// Call from a background thread or coroutine (Dispatchers.IO), never the main thread.
val client = OkHttpClient()
val json = """
{
"title": "Kampot Pepper Gift Box",
"titleKm": "ប្រអប់កាដូម្រេចកំពត",
"price": 15.5,
"category": "groceries"
}
""".trimIndent()
val request = Request.Builder()
.url("https://toolorca.com/api/v1/products")
.post(json.toRequestBody("application/json".toMediaType()))
.build()
client.newCall(request).execute().use { res ->
if (!res.isSuccessful) error("HTTP ${res.code}")
println(res.body?.string())
}
Change some fields of a product (PUT replaces the whole product). Nothing is saved: the response shows what a real API would return, and the data stays the same for everyone.
const res = await fetch('https://toolorca.com/api/v1/products/1', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
"price": 259.99,
"stock": 10
}),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
console.log(data);
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> main() async {
final res = await http.patch(
Uri.parse('https://toolorca.com/api/v1/products/1'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'price': 259.99,
'stock': 10,
}),
);
if (res.statusCode >= 400) throw Exception('HTTP ${res.statusCode}');
final data = jsonDecode(utf8.decode(res.bodyBytes));
print(data);
}
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
// Call from a background thread or coroutine (Dispatchers.IO), never the main thread.
val client = OkHttpClient()
val json = """
{
"price": 259.99,
"stock": 10
}
""".trimIndent()
val request = Request.Builder()
.url("https://toolorca.com/api/v1/products/1")
.patch(json.toRequestBody("application/json".toMediaType()))
.build()
client.newCall(request).execute().use { res ->
if (!res.isSuccessful) error("HTTP ${res.code}")
println(res.body?.string())
}
Delete a product. Returns the product with isDeleted: true. Nothing is saved: the response shows what a real API would return, and the data stays the same for everyone.
const res = await fetch('https://toolorca.com/api/v1/products/2', {
method: 'DELETE',
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
console.log(data);
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> main() async {
final res = await http.delete(
Uri.parse('https://toolorca.com/api/v1/products/2'),
);
if (res.statusCode >= 400) throw Exception('HTTP ${res.statusCode}');
final data = jsonDecode(utf8.decode(res.bodyBytes));
print(data);
}
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
// Call from a background thread or coroutine (Dispatchers.IO), never the main thread.
val client = OkHttpClient()
val request = Request.Builder()
.url("https://toolorca.com/api/v1/products/2")
.delete()
.build()
client.newCall(request).execute().use { res ->
if (!res.isSuccessful) error("HTTP ${res.code}")
println(res.body?.string())
}
import Foundation
var request = URLRequest(url: URL(string: "https://toolorca.com/api/v1/products/2")!)
request.httpMethod = "DELETE"
let (data, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse, http.statusCode < 400 else {
throw URLError(.badServerResponse)
}
print(String(decoding: data, as: UTF8.self))
DELETE /products/2
Example response
{
"id":2,
"title":"Orca X1 Pro 256GB",
"titleKm":"ស្មាតហ្វូន Orca X1 Pro 256GB",
"description":"Orca X1 Pro 256GB by Orcatech. A bright display and all-day battery for work, study and social media. Dual SIM, so you can keep a work number and a personal number on one phone.",
"category":"smartphones",
"brand":"Orcatech",
"price":515.99,
"currency":"USD",
"discountPercentage":0,
"rating": {
"rate":4.8,
"count":667
},
"stock":83,
"sku":"TO-SMA-0002",
"tags": [
"smartphones",
"orcatech"
],
"thumbnail":"https://toolorca.com/api/v1/images/placeholder/300x300.png?text=X1%20PRO&bg=0f6fde&fg=ffffff",
"images": [
"https://toolorca.com/api/v1/images/placeholder/800x800.png?text=X1%20PRO&bg=0f6fde&fg=ffffff",
"https://toolorca.com/api/v1/images/placeholder/800x800.png?text=ORCATECH&bg=0f6fde&fg=ffffff"
],
"createdAt":"2025-02-15T00:00:00Z",
"isDeleted":true,
"deletedAt":"2026-11-14T05:15:25Z"
}
Items per page (default 30, max 100). 0 returns the maximum.
skip
query
integer
Items to skip, for offset pagination.
page
query
integer
1-based page number; used when skip is not given.
curl 'https://toolorca.com/api/v1/carts?limit=1'
const res = await fetch('https://toolorca.com/api/v1/carts?limit=1');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
console.log(data);
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> main() async {
final res = await http.get(
Uri.parse('https://toolorca.com/api/v1/carts?limit=1'),
);
if (res.statusCode >= 400) throw Exception('HTTP ${res.statusCode}');
final data = jsonDecode(utf8.decode(res.bodyBytes));
print(data);
}
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
// Call from a background thread or coroutine (Dispatchers.IO), never the main thread.
val client = OkHttpClient()
val request = Request.Builder()
.url("https://toolorca.com/api/v1/carts?limit=1")
.build()
client.newCall(request).execute().use { res ->
if (!res.isSuccessful) error("HTTP ${res.code}")
println(res.body?.string())
}
import Foundation
var request = URLRequest(url: URL(string: "https://toolorca.com/api/v1/carts?limit=1")!)
let (data, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse, http.statusCode < 400 else {
throw URLError(.badServerResponse)
}
print(String(decoding: data, as: UTF8.self))
One cart. Also available per user: /users/{id}/carts.
Parameter
In
Type
Description
id required
path
integer
The cart id.
curl 'https://toolorca.com/api/v1/carts/3'
const res = await fetch('https://toolorca.com/api/v1/carts/3');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
console.log(data);
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> main() async {
final res = await http.get(
Uri.parse('https://toolorca.com/api/v1/carts/3'),
);
if (res.statusCode >= 400) throw Exception('HTTP ${res.statusCode}');
final data = jsonDecode(utf8.decode(res.bodyBytes));
print(data);
}
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
// Call from a background thread or coroutine (Dispatchers.IO), never the main thread.
val client = OkHttpClient()
val request = Request.Builder()
.url("https://toolorca.com/api/v1/carts/3")
.build()
client.newCall(request).execute().use { res ->
if (!res.isSuccessful) error("HTTP ${res.code}")
println(res.body?.string())
}
import Foundation
var request = URLRequest(url: URL(string: "https://toolorca.com/api/v1/carts/3")!)
let (data, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse, http.statusCode < 400 else {
throw URLError(.badServerResponse)
}
print(String(decoding: data, as: UTF8.self))
Create a cart. The API looks up each product and calculates line totals, discounts and the cart total. Nothing is saved: the response shows what a real API would return, and the data stays the same for everyone.
Load the list with select=title,price,thumbnail,rating to keep responses small, then fetch /products/{id} on the detail screen. Use skip and limit for infinite scroll and stop when meta.page equals meta.pages.
Prices are in USD, the currency most Cambodian shops quote online. Show discountPercentage as a sale badge and stock: 0 as "sold out" to test those states.
Frequently asked questions
Is this like FakeStoreAPI or DummyJSON?
It works the same way, with original data, Khmer product names, Cambodian products and USD prices. Everything is generated by ToolOrca.
Are POST and DELETE requests saved?
No. They return realistic responses (201 with a new id, the updated or deleted object) but the data never changes.
Why are the images plain colours with text?
They are placeholders generated by /images/placeholder, so there are no copyright issues and they always load fast.
Can I use it in a tutorial or course?
Yes, that is what it is for. A link back to ToolOrca is appreciated.