3Moss
Englishრეგისტრაცია

დიზაინერებისა და მათი დეველოპერებისთვის

3Moss-ის API

3Moss-ზე ყველა დიზაინერს საკუთარი API აქვს. გამოიყენე ის, რომ შენი პროდუქტები საკუთარ ვებსაიტზე გაყიდო, ან მიეცი გასაღები სხვა ვებსაიტს, რომელსაც მათი გაყიდვა სურს, და შეკვეთები 3Moss-ს დაუბრუნე: 3Moss თითოეულს დაბეჭდავს და გააგზავნის. გასაღებები შენს ანგარიშში შექმენი, API-ის გვერდზე.

ავთენტიფიკაცია

გასაღები Authorization ჰედერში გაგზავნე, შენი სერვერიდან. ვისაც შენი გასაღები აქვს, შენს პროდუქტებს წაიკითხავს, ამიტომ არასოდეს ჩასვა ის გვერდის JavaScript-ში ან საჯარო რეპოზიტორიაში. თითო ვებსაიტს ცალკე გასაღები შეუქმენი, რომ ერთის გაუქმებამ დანარჩენები არ გააფუჭოს; შეიძლება გქონდეს 10-მდე.

Authorization: Bearer 3m_live_…

შენი პროდუქტების სია

GET http://localhost:3000/api/v1/products
limit
1–100, ნაგულისხმევად 50.
store
მხოლოდ ერთი მაღაზიის პროდუქტები — id /api/v1/stores-დან.
tag
მხოლოდ ამ თეგის მქონე პროდუქტები. გაიმეორე ან მძიმით გამოყავი, თუ გინდა პროდუქტები, რომლებსაც ყველა ეს თეგი აქვს — თეგების გვერდებისთვის.
q
ძიება: ემთხვევა სათაურს, აღწერას, SEO სათაურს და თეგებს, ქართულად ან ინგლისურად. 100 სიმბოლომდე.
updated_since
დრო ISO 8601 ფორმატში, სინქრონიზაციისთვის.
cursor
next_cursor წინა გვერდიდან.

updated_since-ის გარეშე მიიღებ იმას, რაც ახლა იყიდება. მასთან ერთად — ყველა პროდუქტს, რომელიც მას შემდეგ შეიცვალა, იყიდება თუ არა; დამალე ისინი, რომლებსაც აქვთ "available": false. გვერდზე შეიძლება limit-ზე ნაკლები პროდუქტი იყოს; მიჰყევი next_cursor-ს, სანამ ის null არ გახდება. წაშლილი პროდუქტები ამ სიაში არ არის — ისინი /products/deleted-ს ჰკითხე. პროდუქტი შეცვლილად ითვლება, როცა იცვლება ნებისმიერი რამ, რაც მასზე ჩანს — მომწოდებლის მარაგის, ფასების, მიწოდებისა და მისი მაღაზიის სახელის ჩათვლით — და როდის, ამას მისი updated_at გეტყვის.

curl "http://localhost:3000/api/v1/products?limit=20" \
  -H "Authorization: Bearer $THREEMOSS_KEY"

ერთი პროდუქტი

GET http://localhost:3000/api/v1/products/{id}
GET http://localhost:3000/api/v1/products/{handle}

პროდუქტს აბრუნებს მაშინაც, როცა ის არ იყიდება, რომ მარაგში არარსებულად აჩვენო. გამოიყენე id ან handle — ასე /products/owl-moon გვერდს შეუძლია პირდაპირ owl-moon მოითხოვოს. თუ ერთი სტრიქონი ერთდროულად id-იც არის და handle-იც, id-ს აქვს უპირატესობა.

წაშლილი პროდუქტები

GET http://localhost:3000/api/v1/products/deleted?since=2026-10-09T21:14:03Z

ყველა პროდუქტი, რომელიც since-ის შემდეგ წაშალე, ჯერ უახლესი, იგივე store, limit და cursor პარამეტრებით, რაც პროდუქტების სიას აქვს. თითოეული შენი საიტიდან მოხსენი. since-ად ბოლო სინქრონიზაციის დრო გამოიყენე — იგივე, რასაც updated_since-ად აგზავნი. წაშლები 90 დღე ინახება; თუ შენი საიტი ამაზე დიდხანს არ სინქრონიზებულა, სრული სინქრონიზაცია გაუშვი.

{
  "deleted": [
    {
      "id": "p_7Hk2…",
      "store_id": "ecc0…",
      "handle": "owl-moon",
      "deleted_at": "2026-10-10T08:02:51.000Z"
    }
  ],
  "next_cursor": null
}

