Mangrove Review Standard Schema Guide
This guide provides a comprehensive reference for implementing reviews that comply with the Mangrove Review Standard (MaReSt). It includes field definitions, examples, and practical guidance for developers.
Introduction
The Mangrove Review Standard uses JSON Web Tokens (JWT) to create cryptographically signed reviews. This guide focuses on the JWT payload structure - the actual review content that gets signed.
Quick Reference
Schema Overview
{
"sub": "URI", // REQUIRED: What is being reviewed
"iat": number, // REQUIRED: Unix timestamp
// At least ONE of these is required:
"rating": 0-100, // Numeric rating
"opinion": "text", // Text review (max 1000 chars)
"action": "type", // Moderation action
// Optional fields:
"images": [...], // Array of image objects
"metadata": {...} // Additional context
}
Field Requirements at a Glance
- ✅ Required:
sub,iat - ⚠️ At least one:
rating,opinion, oraction - ➕ Optional:
images,metadata
Field Definitions
Required Fields
sub (Subject URI)
Type: String
Purpose: Identifies what is being reviewed using a URI
The sub field must use one of these URI schemes:
1. HTTPS - Website Reviews
"sub": "https://example.org"
- Must be a valid URL
- Maximum 100 characters
- Used for reviewing websites, online services, or web pages
2. GEO - Location Reviews
"sub": "geo:40.7128,-74.0060?q=Great+Italian+Restaurant&u=50"
- Format:
geo:latitude,longitude?q=name&u=radius q: URL-encoded place name (required, max 100 chars)u: Uncertainty radius in meters (optional, max 40,000,000)- Used for restaurants, hotels, tourist attractions, etc.
3. URN:LEI - Company Reviews
"sub": "urn:lei:969500KSV493XWY0PS33"
- Must be a valid Legal Entity Identifier (LEI)
- 20-character alphanumeric code
- Verified against GLEIF database
4. URN:ISBN - Book Reviews
"sub": "urn:isbn:9781718500440"
- Must be a valid ISBN-10 or ISBN-13
- Used for reviewing books
5. URN:MARESI - Review of Reviews
"sub": "urn:maresi:7xQtIlHuDdCVioyztj8i3zJ8dk3oCSfKr6VCR5RtBn6sBcqvpfyvs13PlKGJoamKzx8xUgQTQJjRPv5s91-VLQ"
- References another review’s signature
- Used for meta-reviews or moderation actions
iat (Issued At)
Type: Number
Purpose: Timestamp when the review was created
"iat": 1708098842
- Unix timestamp (seconds since January 1, 1970)
- Must be a valid timestamp
- JavaScript:
Math.floor(Date.now() / 1000)
Conditional Fields (At Least One Required)
rating
Type: Number
Range: 0-100 (integer)
Purpose: Numeric score for the subject
"rating": 85
- 100 = highest recommendation
- 0 = lowest recommendation
- Maps to different scales:
- 5-star: 1=⭐, 25=⭐⭐, 50=⭐⭐⭐, 75=⭐⭐⭐⭐, 100=⭐⭐⭐⭐⭐
- Thumbs: 1-50=👎, 51-100=👍
- Percentage: Direct mapping
opinion
Type: String
Max Length: 1000 characters
Purpose: Text content of the review
"opinion": "Amazing pasta and great service! The ambiance was perfect for a date night."
- Can contain any UTF-8 text
- Should be meaningful and relevant to the subject
- Limited to 1000 characters for consistency
action
Type: String
Values: "delete", "edit", "report_abuse", "equivalence", "identity"
Purpose: Act on an existing review, on a subject, or on the reviewer’s own key
"action": "delete"
delete: Remove your own review.subis theurn:maresi:of the review.edit: Update your own review.subis theurn:maresi:of the review.report_abuse: Flag another user’s review.subis theurn:maresi:of the review.equivalence: Claim that two subjects are the same.subis one subject andopinionthe other.identity: Bind the signing key to a decentralized identifier.subis the DID, and the server accepts the claim only when the DID document lists the key; see the identity guide.
Optional Fields
images
Type: Array of Objects
Max Items: 5 (recommended)
Purpose: Attach images to the review
"images": [
{
"src": "https://example.com/images/pasta.jpg",
"label": "Delicious pasta dish"
},
{
"src": "https://example.com/images/tiramisu.jpg",
"label": "Homemade tiramisu"
}
]
- Each image object requires:
src(required): Public URL of the imagelabel(optional): Description (max 50 chars)
metadata
Type: Object
Purpose: Additional context about the reviewer or review
Common metadata fields:
"metadata": {
"nickname": "FoodLover",
"experience_context": "couple",
"is_personal_experience": true,
"client_id": "https://myapp.com"
}
Identity Fields
nickname: Display name (max 50 chars)preferred_username: Account name (max 20 chars)given_name: First name (max 20 chars)family_name: Last name (max 20 chars)birthdate: Date of birth (YYYY-MM-DD format)age: Age in years (max 200)gender: Gender identity (max 20 chars)
Context Fields
experience_context:"solo","couple","family","friends","business"client_id: URI of the app/website used to submit review
Flag Fields (Boolean)
is_personal_experience: Direct experience with subjectis_affiliated: Reviewer affiliated with subjectis_generated: Review created by automated system
Other Fields
data_source: URL if data from external sourceosm_id: OpenStreetMap identifier, optionally with version (e.g.,node/10066902935/13,way/12345/4,relation/67890)original_url: URL of original review on external platform (for deduplication when importing)reviewer_index: Unique index for ranking (max 9007199254740991)license: SPDX license identifier ("CC-BY-4.0"or"CC-BY-SA-4.0"). Defaults to"CC-BY-4.0"when not specified.
Complete Examples by Use Case
1. Basic Website Review
{
"iat": 1708098842,
"sub": "https://example.org",
"rating": 85,
"opinion": "Great testing first link example.",
"metadata": {
"nickname": "TestNickName1",
"client_id": "https://example.org"
}
}
2. Restaurant Review with Images
{
"iat": 1708098900,
"sub": "geo:40.7128,-74.0060?q=Great+Italian+Restaurant&u=50",
"rating": 90,
"opinion": "Amazing pasta and great service! The ambiance was perfect for a date night. I would highly recommend their house special and tiramisu.",
"metadata": {
"nickname": "FoodLover",
"experience_context": "couple",
"is_personal_experience": true
},
"images": [
{
"src": "https://example.com/images/pasta.jpg",
"label": "Delicious pasta dish"
},
{
"src": "https://example.com/images/tiramisu.jpg",
"label": "Homemade tiramisu"
}
]
}
3. Book Review
{
"iat": 1708098950,
"sub": "urn:isbn:9781718500440",
"rating": 85,
"opinion": "Excellent programming book that helped me understand complex concepts in a straightforward way. The examples are practical and the explanations are clear.",
"metadata": {
"nickname": "CodeReader",
"is_personal_experience": true
}
}
4. Company Review
{
"iat": 1708099000,
"sub": "urn:lei:969500KSV493XWY0PS33",
"rating": 70,
"opinion": "Good company to work with. Their customer service was responsive, though there were some delays in delivery.",
"metadata": {
"nickname": "BusinessPartner",
"experience_context": "business",
"is_personal_experience": true
}
}
5. Meta-Review (Review of a Review)
{
"iat": 1708099050,
"sub": "urn:maresi:7xQtIlHuDdCVioyztj8i3zJ8dk3oCSfKr6VCR5RtBn6sBcqvpfyvs13PlKGJoamKzx8xUgQTQJjRPv5s91-VLQ",
"rating": 90,
"opinion": "This review was very helpful and accurately described my experience as well.",
"metadata": {
"nickname": "MetaReviewer"
}
}
6. Delete Action
{
"iat": 1745329011,
"sub": "urn:maresi:fWMOYt4feQbeQqpQP1qzOGV3kCExUuj87vIZHPbSV0p7TxuW1xpLfqDFJ3S3WLIVLv_8xP3RsmyuYhbKcFVKYQ",
"action": "delete",
"metadata": {
"client_id": "http://localhost:5173/"
}
}
7. Edit Action
{
"iat": 1745348936,
"sub": "urn:maresi:fWMOYt4feQbeQqpQP1qzOGV3kCExUuj87vIZHPbSV0p7TxuW1xpLfqDFJ3S3WLIVLv_8xP3RsmyuYhbKcFVKYQ",
"action": "edit",
"rating": 95,
"opinion": "Updated opinion with improved rating",
"metadata": {
"client_id": "http://localhost:5173/"
}
}
8. Report Abuse Action
{
"iat": 1708099200,
"sub": "urn:maresi:aBC123xyz456def789GHI012jkl345MNO678pqr901STU234vwx567YZA890bcd",
"action": "report_abuse",
"opinion": "This review contains promotional content that violates terms of service."
}
9. Comprehensive Review with All Fields
{
"iat": 1708099250,
"sub": "geo:48.8584,2.2945?q=Eiffel+Tower+Restaurant&u=100",
"rating": 95,
"opinion": "The view from this restaurant is absolutely breathtaking! We visited during sunset and the food was as amazing as the scenery. The staff was incredibly attentive and knowledgeable about the menu. The wine pairing suggestions were perfect.",
"metadata": {
"nickname": "TravelFoodie",
"given_name": "Alex",
"age": 34,
"gender": "non-binary",
"experience_context": "family",
"is_personal_experience": true,
"client_id": "https://travel-reviews-app.example.com",
"preferred_username": "alexfoodie123",
"birthdate": "1990-05-15"
},
"images": [
{
"src": "https://example.com/images/view.jpg",
"label": "View from our table"
},
{
"src": "https://example.com/images/appetizer.jpg",
"label": "Starter dish"
},
{
"src": "https://example.com/images/main_course.jpg",
"label": "Main course"
},
{
"src": "https://example.com/images/dessert.jpg",
"label": "Dessert"
},
{
"src": "https://example.com/images/wine.jpg",
"label": "Wine selection"
}
]
}
10. Opinion-only Review
{
"iat": 1708099300,
"sub": "https://blog.example.com",
"opinion": "This blog has been a fantastic resource for learning about sustainable practices. The articles are well-researched and practical.",
"metadata": {
"nickname": "EcoReader"
}
}
11. Rating-only Review
{
"iat": 1708099350,
"sub": "geo:34.0522,-118.2437?q=Local+Coffee+Shop&u=20",
"rating": 80,
"metadata": {
"nickname": "QuickRater",
"experience_context": "solo"
}
}
12. Affiliated Review
{
"iat": 1708099400,
"sub": "https://restaurant.example.com",
"rating": 100,
"opinion": "As the chef and owner of this restaurant, I'm proud of what we've created. We focus on fresh, local ingredients and creating memorable dining experiences.",
"metadata": {
"nickname": "ChefOwner",
"is_affiliated": true,
"client_id": "https://restaurant.example.com"
}
}
13. Imported Review
{
"iat": 1708099500,
"sub": "geo:52.5200,13.4050?q=Berlin+Restaurant&u=30",
"rating": 80,
"opinion": "Excellent food and friendly staff.",
"metadata": {
"data_source": "https://lib.reviews",
"nickname": "original_author",
"license": "CC-BY-SA-4.0",
"is_personal_experience": true,
"osm_id": "node/12345678/4"
}
}
14. Generated Review
{
"iat": 1708099450,
"sub": "urn:lei:5493006MHB84DD0ZWV18",
"rating": 75,
"opinion": "Based on analysis of multiple data sources, this company shows consistent performance in its sector.",
"metadata": {
"nickname": "AnalyticsBot",
"is_generated": true,
"data_source": "https://analytics.example.com/reports/company-analysis",
"reviewer_index": 42
}
}
Validation Rules
Common Validation Errors
-
Missing Required Fields
- Error: “Missing required field: sub”
- Solution: Ensure both
subandiatare present
-
Missing Conditional Fields
- Error: “Payload must include at least one of: rating, opinion, or action”
- Solution: Add at least one of these fields
-
Invalid URI Scheme
- Error: “Invalid URI scheme. Must be one of: https, geo, urn:lei, urn:isbn, urn:maresi”
- Solution: Use a supported URI scheme
-
Rating Out of Range
- Error: “Rating must be between 0 and 100”
- Solution: Ensure rating is an integer from 0 to 100
-
Opinion Too Long
- Error: “Opinion exceeds maximum length of 1000 characters”
- Solution: Shorten the opinion text
-
Invalid Geo URI
- Error: “Invalid geo URI format”
- Solution: Use format
geo:lat,lon?q=name&u=radius
-
Invalid Action
- Error: “Action must be one of: delete, edit, report_abuse”
- Solution: Use a valid action value
Best Practices
-
Always validate before signing
- Use validation libraries or the JWT Inspector tool
- Check for required fields and proper formats
-
Use appropriate URI schemes
- Choose the scheme that best matches your subject
- Ensure URIs are properly formatted
-
Keep opinions concise
- Focus on key points within the 1000 character limit
- Use images to supplement text when needed
-
Include relevant metadata
- Add context that helps readers understand your perspective
- Use standard fields when possible
-
Handle actions carefully
- Only use actions on reviews you own (except report_abuse)
- Include clear reasons when reporting abuse
Integration Guide
Tools and Resources
The JWT Inspector builds a payload from example templates, signs it, and checks it against the Mangrove Review Standard.
The ecosystem page lists the libraries, APIs and other tools. Integration guides are at open-reviews.net/technology.
Quick Start Code
import { generateKeypair, signAndSubmitReview } from 'mangrove-reviews';
// Generate a keypair; store it to keep the same reviewer identity
const keypair = await generateKeypair();
// Sign and submit; the library sets `iat` itself
await signAndSubmitReview(keypair, {
sub: "https://example.com",
rating: 90,
opinion: "Excellent service!",
metadata: {
nickname: "HappyCustomer"
}
});
FAQ
General Questions
Q: Can I submit a review without a rating or opinion?
A: No, you must include at least one of: rating, opinion, or action.
Q: What’s the difference between nickname and preferred_username?
A: nickname is a display name (e.g., “John Smith”), while preferred_username is an account identifier (e.g., “jsmith123”).
Q: Can I review the same subject multiple times?
A: Yes, but each review should differ in sub, rating, or opinion. Use the edit action to update existing reviews.
Q: How do I reference my own review for editing?
A: Use the JWT signature from your original review as the MaReSi URI: urn:maresi:[signature]
Technical Questions
Q: Why use JWT instead of a simpler format?
A: JWTs provide cryptographic signatures, ensuring reviews can’t be tampered with and maintaining reviewer identity without central control.
Q: Can I use my own image hosting?
A: Yes, any publicly accessible URL works for image sources.
Q: What happens to deleted reviews?
A: The server marks them as deleted but may retain them for audit purposes. Clients should not display deleted reviews.
Q: How do I validate a geo URI?
A: Ensure it follows the pattern geo:latitude,longitude?q=name&u=radius with valid coordinates and URL-encoded place names.
Troubleshooting
Issue: “Invalid signature” error
- Ensure you’re using the same private key that generated the public key
- Check that the JWT hasn’t been modified after signing
Issue: “Timestamp too old” error
- Some servers may reject reviews with timestamps too far in the past
- Use current timestamp:
Math.floor(Date.now() / 1000)
Issue: Geo URI not working
- URL-encode the place name:
encodeURIComponent("Joe's Pizza")→Joe%27s%20Pizza - Ensure coordinates are in decimal format
Issue: Images not displaying
- Verify image URLs are publicly accessible
- Check that URLs use HTTPS (some clients require secure connections)
For more details, see the Mangrove Review Standard specification.