Gallery Submissions API
A write-only REST API for sharing real products and services purchased through OneCheckout. Submissions are private: they are content-screened and reviewed internally, and a curated subset may later appear in the OneCheckout gallery. Only approved items are ever readable, through the separate public feed.
base endpoint
overview
How it works
- 1
Submit a purchase
POST the product details to the endpoint. Writes require an API key, which you can register for yourself in one request. The endpoint is CORS-enabled and write-only.
- 2
Acknowledged privately
You receive a generic confirmation. Submissions are stored privately; the API never reveals whether an item was screened, is pending, approved, or rejected.
- 3
Internal review
Every submission is content-screened and reviewed by our team. All moderation happens internally and is never exposed through the API.
- 4
May be curated into the gallery
We may later feature a curated subset in the OneCheckout gallery, showing only limited, public-safe fields, never full last names, emails, or phone numbers.
access
Authentication
Submitting requires an API key. Reading the public gallery feed does not. You can register a key yourself in a single request, with no human approval step and no key needed to register.
/api/v1/keyscurl -X POST https://onecheckout.ai/api/v1/keys \
-H "Content-Type: application/json" \
-d '{"name":"my-agent","contactEmail":"you@example.com"}'
# => {"key":"oc_live_...","prefix":"oc_live_...","scopes":["write"]}
# The key is shown once. Store it now.The response returns key once and never again, so store it before continuing. Send it on every submission as either Authorization: Bearer <key> or X-API-Key: <key>. Both are accepted.
endpoint
Submit a purchase
/api/v1/gallery/submissionsAccepts a JSON body. Field names are accepted in both camelCase and snake_case. Every text field has a length limit and is safely truncated, so keep values concise (names ≤ 200 chars, each option ≤ 120 chars, description ≤ 2000 chars).
Required fields
| Field | Type | Description |
|---|---|---|
productUrlreq | string (URL) | The product, service, or offering URL that was purchased. e.g. "https://www.nike.com/t/air-force-1" |
sellerNamereq | string | Name of the seller, brand, or merchant. e.g. "Nike" |
productNamereq | string | Name of the product or service. e.g. "Air Force 1 '07" |
pricereq | number | Purchase price. Alias: priceAmount. Must be a non-negative number; stored to 2 decimals. e.g. 115.00 |
submitterEmailreq | string (email) | Contact email for the caller. Must contain @. Never shown publicly. e.g. "dev@yourapp.com" |
submitterPhonereq | string (E.164) | Contact phone for the caller. US & Canada only: must be "+1" followed by 10 digits. Common separators (spaces, dashes, parens, dots) are stripped before validation. Never shown publicly. e.g. "+14155550123" · "+1 (415) 555-0123" |
Optional fields
| Field | Type | Description |
|---|---|---|
rootUrl | string (URL) | Root URL of the site. Auto-derived from productUrl's origin when omitted. e.g. "https://www.nike.com" |
selectedOptions | string[] | The chosen options as discrete strings: size, color, tier, seat, etc. List each option separately; the specific variant/SKU should be apparent from the options here. Each option max 120 chars, up to 24 options. A single string is also accepted and split on common separators. e.g. ["White/White", "Men's size M 10 / W 11.5"] |
quantity | integer | Whole number of units purchased (min 1). e.g. 1 |
description | string | Optional longer description (max 2000 chars). |
currency | string (ISO 4217) | 3-letter currency code. Defaults to USD. e.g. "USD" |
priceInterval | enum | Billing cadence for subscriptions: once, day, week, month, quarter, year. Defaults to once. e.g. "month" |
imageUrl | string (URL) | Product image URL. Send the highest-resolution image available; 600px wide or larger strongly preferred. Avoid tiny thumbnails. e.g. "https://cdn.example.com/product-1200.png" |
textPoster | boolean | Set true when the image is a text-forward poster/card (a wordmark, plan card, event ticket, etc.) rather than a photographic product shot. Poster images are shown fully (contain-fit + centered) over a matching backdrop instead of being cropped to fill, so no artwork is cut off. Also accepts snake_case text_poster. Defaults to false. e.g. true |
logoUrl | string (URL) | A direct URL to the seller's own brand logo. Do not send third-party logo-service links; when a valid direct logo isn't available we resolve one ourselves. |
| category | string | The product category. Use one of our standard categories whenever it applies; send the exact value shown in the category list. If your product is genuinely new, you can submit a custom value. e.g. "apparel" · "saas" · "consumer services" |
sourcePlatform | string | Where the buy button lived, e.g. the ad network, chat, or article. e.g. "Pinterest ad" · "ChatGPT" · "The Verge" |
mcpRequest | object | string (JSON) | A re-playable MCP request that lets others buy exactly this product. Send the JSON-RPC style request object (or a JSON string). Stored as-is for reference and never executed by us. Max 20,000 chars serialized. e.g. { "method": "tools/call", "params": { "name": "checkout", "arguments": { "productUrl": "https://www.nike.com/t/air-force-1", "options": ["White/White", "M 10"] } } } |
buyerDisplayFirstName | string | Buyer's first name for public attribution (max 80 chars). e.g. "Alex" |
buyerDisplayLastInitial | string | Buyer's last initial only, never a full last name. Trimmed to a single initial. e.g. "R" |
purchasedAt | string (ISO 8601) | When the purchase happened. e.g. "2026-07-02T18:30:00Z" |
eventDate | string (YYYY-MM-DD) | Event start date for ticketed purchases. Any date is welcome, near or far. Always send it for tickets (category tickets/events), since it is the only field that lets us date the entry and a ticketed submission without one cannot be placed. Dated entries have a display lifetime: once the date has passed (evaluated in US Pacific time) the entry stops appearing on the public wall. Bare calendar date only, no time or timezone. Also accepts snake_case event_date. e.g. "2026-08-15" |
submitterRef | string | Your own reference / partner id for this submission. |
Content self-flag
Any truthy flag below forces the submission out of the public gallery, even if the automated screen passes. Use it to responsibly mark sensitive content.
| Field | Type | Description |
|---|---|---|
nsfw | boolean | Self-declare mature / sensitive content. Also accepts: mature, flagged, sensitive. e.g. true |
contentRating | string | Set to "mature" to hold the item out of the public gallery. e.g. "mature" |
Example request
curl -X POST https://onecheckout.ai/api/v1/gallery/submissions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ONECHECKOUT_API_KEY" \
-d '{
"productUrl": "https://www.nike.com/t/air-force-1",
"rootUrl": "https://www.nike.com",
"sellerName": "Nike",
"productName": "Air Force 1 '07",
"submitterEmail": "dev@yourapp.com",
"submitterPhone": "+14155550123",
"selectedOptions": ["White/White", "Men's size M 10 / W 11.5"],
"quantity": 1,
"price": 115.00,
"currency": "USD",
"priceInterval": "once",
"imageUrl": "https://static.nike.com/air-force-1-1200.png",
"category": "apparel",
"sourcePlatform": "Pinterest ad",
"buyerDisplayFirstName": "Alex",
"buyerDisplayLastInitial": "R",
"purchasedAt": "2026-07-02T18:30:00Z"
}'const res = await fetch("https://onecheckout.ai/api/v1/gallery/submissions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.ONECHECKOUT_API_KEY}`,
},
body: JSON.stringify({
productUrl: "https://www.netflix.com/premium",
sellerName: "Netflix",
productName: "Netflix Premium 4K",
submitterEmail: "dev@yourapp.com",
submitterPhone: "+1 (415) 555-0123",
selectedOptions: ["Premium", "Ad-free", "4K UHD"],
quantity: 1,
price: 22.99,
currency: "USD",
priceInterval: "month",
category: "streaming",
sourcePlatform: "X promoted post",
}),
})
const { id, message } = await res.json()
// The response is intentionally generic; no moderation status is returned.// Ticketed event: always include eventDate (YYYY-MM-DD). Ticket art is
// usually a poster/card, so set textPoster: true so it isn't cropped.
const res = await fetch("https://onecheckout.ai/api/v1/gallery/submissions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.ONECHECKOUT_API_KEY}`,
},
body: JSON.stringify({
productUrl: "https://tickets.example-arena.com/e/aurora-live",
sellerName: "Aurora Live",
productName: "Aurora: World Tour",
category: "tickets",
eventDate: "2026-08-15", // event START date; any date is welcome
selectedOptions: ["Section 104", "Row 12", "2 seats"],
quantity: 2,
price: 189.5,
currency: "USD",
imageUrl: "https://cdn.example-arena.com/aurora-poster.png",
textPoster: true, // show the full poster, don't crop it
submitterEmail: "dev@yourapp.com",
submitterPhone: "+14155550123",
}),
})
// Dated entries stop showing on the public wall once the date has passed
// (US Pacific). Send the purchase you made; we handle the display lifecycle.Responses
{
"id": "b1c2d3e4-....",
"message": "Received. Thank you, your submission will be reviewed."
}hint field carries the registration steps.{
"ok": false,
"error": {
"code": "unauthorized",
"message": "This endpoint requires an API key.",
"hint": "Writes require an API key. Register one in a single request: POST https://onecheckout.ai/api/v1/keys with {\"name\",\"contactEmail\"}, then send it as \"Authorization: Bearer <key>\". Reads need no key.",
"documentation": "https://onecheckout.ai/docs/gallery-api.md"
}
}{
"ok": false,
"error": {
"code": "validation_failed",
"message": "One or more fields are invalid.",
"hint": "See https://onecheckout.ai/docs/gallery-api.md for the full field list, required fields, and the category taxonomy.",
"details": [
"productUrl is required and must be a valid http(s) URL",
"sellerName is required",
"submitterEmail is required",
"submitterPhone is required"
],
"documentation": "https://onecheckout.ai/docs/gallery-api.md"
}
}Retry-After header (seconds).{
"ok": false,
"error": {
"code": "rate_limited",
"message": "Daily limit reached. Each email and phone is capped at 50 requests per day.",
"hint": "Wait for the number of seconds in `error.retryAfter`, then retry.",
"retryAfter": 43200,
"documentation": "https://onecheckout.ai/docs/gallery-api.md"
}
}- 400 Invalid JSON body.
Rate limits
Limits are enforced per identity: both your submitterEmail and submitterPhone are metered independently, so rotating one while reusing the other does not reset your quota.
- •50 requests per day per email and per phone (resets at 00:00 UTC).
- •1 request per second per email and per phone.
- •Requests that fail validation (
422) do not count against your quota.
Reading the gallery
GET /api/v1/gallery/feed returns approved purchases, newest first. Reading the feed requires no API key; only submissions do. This is the only read path: a GET on the submissions endpoint returns a schema, not a list.
The feed is cursor-paginated. Pass each response's nextCursor back as cursor and stop when it is null.
- •
limitis 1 to 200 (default 60). Asking for more returns 200 plus a cursor; it never silently truncates. - •Treat the cursor as opaque. Pass it back verbatim rather than building or parsing one. A malformed cursor returns
400, never a silent restart from page one. - •A
503while paginating is transient: retry the same cursor. OnlynextCursor: nullmeans the feed ended.
taxonomy
Categories
This is a normalized list of standard categories. Send the exact value shown for the category field whenever one applies, always preferring the most specific fit. Use shopping only as a last-resort catch-all for a physical purchase that fits no more specific category. If your product is genuinely new and none of these fit, you can submit a custom value; anything unrecognized is shown generically in the gallery.
accessoriesBags, belts, watches, and small goods.
apiDeveloper APIs, SDKs, and usage-based developer platforms.
apparelClothing and wearables.
automotiveVehicles, parts, and accessories.
beautyCosmetics, skincare, and grooming.
books mediaPhysical books, print, and physical media.
cloud servicesCloud hosting, storage, and infrastructure.
consumer servicesNon-physical consumer services and memberships.
digital servicesDigital-only services and online tools.
electronicsDevices, gadgets, and hardware.
eventsConferences, shows, and experiences.
financial servicesFintech, banking, and financial service subscriptions.
fitnessFitness gear, equipment, and training products.
food beverageFood, drink, and consumables.
footwearShoes, sneakers, and boots.
furnitureFurniture and large home items.
gamingDigital games, in-game goods, and game subscriptions.
health wellnessSupplements and physical wellness products.
homeHomeware, kitchen, and household goods.
insuranceInsurance policies and coverage plans.
jewelryJewelry and fine accessories.
membershipsMembership and community access plans.
online coursesCourses, cohorts, and digital learning.
petProducts for pets and animals.
saasSoftware-as-a-service products and platforms.
shoppingGeneral ecommerce purchases that don't fit a more specific category.
softwareDownloadable or licensed software and apps.
sporting goodsSports, fitness, and outdoor gear.
streamingStreaming video, music, and media subscriptions.
subscriptionsRecurring subscription plans not covered by another bucket.
telecomMobile, internet, and connectivity plans.
ticketsEvent tickets (usually poster/card artwork).
toys gamesPhysical toys and tabletop games.
travelFlights, stays, and travel bookings.
policy
Submissions are private
This is a write-only API. There is no endpoint that lists or browses what you submitted, and the submit response never discloses moderation state: you cannot tell whether an item was screened, is pending, approved, or rejected. Approved items may later surface in the public gallery feed, which is a curated read-only view and never reflects pending or rejected submissions.
What may become public later
We may later feature a hand-curated subset of submissions in the OneCheckout gallery. That surface will expose only limited, public-safe fields, never full last names, email addresses, or phone numbers. Contact details you provide are used solely for follow-up and abuse prevention.
policy
Moderation & content policy
Every submission is content-screened and reviewed internally before it could ever be considered for the gallery. Moderation decisions are made entirely on our side and are not exposed through the API.
Content policy
Explicit, adult, illegal, weapons, hateful, or otherwise sensitive products are automatically rejected and will not be published. We also honor your self-declared content flags. Submitting content that violates this policy may result in your submissions being blocked.