# ארגונים וסביבות עבודה

> ניהול ארגונים וסביבות עבודה של Nodaro מ-TypeScript: יצירת בתי ספר וצוותים, הזמנת חברים, חלוקת קודי הצטרפות וקריאת דוחות על השימוש בקרדיטים.

Source: https://nodaro.ai/he/docs/developers/sdk/organizations

**`client.organizations`** מנהל ארגונים, כלומר בתי ספר או צוותים, עם החברים, ההזמנות, יומן הביקורת ודוחות השימוש שלהם. המשאב **`client.workspaces`** מנהל את סביבות העבודה שבתוכם, כגון כיתה או פרויקט של צוות, עם החברים וקודי ההצטרפות שלהן. המתודות קוראות לנקודות הקצה שבדף ה-API [סביבות עבודה וארגונים](https://nodaro.ai/docs/developers/api/workspaces). המושגים מוסברים בדף [סביבות עבודה](https://nodaro.ai/docs/concepts/workspaces).

ארגונים הם יכולת של Nodaro Cloud, שמופעלת בנפרד בכל מופע. במופע שבו הם לא מופעלים, כל מתודה בדף הזה זורקת `NotFoundError`.

## השתייכות ופעולה
השתייכות לסביבת עבודה ופעולה בתוכה הן שני דברים שונים. שני המשאבים האלה מנהלים מי שייך לאן. כדי **לפעול** בסביבת עבודה, כך שהרשימות יציגו את מה שיש בה ועבודה חדשה תיכנס אליה, צרו לקוח עם [`client.withWorkspace(workspaceId)`](https://nodaro.ai/docs/developers/sdk/client#withworkspaceworkspaceid) או העבירו את `workspaceId` לפונקציה `createClient`:

```ts
const classroom = client.withWorkspace(workspaceId)
await classroom.workflows.run(workflowId) // runs in the class
await client.workflows.run(workflowId)    // runs in your personal space
```

המתודה [`client.me()`](https://nodaro.ai/docs/developers/sdk/client#me) מחזירה את הארגונים ואת סביבות העבודה שאתם שייכים אליהם, ואת `lastWorkspaceId`.

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

## מתודות
| מתודה | מה היא עושה |
| --- | --- |
| [`organizations.list()`](#organizationslist) | רשימת הארגונים שאתם שייכים אליהם |
| [`organizations.get(id)`](#organizationsgetid) | קריאת ארגון אחד |
| [`organizations.create(input)`](#organizationscreateinput) | יצירת בית ספר או צוות |
| [`organizations.update(id, input)`](#organizationsupdateid-input) | שינוי השם או ההגדרות של ארגון |
| [`organizations.delete(id)`](#organizationsdeleteid) | מחיקת ארגון |
| [`organizations.transferOwnership(id, userId)`](#organizationstransferownershipid-userid) | העברת הבעלות לחבר אחר |
| [`organizations.leave(id)`](#organizationsleaveid) | עזיבת ארגון |
| [`organizations.listMembers()`, `updateMember()`, `removeMember()`](#organization-members) | ניהול החברים |
| [`organizations.invite(orgId, input)`](#organizationsinviteorgid-input) | הזמנת אנשים באימייל |
| [`organizations.listInvitations()`, `revokeInvitation()`, `resendInvitation()`](#manage-invitations) | ניהול ההזמנות שנשלחו |
| [`organizations.previewInvitation()`, `acceptInvitation()`](#preview-and-accept-an-invitation) | הצגה וקבלה של הזמנה |
| [`organizations.audit(orgId, opts?)`](#organizationsauditorgid-opts) | קריאת יומן הביקורת |
| [`organizations.usage()`, `usageRows()`, `usageCsv()`](#organization-usage) | קריאת השימוש בקרדיטים |
| [`workspaces.list()`](#workspaceslist) | רשימת סביבות העבודה שאתם שייכים אליהן |
| [`workspaces.listForOrg(orgId, opts?)`](#workspaceslistfororgorgid-opts) | רשימת סביבות העבודה של ארגון |
| [`workspaces.get(id)`](#workspacesgetid) | קריאת סביבת עבודה אחת |
| [`workspaces.create(orgId, input)`](#workspacescreateorgid-input) | יצירת סביבת עבודה |
| [`workspaces.update(id, input)`](#workspacesupdateid-input) | שינוי סביבת עבודה |
| [`workspaces.setArchived(id, archived)`](#workspacessetarchivedid-archived) | העברה של סביבת עבודה לארכיון או הוצאה שלה מהארכיון |
| [`workspaces.listMembers()`, `addMember()`, `updateMember()`, `removeMember()`](#workspace-members) | ניהול החברים בסביבת עבודה |
| [`workspaces.getJoinCode()`, `actOnJoinCode()`](#join-codes) | ניהול קוד ההצטרפות |
| [`workspaces.join(code)`](#workspacesjoincode) | הצטרפות לסביבת עבודה עם קוד |
| [`workspaces.usage()`, `usageRows()`, `usageCsv()`](#workspace-usage) | קריאת השימוש של סביבת עבודה בקרדיטים |

## client.organizations
### organizations.list()‎
מחזירה את רשימת הארגונים שאתם שייכים אליהם (`GET /v1/orgs`).

```ts
list(): Promise<{ data: OrganizationView[] }>
```

```ts
const { data: orgs } = await client.organizations.list()
```

### organizations.get(id)‎
מחזירה ארגון אחד (`GET /v1/orgs/:id`).

```ts
get(id: string): Promise<{ data: OrganizationView }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה הארגון." },
}}
/>

```ts
const { data: org } = await client.organizations.get(orgId)
console.log(org.status) // pending, active, suspended or deleted
```

### organizations.create(input)‎
יוצרת ארגון, ואתם הבעלים שלו (`POST /v1/orgs`). ארגון חדש מתחיל בדרך כלל בסטטוס `pending`, ועובר ל-`active` אחרי ש-Nodaro מאשר אותו. כל עוד הוא ממתין לאישור, רק אתם יכולים לראות אותו ואי אפשר לשנות בו שום דבר, ולכן הודיעו למשתמש שהארגון ממתין לאישור.

```ts
create(input: { name: string; kind: "school" | "team"; slug?: string; acceptTerms?: boolean; settings?: OrgSettings }): Promise<{ data: OrganizationView }>
```

<TypeTable
type={{
name: { type: 'string', required: true, description: "שם הארגון." },
kind: { type: '"school" | "team"', required: true, description: "סוג הארגון. הסוג קובע את הגדרות ברירת המחדל." },
slug: { type: 'string', description: "שם קצר וייחודי. בקשה עם slug שכבר תפוס נכשלת עם 409 name_taken." },
acceptTerms: { type: 'boolean', description: "נדרש לבית ספר: הבעלים מאשר שיש לו סמכות לרשום תלמידים." },
settings: { type: 'OrgSettings', description: "הגדרות שחלות על כל סביבות העבודה בארגון." },
}}
/>

```ts
const { data: school } = await client.organizations.create({
name: "Sunrise School",
kind: "school",
acceptTerms: true,
})
```

יצירה של בית ספר בלי `acceptTerms: true` נכשלת עם `400 terms_required`.

### organizations.update(id, input)‎
משנה את השם או את ההגדרות של ארגון (`PATCH /v1/orgs/:id`).

```ts
update(id: string, input: { name?: string; settings?: OrgSettings }): Promise<{ data: OrganizationView }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה הארגון." },
name: { type: 'string', description: "שם חדש." },
settings: { type: 'OrgSettings', description: "ההגדרות לשינוי. ראו את טבלת ההגדרות למטה." },
}}
/>

```ts
await client.organizations.update(orgId, {
settings: { default_workflow_visibility: "workspace", allowed_email_domains: ["school.example"] },
})
```

ההגדרות בנויות בשכבות: הגדרה של סביבת עבודה גוברת על ההגדרה של הארגון, שגוברת על ברירת המחדל של סוג הארגון. הערך `false` במפתח הוא ערך לכל דבר, ולא ״לא מוגדר״.

| מפתח | סוג | משמעות |
| --- | --- | --- |
| `admin_access` | `view` או `edit` | מה מנהלים רשאים לעשות עם תהליך של חבר |
| `default_workflow_visibility` | `private` או `workspace` | הנראות של תהליך חדש |
| `member_access_to_shared` | `view` או `edit` | מה חברים רשאים לעשות עם תהליך ששותף לסביבת העבודה |
| `members_can_create_projects` | boolean | אם חברים רשאים ליצור פרויקטים בסביבת העבודה |
| `member_caps_enabled` | boolean | אם חלות תקרות קרדיטים לכל חבר |
| `personal_space_enabled` | boolean | אם לחברים נשאר מרחב אישי |
| `workspace_admins_can_invite` | boolean | אם מנהלי סביבות עבודה רשאים להזמין אנשים חדשים |
| `collaborators_can_invite` | boolean | אם שותף עם הרשאת עריכה רשאי להזמין שותפים נוספים |
| `allowed_email_domains` | רשימת מחרוזות | לארגון בלבד: דומייני האימייל שמורשים להצטרף |
| `vocabulary_overrides` | object | לארגון בלבד: תוויות חדשות במקום המילים של הסוג, כגון `{ "workspace": "Cohort" }`. הוסיפו `"workspace_gender": "m"` כשהמילה החדשה לסביבת עבודה היא בלשון זכר, כדי שמשפטים בעברית ובפורטוגזית (ברזיל) יתאימו את עצמם למין הדקדוקי שלה |

שני המפתחות שקיימים רק בארגון מוחלפים במלואם כשמעדכנים אותם.

### organizations.delete(id)‎
מוחקת ארגון (`DELETE /v1/orgs/:id`). הארגון מוסתר מהחברים שהיו בו, אבל שום נתון לא נמחק לצמיתות. קודם העבירו לארכיון את כל סביבות העבודה שלו: אחרת הקריאה נכשלת עם `409 has_active_workspaces`.

```ts
delete(id: string): Promise<{ data: { id: string; status: string } }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה הארגון." },
}}
/>

```ts
await client.organizations.delete(orgId)
```

### organizations.transferOwnership(id, userId)‎
הופכת חבר אחר לבעלים (`POST /v1/orgs/:id/transfer-ownership`). אתם הופכים למנהלים.

```ts
transferOwnership(id: string, userId: string): Promise<{ data: { orgId: string; ownerUserId: string } }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה הארגון." },
userId: { type: 'string', required: true, description: "החבר שהופך לבעלים." },
}}
/>

```ts
await client.organizations.transferOwnership(orgId, newOwnerId)
```

### organizations.leave(id)‎
מוציאה אתכם מארגון (`POST /v1/orgs/:id/leave`). הבעלים לא יכול לעזוב: העבירו קודם את הבעלות, אחרת הקריאה נכשלת עם `409 owner_cannot_leave`.

```ts
leave(id: string): Promise<{ data: { orgId: string; left: boolean } }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה הארגון." },
}}
/>

```ts
await client.organizations.leave(orgId)
```

### חברי הארגון
המתודות האלה מחזירות את רשימת החברים בארגון, משנות חברים ומסירות אותם (`/v1/orgs/:id/members`). הסטטוס של חבר הוא `active` או `suspended`; חבר מושעה שומר על המקום שלו בארגון, אבל לא יכול לפעול. אי אפשר להשעות את הבעלים, להסיר אותו או להוריד את התפקיד שלו.

```ts
listMembers(orgId: string, opts?: { cursor?: string; limit?: number }): Promise<OrgPage<OrgMemberView>>
updateMember(orgId: string, userId: string, input: { role?: "admin" | "member"; status?: "active" | "suspended" }): Promise<{ data: OrgMemberView }>
removeMember(orgId: string, userId: string): Promise<{ data: { removed: boolean } }>
```

<TypeTable
type={{
orgId: { type: 'string', required: true, description: "מזהה הארגון." },
userId: { type: 'string', description: "החבר. נדרש ב-updateMember וב-removeMember." },
cursor: { type: 'string', description: "ב-listMembers: הערך nextCursor מהעמוד הקודם." },
limit: { type: 'number', description: "ב-listMembers: גודל העמוד." },
role: { type: '"admin" | "member"', description: "ב-updateMember: התפקיד החדש." },
status: { type: '"active" | "suspended"', description: "ב-updateMember: השעיה של החבר או החזרה שלו לפעילות." },
}}
/>

```ts
const { data: members, nextCursor } = await client.organizations.listMembers(orgId)
await client.organizations.updateMember(orgId, userId, { role: "admin" })
```

### organizations.invite(orgId, input)‎
מזמינה אנשים באימייל (`POST /v1/orgs/:id/invitations`). המתודה מחזירה **שורה אחת לכל כתובת**.

```ts
invite(orgId: string, input: {
emails: string[]
orgRole?: "admin" | "member"
workspaceId?: string
workspaceRole?: "admin" | "member"
}): Promise<{ data: InvitationDelivery[] }>
```

<TypeTable
type={{
orgId: { type: 'string', required: true, description: "מזהה הארגון." },
emails: { type: 'string[]', required: true, description: "הכתובות להזמנה." },
orgRole: { type: '"admin" | "member"', description: "התפקיד בארגון." },
workspaceId: { type: 'string', description: "סביבת עבודה שהאנשים יתווספו אליה כשהם מקבלים את ההזמנה." },
workspaceRole: { type: '"admin" | "member"', description: "התפקיד שלהם בסביבת העבודה הזו." },
}}
/>

```ts
const { data: rows } = await client.organizations.invite(orgId, {
emails: ["teacher@school.example"],
workspaceId,
workspaceRole: "admin",
})
for (const row of rows) {
if (row.status !== "sent") showInviteLink(row.email, row.link) // not emailed: share the link yourself
}
```

**הציגו את הקישור.** בשורה שה-`status` שלה שונה מ-`sent` יש במקום זאת `link`: בהתקנה אין שירות אימייל, או ששליחת האימייל נכשלה. ההזמנה קיימת בכל מקרה, ובלי הקישור אף אחד לא יכול להגיע אליה. תוקף ההזמנה פג אחרי 14 ימים. כשהארגון מגיע למגבלת ההזמנות היומית שלו, הקריאה נכשלת עם `RateLimitedError`.

### ניהול הזמנות
המתודות האלה מחזירות את רשימת ההזמנות, מבטלות הזמנות ושולחות אותן שוב.

```ts
listInvitations(orgId: string, opts?: { status?: "open" | "accepted" | "revoked" | "expired"; workspaceId?: string; cursor?: string; limit?: number }): Promise<OrgPage<InvitationView>>
revokeInvitation(id: string): Promise<{ data: { id: string; revoked: boolean } }>
resendInvitation(id: string): Promise<{ data: InvitationDelivery & { id: string } }>
```

<TypeTable
type={{
orgId: { type: 'string', description: "ב-listInvitations: מזהה הארגון." },
id: { type: 'string', description: "ב-revokeInvitation וב-resendInvitation: מזהה ההזמנה." },
status: { type: '"open" | "accepted" | "revoked" | "expired"', description: "ב-listInvitations: רק הזמנות במצב הזה." },
workspaceId: { type: 'string', description: "ב-listInvitations: רק הזמנות לסביבת העבודה הזו." },
cursor: { type: 'string', description: "ב-listInvitations: הערך nextCursor מהעמוד הקודם." },
limit: { type: 'number', description: "ב-listInvitations: גודל העמוד." },
}}
/>

```ts
const { data: open } = await client.organizations.listInvitations(orgId, { status: "open" })
await client.organizations.resendInvitation(open[0].id)
```

### תצוגה מקדימה וקבלה של הזמנה
המתודה `previewInvitation()` היא **ציבורית**: היא עובדת גם כשהמוזמן עדיין לא מחובר, ומחזירה את כתובת האימייל כשחלק ממנה מוסתר. המתודה `acceptInvitation()` דורשת שמי שקורא לה יהיה מחובר, עם אימייל שתואם להזמנה.

```ts
previewInvitation(token: string): Promise<{ data: InvitationPreview }>
acceptInvitation(token: string): Promise<{ data: { orgId: string; workspaceId: string | null } }>
```

<TypeTable
type={{
token: { type: 'string', required: true, description: "האסימון מקישור ההזמנה." },
}}
/>

```ts
const { data: preview } = await client.organizations.previewInvitation(token)
const { data: joined } = await signedInClient.organizations.acceptInvitation(token)
```

קבלת ההזמנה נכשלת עם `400 invitation_expired`, `invitation_revoked`, `invitation_accepted` או `email_mismatch`, ועם `ForbiddenError` כשהארגון מקבל רק כתובות אימייל מהדומיינים שברשימה שלו.

### organizations.audit(orgId, opts?)‎
מחזירה את יומן הביקורת של הארגון, מהחדש לישן (`GET /v1/orgs/:id/audit`). אפשר לקרוא את היומן גם כשהארגון מושעה.

```ts
audit(orgId: string, opts?: { cursor?: string; limit?: number }): Promise<OrgPage<OrgAuditEntry>>
```

<TypeTable
type={{
orgId: { type: 'string', required: true, description: "מזהה הארגון." },
cursor: { type: 'string', description: "הערך nextCursor מהעמוד הקודם." },
limit: { type: 'number', description: "גודל העמוד." },
}}
/>

```ts
const { data: entries } = await client.organizations.audit(orgId, { limit: 50 })
for (const e of entries) console.log(e.createdAt, e.action, e.actor?.displayName ?? "system")
```

בכל רשומה יש `id`, `workspaceId`, `action`, `targetType`, `targetId`, `details`, `createdAt` ו-`actor`, שהוא `null` בפעולות שהמערכת ביצעה. השדה `action` הוא רשימה **פתוחה**: הציגו את הפעולות שאתם מכירים, ואת המחרוזת הגולמית בשאר המקרים. קוד שמטפל רק ברשימה קבועה נשבר בפעולה החדשה הראשונה.

### נתוני השימוש של הארגון
דוחות על השימוש בקרדיטים בטווח תאריכים, לבעלים ולמנהלי הארגון (`GET /v1/orgs/:id/usage`). המתודה `usage()` מחזירה דוח מקובץ, המתודה `usageRows()` מחזירה את ההרצות שמאחוריו, מהחדש לישן, עמוד אחד בכל פעם, והמתודה `usageCsv()` מחזירה את הדוח או את השורות כטקסט CSV.

```ts
usage(orgId: string, opts?: { from?: string; to?: string; tz?: string; groupBy?: "workspace" | "member" | "model" | "day"; workspaceId?: string; userId?: string }): Promise<{ data: UsageReport }>
usageRows(orgId: string, opts?: { from?: string; to?: string; tz?: string; workspaceId?: string; userId?: string; cursor?: string; limit?: number }): Promise<OrgPage<UsageLogEntry>>
usageCsv(orgId: string, opts?: { from?: string; to?: string; tz?: string; groupBy?: string; workspaceId?: string; userId?: string }): Promise<string>
```

<TypeTable
type={{
orgId: { type: 'string', required: true, description: "מזהה הארגון." },
from: { type: 'string', description: "היום הראשון בטווח, כולל." },
to: { type: 'string', description: "היום האחרון בטווח, כולל. טווח יכול להימשך 366 ימים לכל היותר." },
tz: { type: 'string', description: "אזור זמן של IANA לחלוקה לימים, כגון Europe/Rome." },
groupBy: { type: '"workspace" | "member" | "model" | "day"', description: "ב-usage וב-usageCsv: לפי מה לקבץ את הדוח." },
workspaceId: { type: 'string', description: "סביבת עבודה אחת בלבד." },
userId: { type: 'string', description: "חבר אחד בלבד." },
cursor: { type: 'string', description: "ב-usageRows: הערך nextCursor מהעמוד הקודם." },
limit: { type: 'number', description: "ב-usageRows: גודל העמוד." },
}}
/>

```ts
const { data: report } = await client.organizations.usage(orgId, {
from: "2026-09-01",
to: "2026-09-30",
tz: "Europe/Rome",
groupBy: "workspace",
})
const csv = await client.organizations.usageCsv(orgId, { from: "2026-09-01", to: "2026-09-30" })
```

כל שורה מדווחת על שלושה נתוני קרדיטים. השדה `credits` הוא מה שההרצות עלו עד עכשיו: הסכום אחרי החיוב הסופי בהרצות שהסתיימו, והשריון בשאר ההרצות. השדות `settledCredits` ו-`inFlightCredits` מפצלים את הסכום הזה לשני חלקיו. הסיכומים מכסים את כל הטווח, גם כשקיבוץ מסוים הוא `truncated`.

- השדה `platformAbsorbedCredits` הוא החריגה של הרצה שמחויבת לפי שימוש ועלתה יותר ממה שנשאר בתקציב של סביבת העבודה. את החריגה הזו הפלטפורמה סופגת.
- השדה `chargedToBudget` שווה ל-`settledCredits` פחות `platformAbsorbedCredits`: הקרדיטים שנגבו בחיוב הסופי וירדו מהתקציב.
- השדה `appMarkupAbsorbedCredits` מסכם את תוספות המחיר של אפליקציות מאושרות שהפלטפורמה ספגה, בתקופה שבה תוספת המחיר של אפליקציה עוד לא נגבתה רק עד גובה היתרה הפנויה בתקציב. הרצות חדשות כבר לא מוסיפות לו: היוצר מקבל רק את מה שהתקציב שילם. אין לו הרצה בדוח, ולכן הוא לא חלק מ-`chargedToBudget`. מכאן שהשדה `chargedToBudget` לבדו הוא לא הסכום שסביבת העבודה הוציאה.

ייצוא לקובץ CSV מוגבל ל-10 ייצואים בדקה לכל משתמש.

## client.workspaces
### workspaces.list()‎
מחזירה את רשימת סביבות העבודה שאתם שייכים אליהן, ואת ה-`lastWorkspaceId` שלכם (`GET /v1/workspaces`). המתודה מחזירה סיכומים: אותה רשימה שהמתודה `client.me()` מחזירה.

```ts
list(): Promise<{ data: WorkspaceSummary[]; lastWorkspaceId: string | null }>
```

```ts
const { data: workspaces, lastWorkspaceId } = await client.workspaces.list()
```

### workspaces.listForOrg(orgId, opts?)‎
מחזירה את רשימת סביבות העבודה של ארגון אחד (`GET /v1/orgs/:id/workspaces`).

```ts
listForOrg(orgId: string, opts?: { includeArchived?: boolean }): Promise<{ data: WorkspaceView[] }>
```

<TypeTable
type={{
orgId: { type: 'string', required: true, description: "מזהה הארגון." },
includeArchived: { type: 'boolean', default: 'false', description: "לכלול גם סביבות עבודה שבארכיון." },
}}
/>

```ts
const { data: classes } = await client.workspaces.listForOrg(orgId)
```

### workspaces.get(id)‎
מחזירה סביבת עבודה אחת במלואה (`GET /v1/workspaces/:id`).

```ts
get(id: string): Promise<{ data: WorkspaceView }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה סביבת העבודה." },
}}
/>

```ts
const { data: workspace } = await client.workspaces.get(workspaceId)
```

### workspaces.create(orgId, input)‎
יוצרת סביבת עבודה בארגון (`POST /v1/orgs/:id/workspaces`). הארגון חייב להיות פעיל.

```ts
create(orgId: string, input: { name: string; slug?: string; description?: string; settings?: WorkspaceSettings }): Promise<{ data: WorkspaceView }>
```

<TypeTable
type={{
orgId: { type: 'string', required: true, description: "מזהה הארגון." },
name: { type: 'string', required: true, description: "שם סביבת העבודה." },
slug: { type: 'string', description: "שם קצר וייחודי." },
description: { type: 'string', description: "תיאור." },
settings: { type: 'WorkspaceSettings', description: "הגדרות שגוברות על ההגדרות של הארגון." },
}}
/>

```ts
const { data: classOne } = await client.workspaces.create(orgId, { name: "Class 1" })
```

### workspaces.update(id, input)‎
משנה סביבת עבודה (`PATCH /v1/workspaces/:id`).

```ts
update(id: string, input: { name?: string; description?: string | null; settings?: WorkspaceSettings }): Promise<{ data: WorkspaceView }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה סביבת העבודה." },
name: { type: 'string', description: "שם חדש." },
description: { type: 'string | null', description: "תיאור חדש, או null כדי להסיר אותו." },
settings: { type: 'WorkspaceSettings', description: "ההגדרות לשינוי." },
}}
/>

```ts
await client.workspaces.update(workspaceId, { settings: { members_can_create_projects: true } })
```

### workspaces.setArchived(id, archived)‎
מעבירה סביבת עבודה לארכיון או מוציאה אותה מהארכיון (`POST /v1/workspaces/:id/archive` או `/unarchive`). אפשר לבטל העברה לארכיון, והיא לא מוחקת כלום: סביבת העבודה מפסיקה לקבל עבודה חדשה, ונשארת זמינה לקריאה במלואה. כתיבה לסביבת עבודה שבארכיון נכשלת עם `ForbiddenError`.

```ts
setArchived(id: string, archived: boolean): Promise<{ data: WorkspaceView }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה סביבת העבודה." },
archived: { type: 'boolean', required: true, description: "הערך true מעביר לארכיון, והערך false מוציא מהארכיון." },
}}
/>

```ts
await client.workspaces.setArchived(workspaceId, true)
```

### חברי סביבת העבודה
המתודות האלה מחזירות את רשימת החברים בסביבת עבודה, מוסיפות חברים, משנות אותם ומסירות אותם (`/v1/workspaces/:id/members`). אפשר להוסיף רק אדם שכבר שייך לארגון; כדי לצרף מישהו חדש, הזמינו אותו. הבעלים והמנהלים של הארגון הם מנהלים בכל סביבת עבודה, גם בלי שהם מופיעים ברשימה.

```ts
listMembers(id: string, opts?: { cursor?: string; limit?: number }): Promise<OrgPage<WorkspaceMemberView>>
addMember(id: string, input: { userId: string; role: "admin" | "member" }): Promise<{ data: WorkspaceMemberView }>
updateMember(id: string, userId: string, input: { role?: "admin" | "member"; status?: "active" | "suspended"; creditCap?: number | null }): Promise<{ data: WorkspaceMemberView }>
removeMember(id: string, userId: string): Promise<{ data: { removed: boolean } }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה סביבת העבודה." },
userId: { type: 'string', description: "החבר. נדרש ב-addMember, ב-updateMember וב-removeMember." },
role: { type: '"admin" | "member"', description: "התפקיד בסביבת העבודה." },
status: { type: '"active" | "suspended"', description: "ב-updateMember: השעיה של החבר בסביבת העבודה הזו או החזרה שלו לפעילות." },
creditCap: { type: 'number | null', description: "ב-updateMember: תקרת קרדיטים לחבר הזה, או null בלי תקרה." },
cursor: { type: 'string', description: "ב-listMembers: הערך nextCursor מהעמוד הקודם." },
limit: { type: 'number', description: "ב-listMembers: גודל העמוד." },
}}
/>

```ts
await client.workspaces.addMember(workspaceId, { userId, role: "member" })
await client.workspaces.updateMember(workspaceId, userId, { creditCap: 500 })
```

הוספה של מי שהוא לא חבר פעיל בארגון נכשלת עם `400 not_org_member`, והוספה של אותו אדם פעמיים נכשלת עם `409 already_a_member`.

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

```ts
getJoinCode(id: string): Promise<{ data: JoinCodeView | null }>
actOnJoinCode(id: string, action: "rotate" | "enable" | "disable"): Promise<{ data: JoinCodeView }>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה סביבת העבודה." },
action: { type: '"rotate" | "enable" | "disable"', description: "ב-actOnJoinCode: הערך rotate יוצר קוד חדש ומשבית מיד את הקוד הישן. הערכים enable ו-disable מפעילים ומכבים את הקוד." },
}}
/>

```ts
const { data: code } = await client.workspaces.actOnJoinCode(workspaceId, "enable")
console.log(code.code) // for example BCDFGHJK
```

המתודה `getJoinCode()` מחזירה `null` כשעוד לא נוצר קוד.

### workspaces.join(code)‎
מצרפת אתכם לסביבת עבודה לפי קוד ההצטרפות שלה (`POST /v1/workspaces/join`). המתודה עובדת בלי קשר לסביבת העבודה שבה הלקוח שלכם פועל, ולכן בחירה שכבר לא תקפה אף פעם לא חוסמת אותה.

```ts
join(code: string): Promise<{ data: { orgId: string; workspaceId: string } }>
```

<TypeTable
type={{
code: { type: 'string', required: true, description: "קוד ההצטרפות." },
}}
/>

```ts
const { data: joined } = await client.workspaces.join("BCDFGHJK")
```

הצטרפות עם קוד לא מוכר או מושבת, או עם קוד של סביבת עבודה שבארכיון, נכשלת עם `400 join_code_invalid`: תשובה אחת לשלושת המקרים, כדי שקוד לא יחשוף מה קיים. אחרי יותר מדי ניסיונות, הקריאה נכשלת עם `RateLimitedError`.

### נתוני השימוש של סביבת העבודה
אותם דוחות קרדיטים כמו של הארגון, לסביבת עבודה אחת (`GET /v1/workspaces/:id/usage`). חבר רואה את ההרצות שלו; מנהל רואה את ההרצות של כולם, ויכול לסנן לפי `userId`.

```ts
usage(id: string, opts?: { from?: string; to?: string; tz?: string; groupBy?: "member" | "model" | "day"; userId?: string }): Promise<{ data: UsageReport }>
usageRows(id: string, opts?: { from?: string; to?: string; tz?: string; userId?: string; cursor?: string; limit?: number }): Promise<OrgPage<UsageLogEntry>>
usageCsv(id: string, opts?: { from?: string; to?: string; tz?: string; groupBy?: string; userId?: string }): Promise<string>
```

<TypeTable
type={{
id: { type: 'string', required: true, description: "מזהה סביבת העבודה." },
from: { type: 'string', description: "היום הראשון, כולל." },
to: { type: 'string', description: "היום האחרון, כולל." },
tz: { type: 'string', description: "אזור זמן של IANA." },
groupBy: { type: '"member" | "model" | "day"', description: "לפי מה לקבץ את הדוח." },
userId: { type: 'string', description: "למנהלים בלבד: חבר אחד." },
cursor: { type: 'string', description: "ב-usageRows: הערך nextCursor מהעמוד הקודם." },
limit: { type: 'number', description: "ב-usageRows: גודל העמוד." },
}}
/>

```ts
const { data: byModel } = await client.workspaces.usage(workspaceId, { groupBy: "model" })
```

## שגיאות
השרת מחליט על כל הרשאה, ועונה בשגיאה. ב-SDK, שגיאת 403 הופכת ל-`ForbiddenError`, שגיאת 404 ל-`NotFoundError` ושגיאת 429 ל-`RateLimitedError`; הסיבה מופיעה ב-`message` שלהן. סטטוסים אחרים מגיעים כ-`NodaroError`, עם ה-`code` של השרת.

| סטטוס | קוד | מתי |
| --- | --- | --- |
| 400 | `validation_error` | שדה לא תקין, cursor פגום או דוח שימוש שהתאריך, אזור הזמן או הטווח שלו לא תקינים |
| 400 | `terms_required` | בית ספר נוצר בלי `acceptTerms: true` |
| 400 | `not_org_member` | האדם שנוסף לסביבת עבודה הוא לא חבר פעיל בארגון שלה |
| 400 | `token_workspace_mismatch` | אסימון ששויך לסביבת עבודה אחת נשלח עם סביבת עבודה אחרת |
| 400 | `join_code_invalid` | קוד ההצטרפות לא קיים, מושבת או שייך לסביבת עבודה שבארכיון |
| 400 | `invitation_expired`, `invitation_revoked`, `invitation_accepted`, `email_mismatch` | אי אפשר לקבל את ההזמנה |
| 403 | | התפקיד נמוך מדי, החבר מושעה, הארגון לא פעיל, סביבת העבודה בארכיון או שהדומיין של האימייל לא מורשה |
| 404 | | הארגון, סביבת העבודה, החבר או ההזמנה לא קיימים, או שאתם לא חברים בהם |
| 409 | `name_taken` | ה-slug כבר בשימוש |
| 409 | `already_a_member` | האדם כבר נמצא בסביבת העבודה |
| 409 | `owner_cannot_leave` | הבעלים ניסה לעזוב |
| 409 | `has_active_workspaces` | בארגון עדיין יש סביבות עבודה שלא הועברו לארכיון |
| 429 | | יותר מדי ארגונים חדשים, ניסיונות הצטרפות, ייצואים לקובץ CSV או הזמנות |
| 503 | `billing_unavailable` | דוחות השימוש עדיין לא זמינים במופע הזה |
| 503 | `audit_unavailable` | אי אפשר היה לרשום ביומן הביקורת ייצוא לקובץ CSV. נסו שוב. |

## שאלות נפוצות

### מה ההבדל בין ארגון לסביבת עבודה?

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

### איך מריצים תהליך בתוך סביבת עבודה?

צרו לקוח לסביבת העבודה הזו עם client.withWorkspace(workspaceId)‎, וקראו דרכו למתודה run. הלקוח המקורי ממשיך לעבוד במרחב האישי שלכם.

### למה הארגון החדש שלי ממתין לאישור?

ארגון חדש מתחיל בסטטוס pending, ועובר ל-active אחרי ש-Nodaro מאשר אותו. עד אז רק הבעלים של הארגון יכול לראות אותו, ואי אפשר לשנות בו שום דבר.

### מה עושים כשהזמנה חוזרת עם קישור?

הציגו את הקישור למי ששלח את ההזמנה. שורה שהסטטוס שלה שונה מ-sent לא נשלחה באימייל, כי בהתקנה אין שירות אימייל או ששליחת האימייל נכשלה. ההזמנה קיימת, והקישור הוא הדרך היחידה להגיע אליה.
