---
url: https://docs.ogmabox.com/he/plugins/README.md
description: >-
  בנו, ארזו, התקינו, הפעילו והפיצו תוספי Ogma עם לוגיקה בצד השרת, לוחות צד לקוח,
  פקודות, הרשאות ומטא-נתונים לחנות התוספים.
---

# מערכת התוספים של Ogma {#ogma-plugin-system}

תוספי Ogma מרחיבים את הכלי באמצעות לוגיקה מותאמת אישית בצד השרת, לוחות ממשק משתמש בצד הלקוח ושלבים בתהליכי עבודה. תוספים מותקנים מקומית מתיקייה בדיסק, מופעלים לכל פרויקט בנפרד ורצים בסביבה מבודדת.

מסמך זה הוא מדריך העזר הראשי למפתחי תוספים.

להתחלה המהירה ביותר, השתמשו ב[מדריך ההתחלה המהירה עם תוספים](/he/plugins/quickstart).

***

## התחלה מהירה {#quick-start}

### תוסף צד שרת מינימלי {#minimal-backend-plugin}

```
my-plugin/
  manifest.json
  backend/script.js
```

`manifest.json`:

```json
{
  "id": "my-plugin",
  "name": "My Plugin",
  "version": "1.0.0",
  "plugins": [
    {
      "kind": "backend",
      "id": "my-plugin-backend",
      "entrypoint": "backend/script.js"
    }
  ]
}
```

`backend/script.js` (ES2020; ניתן להשתמש בייבוא בעת שימוש בכלי איגוד):

```js
async function init(sdk) {
  sdk.console.log("my-plugin started");

  sdk.events.onInterceptResponse(function(req, res) {
    if (res.getCode() === 403) {
      sdk.console.warn("403 on " + req.getUrl());
    }
  });
}
```

זה מספיק לתוסף פועל בצד השרת בלבד. שמרו אותו כקובץ יחיד ב-`backend/script.js` והפנו אליו ישירות את `manifest.json`.

### תהליך בנייה בן דקה (קוד מקור ב-TypeScript) {#one-minute-build-flow-typescript-source}

אם אתם כותבים ב-TypeScript, השתמשו במבנה הבא:

```text
my-plugin/
  manifest.json
  backend/
    src/index.ts
```

בנייה:

```bash
pnpm add -D @ogmabox/ogma-sdk esbuild typescript
pnpm exec esbuild backend/src/index.ts --bundle --format=iife --platform=neutral --external:@ogma/sdk --external:@ogmabox/ogma-sdk --outfile=backend/script.js
```

התקנה: **תוספים > התקנה**, בחרו את התיקייה `my-plugin/`. לאחר מכן הפעילו אותו.

***

## מדריך עזר למניפסט {#manifest-reference}

`manifest.json` נמצא בתיקיית השורש של החבילה. כל השדות רגישים לאותיות גדולות וקטנות.

### שדות ברמה העליונה {#top-level-fields}