/products/{id}-ზე ამ 90 დღეში წაშლილი პროდუქტის მოთხოვნა 404-ის ნაცვლად 410-ს აბრუნებს, მისი deleted_at-ით.

შენი მაღაზიები

GET http://localhost:3000/api/v1/stores

როგორ გამოიყურება პროდუქტი

{
  "id": "p_7Hk2…",
  "store": {
    "id": "ecc0…",
    "name": "Night Owl Co."
  },
  "title": "Owl moon",
  "description": "…",
  "available": true,
  "garment": {
    "name": "Unisex Heavy Cotton Tee",
    "brand": "Gildan 5000",
    "category": "T-shirts"
  },
  "price": {
    "amount": "59.94",
    "currency": "GEL"
  },
  "shipping": {
    "first": "12.34",
    "additional": "6.24",
    "currency": "GEL"
  },
  "variants": [
    {
      "color": {
        "id": "white",
        "name": "White",
        "hex": "#f7f7f5"
      },
      "size": "M",
      "available": true
    }
  ],
  "handle": "owl-moon",
  "images": [
    {
      "url": "http://localhost:3000/api/images/…?t=…",
      "view": "front",
      "alt": "Owl moon — front"
    }
  ],
  "tags": [
    "owl",
    "night sky"
  ],
  "seo": {
    "title": "Owl moon tee, hand-drawn night sky",
    "description": "…",
    "image": "http://localhost:3000/api/images/…?t=…",
    "json_ld": {
      "@context": "https://schema.org",
      "@type": "Product",
      "name": "Owl moon",
      "offers": {
        "…": "…"
      }
    }
  },
  "attributes": {
    "style": "vintage"
  },
  "updated_at": "2026-10-09T21:14:03.000Z"
}
  • ფასები სტრიქონებია currency-სთან ერთად, რომ მცურავი წერტილის გამო არაფერი დაიკარგოს. ყველა ფასი ქართულ ლარშია (GEL).
  • price შენი ფასია. საკუთარ ვებსაიტზე შეგიძლია მეტი მოითხოვო.
  • სურათების ბმულები ოცდაათ დღეს მოქმედებს და ყოველ მოთხოვნაზე თავიდან ხელმოიწერება. სურათები ჩამოტვირთე და ბმულით ნუ მიუთითებ.

საძიებო სისტემები

ყველა პროდუქტი პროდუქტის გვერდის <head>-ისთვის მზადაა. handle ბმულის წასაკითხი სახელია, ქართული სათაურებით ლათინურ ასოებში (ღამის ბუ → ghamis-bu), უნიკალური შენს პროდუქტებს შორის. ის სათაურს მიჰყვება, სანამ დიზაინერი თავად არ დააყენებს, ამიტომ შეინახე id და handle მისამართად ჩათვალე: როცა შეიცვლება, ძველი ახალზე გადაამისამართე. seo.title და seo.description არასოდეს არის ცარიელი და უკვე ეტევა იმაში, რასაც ძიების შედეგი აჩვენებს. seo.json_ld არის schema.org-ის Product ფასითა და ხელმისაწვდომობით, გაფართოებული შედეგებისთვის; მის offers-ში შენი გვერდის url დაამატე. ყველა სურათს აქვს alt ტექსტი.

<title>{product.seo.title}</title>
<meta name="description" content="{product.seo.description}">
<meta property="og:title" content="{product.seo.title}">
<meta property="og:image" content="{your copy of product.seo.image}">
<link rel="canonical" href="https://your-site.ge/products/{product.handle}">
<script type="application/ld+json">{JSON.stringify(product.seo.json_ld)}</script>

სურათების ბმულებს ვადა გასდის, ამიტომ საკუთარი ასლები გამოიყენე — og:image-შიც და JSON-LD-ის image-შიც.

შეკვეთის გაგზავნა

POST http://localhost:3000/api/v1/orders

როცა შენი ვებსაიტი შენს რომელიმე პროდუქტს გაყიდის, შეკვეთა აქ გამოგზავნე და 3Moss მას დაბეჭდავს და შენს მყიდველს მიაწვდის. მყიდველი შენ გიხდის; 3Moss შენგან ბეჭდვისა და მიწოდების საფასურს იღებს. თუ იმ მაღაზიის ბალანსი, რომელშიც პროდუქტებია, ამას ფარავს, შეკვეთა მაშინვე მისით გადაიხდება და ბეჭდვაზე მიდის (status received). თუ არა, ის awaiting_payment სტატუსით ელოდება, სანამ შენს ანგარიშში არ გადაიხდი, და უქმდება, თუ 7 დღის შემდეგაც გადაუხდელია. ორივე შემთხვევაში შეტყობინებას მიიღებ.

{
  "external_id": "my-site-1042",
  "customer": {
    "name": "ნინო ბერიძე",
    "phone": "+995 555 12 34 56",
    "email": "nino@example.com"
  },
  "address": {
    "line1": "რუსთაველის გამზ. 34, ბ. 5",
    "city": "თბილისი"
  },
  "items": [
    {
      "product_id": "p_7Hk2…",
      "color": "white",
      "size": "M",
      "quantity": 2
    }
  ],
  "paid": {
    "amount": "139.88",
    "currency": "GEL"
  }
}
  • external_id შენივე შეკვეთის id-ია. იმავეს ხელახლა გაგზავნა უსაფრთხოა: იმავე შეკვეთას დაგიბრუნებს created: false-ით და 201-ის ნაცვლად 200 სტატუსით — ასე რომ, ტაიმაუტის შემდეგ თამამად სცადე ხელახლა.
  • გამოიყენე პროდუქტის id, variants[].color.id და size პროდუქტების API-დან. ერთ შეკვეთაში ყველაფერი შენი ერთი მაღაზიიდან უნდა იყოს; თითო მაღაზიაზე ცალკე შეკვეთა გაგზავნე.
  • მთელი შეკვეთა 422-ით უარყოფილია, თუ მასში რამე ახლა ვერ იყიდება — პროდუქტი შეჩერებულია, ფერი ან ზომა ამოიწურა — ამიტომ ჯერ ხელმისაწვდომობა შეამოწმე და, თუ ასე მოხდა, შეტყობინება აჩვენე.
  • 3Moss საქართველოში აწვდის: ტელეფონი და ქალაქი სავალდებულოა, ხოლო address.country, თუ გაგზავნი, GE უნდა იყოს. customer.email და paid არასავალდებულოა; paid არის ის, რაც მყიდველმა შენ გადაგიხადა, შენივე ჩანაწერებისთვის.
{
  "order": {
    "id": "QWPEZW9V",
    "external_id": "my-site-1042",
    "status": "received",
    "tracking": null,
    "items": [
      {
        "product_id": "p_7Hk2…",
        "title": "Owl moon",
        "color": "white",
        "size": "M",
        "quantity": 2
      }
    ],
    "paid": {
      "amount": "139.88",
      "currency": "GEL"
    },
    "timeline": [
      {
        "status": "awaiting_payment",
        "at": "2026-10-19T09:12:40.000Z"
      },
      {
        "status": "received",
        "at": "2026-10-19T09:12:40.000Z"
      }
    ],
    "created_at": "2026-10-19T09:12:40.000Z",
    "updated_at": "2026-10-19T09:12:40.000Z"
  },
  "created": true
}

შენი შეკვეთები

GET http://localhost:3000/api/v1/orders/{id}
GET http://localhost:3000/api/v1/orders/{external_id}
GET http://localhost:3000/api/v1/orders?updated_since=2026-10-19T09:00:00Z

ერთი შეკვეთა, 3Moss-ის id-ით ან შენი external_id-ით; ან ყველა შეკვეთა, რომელიც შენმა ვებსაიტებმა გამოგზავნეს, ჯერ ყველაზე ძველი ცვლილება, იგივე limit-ითა და cursor-ით, რაც პროდუქტების სიას აქვს. updated_since-ით პერიოდულად ჰკითხე, რომ მყიდველებს უთხრა, როდის გაიგზავნა მათი შეკვეთა: მაშინ tracking შეივსება. სტატუსებია awaiting_payment, received, in_production, shipped, delivered და cancelled. სიაში მხოლოდ API-ით გამოგზავნილი შეკვეთებია, შენი ნებისმიერი გასაღებიდან.

ვებჰუკები