| שדה | חובה | טיפוס | הערות |
|-------|----------|------|-------|
| `id` | כן | מחרוזת | אותיות לטיניות קטנות, ספרות ומקפים בלבד. עד 64 תווים. ייחודי בין התוספים המותקנים. |
| `version` | כן | מחרוזת | גרסאות סמנטיות: `MAJOR.MINOR.PATCH` |
| `name` | לא | מחרוזת | שם התצוגה המופיע בממשק. ברירת המחדל היא `id`. |
| `description` | לא | מחרוזת | תקציר בשורה אחת. |
| `author` | לא | אובייקט | `{ "name": "...", "email": "...", "url": "..." }` |
| `homepage` | לא | מחרוזת | כתובת URL למאגר קוד המקור או לתיעוד. |
| `plugins` | כן | מערך | רשומה אחת או יותר של רכיבי תוסף (ראו בהמשך). |
| `permissions` | לא | מערך | רשימת שמות ההרשאות הנדרשות (ראו [הרשאות](#permissions)). |

### רשומת רכיב תוסף {#plugin-component-entry}

כל אובייקט במערך `plugins` מתאר רכיב אחד.

**רכיב צד שרת:**

```json
{
  "kind": "backend",
  "id": "my-plugin-backend",
  "entrypoint": "backend/script.js",
  "runtime": "javascript",
  "assets": "backend/assets"
}
```

**רכיב צד לקוח:**

```json
{
  "kind": "frontend",
  "id": "my-plugin-frontend",
  "entrypoint": "frontend/script.js",
  "style": "frontend/style.css",
  "assets": "frontend/assets",
  "backend": { "id": "my-plugin-backend" }
}
```

| שדה | חובה | הערות |
|-------|----------|-------|
| `kind` | כן | `"backend"` או `"frontend"` |
| `id` | כן | ייחודי במניפסט. אותיות קטנות ומקפים. |
| `entrypoint` | כן | נתיב יחסי לקובץ הכניסה של JS. |
| `style` | לא | קובץ CSS הנטען ב-iframe של התוסף. |
| `assets` | לא | תיקיית נכסים סטטיים המוגשים תחת `/plugins/{id}/assets/`. |
| `backend.id` | לא | מקשר רכיב צד לקוח לרכיב צד השרת שלו עבור RPC דרך `sdk.backend.*`. |
| `runtime` | לא (צד שרת בלבד) | `"javascript"` (ברירת המחדל והערך הנתמך היחיד). |

***

## API של תוספי צד שרת (sdk) {#backend-plugin-api-sdk}

אובייקט `sdk` של צד השרת מועבר לפונקציה `init(sdk)` שלכם. כל המתודות סינכרוניות אלא אם הן מסומנות כ-`async`.

### `sdk.console` {#sdk-console}

```js
sdk.console.log("message")
sdk.console.warn("message")
sdk.console.error("message")
```

כותבת למאגר היומן של התוסף (מוצג בלשונית יומנים). נשמרות עד 500 רשומות. כל הודעה נקטעת ב-1 KB.

### `sdk.meta` {#sdk-meta}

```js
sdk.meta.id()          // > string: plugin id (e.g. "my-plugin")
sdk.meta.packageId()   // > string: same as id
sdk.meta.version()     // > string: semver (e.g. "1.0.0")
sdk.meta.path()        // > string: writable data directory for this plugin
```

`sdk.meta.path()` מצביע על תיקיית הנתונים הפרטית והניתנת לכתיבה של התוסף, למשל `~/.local/share/ogma/plugins/my-plugin/data`. התיקייה נוצרת אוטומטית וניתן להשתמש בה כדי לשמר נתוני תוסף בין הפעלות מחדש.

גם `sdk.storage`,‏ `sdk.path` ו-`sdk.fs` זמינים ככלי עזר למצב ולקבצים.

### `sdk.storage` {#sdk-storage}

```js
sdk.storage.get("key")      // > string | null
sdk.storage.set("key", "value")
sdk.storage.delete("key")
sdk.storage.clear()
sdk.storage.keys()          // > string[]
```

`sdk.storage` מוגבל למזהה התוסף ונשמר בין הפעלות מחדש של התוסף.

### `sdk.fs` {#sdk-fs}

```js
sdk.fs.readFile("relative/file.txt")      // > string
sdk.fs.writeFile("relative/file.txt", "text")
sdk.fs.appendFile("relative/file.txt", "more")
sdk.fs.exists("relative/file.txt")        // > boolean
sdk.fs.existsSync("relative/file.txt")    // > boolean
sdk.fs.list("relative/dir")              // > string[]
sdk.fs.mkdir("relative/dir")
```

`sdk.fs` מוגבל לקבצים תחת `sdk.meta.path()`.

`read` ו-`write` נשארים כינויים לצורכי תאימות עבור `readFile` ו-`writeFile`. השתמשו ב-`exists` או ב-`existsSync` לפני יצירת קובץ שאינכם רוצים לדרוס. ממשקי API אלה פועלים באופן סינכרוני בסביבת הרצת התוסף; הם אינם מודול `fs` המלא של Node.js. גישת תוסף לקבצים דורשת הרשאת `plugin_storage` ונשארת בתוך תיקיית הנתונים הפרטית של התוסף. ל-JavaScript בתהליכי עבודה יש הקשר שונה של מערכת הקבצים; ראו [גישת תהליך עבודה לקבצים](../app/workflows.md#javascript-and-files).

### `sdk.path` {#sdk-path}

```js
sdk.path.join("a", "b", "c")
sdk.path.basename("/tmp/file.txt")
sdk.path.dirname("/tmp/file.txt")
sdk.path.extname("file.txt")
sdk.path.resolve("/a", "b")
sdk.path.isAbsolute("/tmp/file.txt")
sdk.path.sep
```

### `sdk.events` {#sdk-events}

רשמו פונקציות קריאה חוזרת לאירועי Ogma. כל הפונקציות נקראות באופן סינכרוני בתוך סביבת QuickJS המבודדת.

```js
sdk.events.onInterceptRequest(function(req) {
  // req: RequestSpecRaw
  // Return a modified RequestSpecRaw to mutate the request.
  // Return null/undefined to pass through unchanged.
});

sdk.events.onInterceptResponse(function(req, res) {
  // req: Request (read-only), res: Response (read-only)
  // Return value is ignored.
});

sdk.events.onProjectChange(function() {
  // no callback args
});

sdk.events.onFindingCreated(function(finding) {
  // finding: { id, title, reporter }
});
```

### `sdk.requests` {#sdk-requests}

```js
// Get a single HTTP entry by id
var entry = sdk.requests.get("entry-id");
// entry: { id, method, host, path, query, tls, ... } or null

// Search HTTP history
var results = sdk.requests.search({ limit: 20, offset: 0 });
// results: { entries: [...], total: N }

// Send an HTTP request (requires send_requests permission)
var response = await sdk.requests.send(spec);
// spec: RequestSpecRaw (see below)
// response: Response
```

`sdk.requests.send` דורש שהרשאת `send_requests` תוצהר במניפסט ותוענק בידי המשתמש. ראו [הרשאות](#permissions).

### `sdk.findings` {#sdk-findings}

```js
// Create a finding (requires write_findings permission)
sdk.findings.create({
  title: "SSRF via redirect",
  reporter: "my-plugin",
  dedupeKey: "ssrf-" + request.getId(),
  request: { id: request.getId() }
});

// Check if a finding already exists (dedup check)
var exists = sdk.findings.exists({ dedupeKey: "ssrf-abc" });

// List findings
var page = sdk.findings.list({ limit: 20, offset: 0 });

// Get a single finding
var finding = sdk.findings.get("finding-id");
```

מגבלות קצב על `sdk.findings.create`: ‏10 בדקה, 500 בכל הפעלת תוסף, 3 בכל פונקציית קריאה חוזרת לאירוע.

### `sdk.api` {#sdk-api}

רשמו פונקציות RPC בצד השרת שצד הלקוח יכול לקרוא להן דרך `sdk.backend.*`:

```js
// In backend init:
sdk.api.register("getScans", function(scanId) {
  return { scans: [] };
});

// Emit an event to connected frontends:
sdk.api.send("scan:complete", { scanId: 1, status: "ok" });
```

פונקציית הטיפול מקבלת ארגומנטים שהועברו מצד הלקוח (לא מוזרק ארגומנט `sdk` נוסף). הערכים המוחזרים עוברים סריאליזציה ל-JSON ונשלחים בחזרה לקורא.

צד הלקוח קורא להן דרך `sdk.backend.getScans(scanId)` - ראו [API של תוספי צד לקוח](#frontend-plugin-api-sdk).

`sdk.api.send` דוחף אירועים לתור ייעודי לכל תוסף (עד 200 רשומות). צדדי הלקוח מתשאלים את התור במחזוריות דרך `sdk.backend.onEvent`.

### `sdk.replay`, `sdk.projects`, `sdk.scope`, `sdk.workflows`, `sdk.matchReplace` {#sdk-replay-sdk-projects-sdk-scope-sdk-workflows-sdk-matchreplace}

אלה מרחבי שמות לתשאול בקריאה בלבד. ראו [מדריך העזר ל-SDK של צד השרת](./backend-sdk.md) לחתימות המתודות המלאות.

### מחלקות בקשה {#request-classes}

**`RequestSpecRaw`** - מייצגת בקשה מיורטת. אתם מקבלים אותה ב-`onInterceptRequest`.

```js
spec.getMethod()          // > string
spec.setMethod("POST")
spec.getHost()            // > string
spec.setHost("example.com")
spec.getPort()            // > number
spec.getPath()            // > string
spec.setPath("/new/path")
spec.getQuery()           // > string
spec.getTls()             // > boolean
spec.getHeaders()         // > Record<string, string[]>
spec.setHeader("X-Foo", "bar")
spec.getBody()            // > Body | null
spec.setBody("new body")
spec.getRaw()             // > Uint8Array (raw bytes) or []
spec.setRaw(bytes)        // set raw bytes

// Create a new spec from a URL string:
var spec = new RequestSpecRaw("https://example.com/path?q=1");
```

**`Request`** - בקשה שנלכדה וניתנת לקריאה בלבד (מ-`sdk.requests.get`).

```js
req.getId()
req.getMethod()
req.getHost()
req.getPort()
req.getTls()
req.getPath()
req.getQuery()
req.getUrl()           // > full URL string
req.getHeaders()       // > Record<string, string>
req.getHeader("name")
req.getBody()          // > Body | null
req.getCreatedAt()     // > Date
req.toSpec()           // > RequestSpec (mutable copy)
```

**`Response`** - תשובה שנלכדה וניתנת לקריאה בלבד.

```js
res.getCode()            // > number (HTTP status)
res.getHeaders()         // > Record<string, string>
res.getHeader("name")
res.getBody()            // > Body | null
res.getRoundtripTime()   // > number (ms)
res.getCreatedAt()       // > Date
```

**`Body`**:

```js
body.toText()    // > string
body.toJson()    // > parsed object or null
body.toRaw()     // > Uint8Array
body.length      // > number (original size, may differ from toText() if truncated)
```

***

## API של תוספי צד לקוח (sdk) {#frontend-plugin-api-sdk}

קוד תוסף צד הלקוח רץ ב-iframe מבודד הנטען מ-`/plugins/{id}/ui`. ה-iframe משתמש ב-`postMessage` כדי לתקשר עם מארח Ogma, המתווך את הקריאות לצד השרת.

ה-SDK זמין דרך `window.ogmaSDK`. קראו ל-`ogmaSDK.ready(cb)` כדי לקבל את ה-SDK הפעיל לאחר הקמת גשר המארח:

```js
ogmaSDK.ready(function(sdk) {
  // sdk is the live SDK - safe to call any method here
  sdk.log.info("frontend ready");
});
```

כל מתודות ה-SDK מחזירות Promise.

### `sdk.log` {#sdk-log}

```js
sdk.log.info("message")
sdk.log.warn("message")
sdk.log.error("message")
```

### `sdk.meta` {#sdk-meta-1}

```js
var meta = await sdk.meta.get();
// { pluginId, packageId, name, version, ogmaVersion }
```

### `sdk.requests` {#sdk-requests-1}

```js
var entry = await sdk.requests.get({ id: "entry-id" });
var result = await sdk.requests.search({ limit: 20, offset: 0, query: "host:example.com" });
```

דורשת הרשאת `read_http_history` (מוענקת אוטומטית; אין צורך באישור המשתמש).

### `sdk.findings` {#sdk-findings-1}

```js
var page = await sdk.findings.list({ limit: 20, offset: 0 });
```

דורשת הרשאת `read_findings` (מוענקת אוטומטית).

### `sdk.scope` {#sdk-scope}

```js
var scope = await sdk.scope.getActive();
```

### `sdk.projects` {#sdk-projects}

```js
var project = await sdk.projects.getCurrent();
```

### `sdk.backend` - RPC בצד השרת {#sdk-backend-backend-rpc}

קראו לפונקציות שנרשמו באמצעות `sdk.api.register` בצד השרת:

```js
// Call a named backend function
var result = await sdk.backend.call("getScans", [scanId]);

// Poll for backend-emitted events (sdk.api.send on the backend side)
var { events, next_since } = await sdk.backend.poll(since);
// events: [{ event: "scan:complete", args: [...] }]

// Register an event listener (uses polling internally)
sdk.backend.onEvent("scan:complete", function(data) {
  console.log("scan done", data);
});
```

`sdk.backend.onEvent` משתמשת פנימית בלולאת תשאול במרווח של 2 שניות. הפסיקו להאזין באמצעות קריאה לפונקציית ביטול ההרשמה המוחזרת:

```js
var unsub = sdk.backend.onEvent("scan:complete", handler);
// later:
unsub();
```

### `sdk.navigation` {#sdk-navigation}

```js
await sdk.navigation.addPage("/my-plugin", { title: "My Plugin" });
```

רושמת עמוד ניווט. המארח מאשר אותו כעת. שילוב מלא עם הנתב נמצא בתהליך.

### `sdk.sidebar` {#sdk-sidebar}

```js
await sdk.sidebar.registerItem("My Plugin", "/my-plugin", { icon: "puzzle" });
```

רושמת רשומה בסרגל הצד. כרגע היא מקומית ללוח ממשק התוסף - חיבור למיקום בסרגל הצד הכללי נמצא בתהליך.

### `sdk.commands` {#sdk-commands}

```js
await sdk.commands.register("my-plugin:scan", {
  name: "Scan with My Plugin",
  handler: function(context) { /* ... */ }
});
```

מאושרת בידי המארח. שילוב עם חלונית הפקודות נמצא בתהליך.

### `sdk.menu` {#sdk-menu}

```js
await sdk.menu.registerItem({
  type: "Request",
  commandId: "my-plugin:scan",
  leadingIcon: "shield"
});
```

מאושרת בידי המארח. הוספה לתפריט ההקשר נמצאת בתהליך.

### `sdk.window` {#sdk-window}

```js
sdk.window.showToast("Scan complete", { variant: "success", duration: 3000 });
```

מציגה התראה זמנית בלוח התוסף. סוגים: `info`,‏ `success`,‏ `warning`,‏ `error`.

### `sdk.ui` {#sdk-ui}

```js
sdk.ui.resize(600);                              // request iframe height change
sdk.ui.sidebar.registerItem("name", "/path");    // alias for sdk.sidebar.registerItem
```

***

## הרשאות {#permissions}

הצהירו על הרשאות ב-`manifest.json`:

```json
{
  "permissions": ["send_requests", "write_findings"]
}
```

### הרשאות המוענקות אוטומטית (ללא צורך באישור המשתמש) {#auto-granted-permissions-no-user-approval-needed}

הרשאות אלה מוענקות תמיד לכל תוסף מותקן:

| הרשאה | מה היא מאפשרת |
|------------|----------------|
| `read_http_history` | `sdk.requests.get`, `sdk.requests.search` |
| `read_findings` | `sdk.findings.get`, `sdk.findings.list` |
| `read_scope` | `sdk.scope.getActive` |
| `read_projects` | `sdk.projects.getCurrent`, `sdk.projects.list` |
| `plugin_storage` | `sdk.storage`, `sdk.fs`, `sdk.path` |

### הרשאות מוגנות (דורשות אישור משתמש) {#protected-permissions-require-user-approval}

יש להצהיר עליהן במניפסט, והמשתמש חייב להעניק אותן במפורש מלשונית הרשאות:

| הרשאה | מה היא מאפשרת |
|------------|----------------|
| `send_requests` | `sdk.requests.send` - ביצוע בקשות HTTP יוצאות |
| `write_findings` | `sdk.findings.create`, `sdk.findings.update` |

המשתמש רואה בקשת אישור בעת הפעלת תוסף שמצהיר על הרשאות מוגנות. ניתן גם להעניק או לבטל הרשאות בכל עת מלשונית הרשאות.

***

## מבנה חבילת תוסף {#plugin-package-structure}

ניתן להתקין חבילת תוסף מתיקייה מקומית וגם מקובצי ייצוא `.zip` בתהליכי עבודה בדפדפן.

```
my-plugin/
  manifest.json           - required
  backend/
    script.js             - bundled backend JS (ES2020)
  frontend/
    script.js             - bundled frontend JS
    style.css             - optional CSS
    assets/               - static assets (images, fonts, etc.)
```

### דרישות סקריפט צד השרת {#backend-script-requirements}

* חייב להיות קובץ JS יחיד ועצמאי.
* `require()` ו-`import()` דינמי אינם נתמכים.
* חייב לייצא פונקציה `init(sdk)` (או להגדיר אותה כגלובלית).
* תת-קבוצה של ES2020 הנתמכת ב-QuickJS: ‏`async/await`,‏ `Promise`,‏ `Map`,‏ `Set`,‏ `Symbol`,‏ `Proxy`,‏ `Date`,‏ `RegExp`,‏ `JSON`. ללא `fetch`, ללא `Buffer`.
* ייבואים סטטיים נתמכים נפתרים בעיבוד המקדים של התוסף: `@ogma/sdk`,‏ `crypto`,‏ `fs`,‏ `path`.
* גודל הקובץ המרבי: 256 KB.

### דרישות סקריפט צד הלקוח {#frontend-script-requirements}

* רץ בתוך iframe מבודד. `connect-src: 'self'` מותר כדי שהתוסף יוכל לשלוח POST אל `/plugins/{id}/api/*` ולתשאל במחזוריות את `/plugins/{id}/events/poll`.
* מדיניות CSP: ‏`default-src 'none'; script-src 'nonce-...'; style-src 'self'; img-src data: blob: 'self'; connect-src 'self'`.
* אין `allow-same-origin` בסביבת הבידוד של ה-iframe - התוסף אינו יכול לגשת ל-DOM של הורה Ogma או לעוגיות.
* השתמשו ב-`ogmaSDK.ready(cb)` כדי לגשת ל-SDK; אל תקראו למתודות SDK לפני שפונקציית הקריאה החוזרת מופעלת.

***

## בניית תוסף ל-Ogma {#building-a-plugin-for-ogma}

מכיוון שצד השרת חייב להיות קובץ JS מאוגד יחיד, עליכם לאגד את קוד המקור שלכם ב-TypeScript או במודולי ES לפני ההתקנה.

שרשרת הכלים המומלצת:

```bash
# Install dependencies
pnpm install

# Bundle backend (outputs a single CJS/IIFE file):
esbuild packages/backend/src/index.ts \
  --bundle \
  --platform=neutral \
  --format=iife \
  --global-name=_plugin \
  --outfile=dist/backend/script.js \
  --external:@ogma/sdk --external:@ogmabox/ogma-sdk

# Bundle frontend:
vite build packages/frontend --outDir ../../dist/frontend
```

אם אתם משתמשים בשרשרת כלי הפיתוח של Caido‏ (`@caido-community/dev`), הריצו `caido-dev build` ואז העתיקו את הפלט למבנה תואם Ogma עם `manifest.json` בשורש.

***

## התקנת תוסף {#installing-a-plugin}

1. פתחו את **תוספים** בסרגל הצד השמאלי.
2. לחצו על **התקנה** (בחלק העליון של לשונית **מותקנים**).
3. ביישום שולחן העבודה, לחצו על **עיון** כדי לפתוח בורר תיקיות מקורי של המערכת. בדפדפן, הקלידו את הנתיב המלא בצד השרת לתיקיית התוסף.
4. לחצו על **בדיקת תקינות** כדי לבדוק את המניפסט ומצאי הקבצים.
5. לחצו על **התקנה** אם הבדיקה עברה בהצלחה.
6. בחרו את התוסף ברשימה ולחצו על **הפעלה**.
7. אם התוסף מצהיר על הרשאות מוגנות, בדקו והעניקו אותן מלשונית **הרשאות** לפני ההפעלה.

***

## פתרון בעיות {#troubleshooting}

**אתחול התוסף נכשל ללא הודעת שגיאה:** בדקו את לשונית **יומנים**. הסיבות הנפוצות ביותר:

* נקראה `sdk.meta.path()` אך לא ניתן היה ליצור את תיקיית הנתונים.
* חריגה שלא טופלה ב-`init()`.
* קריאת `sdk.*` חסרה או מאויתת באופן שגוי.

**צד הלקוח מוצג ריק:** בדקו את מסוף הדפדפן לחיפוש הפרות CSP. ודאו שסקריפט צד הלקוח קורא ל-`ogmaSDK.ready(cb)` לפני גישה למתודת SDK כלשהי.

**`sdk.requests.send` זורקת `Permission denied`:** יש להצהיר על הרשאת `send_requests` במניפסט וגם לקבל את הענקתה בידי המשתמש בלשונית הרשאות.

**לא ניתן לקרוא לפונקציות `sdk.api.register` מצד הלקוח:** צד השרת חייב להיות מופעל (לא רק מותקן). שם הפונקציה חייב להתאים בדיוק (רגיש לאותיות גדולות וקטנות) למה שצד הלקוח מעביר אל `sdk.backend.call`.

**אזהרות תאימות מוצגות כשגיאות:** הן אינן חוסמות פעולה, אך מצביעות על פערים בכיסוי ה-API. ראו את טבלאות המיפוי של ה-SDK לעיל למידע על כיסוי ה-API.