პერიოდული კითხვის ნაცვლად, API-ის გვერდზე გასაღებს ვებჰუკის მისამართი მიეცი (https:// URL შენს სერვერზე). ცვლილებიდან დაახლოებით ერთ წუთში 3Moss მას POST-ს გაუგზავნის:

  • order.updated — შეკვეთამ, რომელიც ამ გასაღებმა გამოგზავნა, სტატუსი შეიცვალა ან თრექინგის ნომერი მიიღო.
  • product.updated — შეიცვალა რამე, რასაც პროდუქტის გვერდი აჩვენებს: სათაური, ფასი, ფოტოები, ფერები, ზომები.
  • product.unavailable — მისი ყიდვა აღარ შეიძლება: შეჩერებულია, ყველა ფერსა და ზომაში ამოიწურა, მოხსნილია ან წაშლილია.

შეკვეთები მხოლოდ იმ გასაღებს მიდის, რომელმაც ისინი გამოგზავნა; პროდუქტები — შენს ყველა გასაღებს. სხეული მთელ ობიექტს შეიცავს, ზუსტად ისე, როგორც მას GET /orders/{id} ან GET /products/{id} აბრუნებს, ამიტომ მეორე მოთხოვნა არ გჭირდება:

{
  "id": "evt_8fK2mQ…",
  "type": "order.updated",
  "created_at": "2026-10-20T09:14:03.000Z",
  "data": {
    "order": { "id": "QWPEZW9V", "external_id": "1042", "status": "shipped", "tracking": "GE123…", … }
  }
}

წაშლილ პროდუქტს საჩვენებელი აღარაფერი აქვს, ამიტომ მხოლოდ ამას შეიცავს:

{
  "type": "product.unavailable",
  "data": { "product": { "id": "p_7Hk2…", "store_id": "ecc0…", "handle": "owl-moon", "deleted": true } }
}

ყველა მოთხოვნას აქვს X-3Moss-Event (ტიპი), X-3Moss-Delivery (მოვლენის id, ყოველ ხელახალ ცდაზე ერთი და იგივე, რომ გამეორება უგულებელყო) და X-3Moss-Signature. სანამ სხეულს ენდობი, ხელმოწერა შეამოწმე: ეს არის HMAC-SHA256, შექმნილი გასაღების ხელმოწერის საიდუმლოთი (whsec_…, ჩანს ერთხელ, როცა მისამართს შეინახავ), დროისა და დაუმუშავებელი სხეულისგან. უარყავი ყველაფერი, რაც ხუთ წუთზე ძველია, რომ მოგვიანებით გამეორებულმა ძველმა მოთხოვნამ ვერაფერი გააკეთოს:

import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody: the request body exactly as received, before JSON.parse.
function from3Moss(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((part) => part.split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const given = Buffer.from(parts.v1 ?? "", "hex");
  return given.length === 32 && timingSafeEqual(given, Buffer.from(expected, "hex"));
}

სწრაფად უპასუხე ნებისმიერი 2xx-ით, საქმე კი მერე გააკეთე; 3Moss ათ წამს ელოდება. გადამისამართებას არ მიჰყვება. ნებისმიერ სხვა პასუხზე ხელახლა ცდის 1, 5 და 30 წუთის შემდეგ, მერე ყოველ საათში. იგზავნება მხოლოდ ობიექტის ბოლო მდგომარეობა: თუ შეკვეთა ორჯერ შეიცვლება, სანამ პირველს მიიღებ, მეორეს მიიღებ. ერთი დღის წარუმატებელი ცდების შემდეგ მისამართი ითიშება და შეგატყობინებთ; ჩართე ის API-ის გვერდიდან და updated_since-ით დაეწიე გამოტოვებულს. იქვე „Send a test“ აგზავნის ping მოვლენას ცარიელი data-ით, ხელმოწერილს ისევე, როგორც დანარჩენები.

შენი სერვერიდან

Node 22 ან უფრო ახალი:

const res = await fetch("http://localhost:3000/api/v1/products", {
  headers: { Authorization: `Bearer ${process.env.THREEMOSS_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).error.message);
const { products, next_cursor } = await res.json();

ლიმიტები და შეცდომები

60 მოთხოვნა წუთში და 1000 საათში, თითო გასაღებზე. ამის მერე მიიღებ 429-ს Retry-After ჰედერით, წამებში. ყველა შეცდომა ერთნაირად გამოიყურება:

{ "error": { "code": "not_found", "message": "No such product." } }
400bad_requestquery პარამეტრი ისეთი არ არის, რომელსაც API იღებს. შეტყობინება მას ასახელებს.
401unauthorizedგასაღები არ არის, არასწორია ან გაუქმებულია.
404not_foundასეთი პროდუქტი ან შეკვეთა შენსას შორის არ არის.
410goneშენი ეს პროდუქტი წაიშალა. სხეულში deleted_at-იც არის.
422invalid_orderშეკვეთის მიღება ასე გაგზავნილი სახით შეუძლებელია: შეტყობინება გეტყვის, რომელი ნივთი ან ველი და რატომ. არაფერი შექმნილა.
429rate_limitedზედმეტად ბევრი მოთხოვნა. დაელოდე Retry-After-ში მითითებულ წამებს.
500internalჩვენს მხარეს რაღაც შეცდომა მოხდა. სცადე ხელახლა.