קבצים ב‑REST
העלאת קובץ למערכת היא שתי פעולות נפרדות, לא אחת:
- העלאה — הבייטים עולים לאחסון והשרת מחזיר
nameו‑url. - שיוך — כותבים את הערך שחזר לתוך שדה מסוג
Fileברשומה.
בין שתי הפעולות אין קשר אוטומטי. זו הנקודה שממנה נובעות כמעט כל התקלות בעמוד הזה.
שלב 1 · העלאה
Section titled “שלב 1 · העלאה”הגוף הוא בייטים גולמיים; כותרת Content-Type היא של הקובץ, לא application/json.
curl -sS -X POST "https://api.mbapps.co.il/parse/files/contract.pdf" \ -H "X-Parse-Application-Id: $APP_ID" \ -H "X-Parse-Master-Key: $MASTER_KEY" \ -H "Content-Type: application/pdf" \ --data-binary '@/path/to/contract.pdf'const fs = require('fs');const APP_ID = process.env.APP_ID, MASTER_KEY = process.env.MASTER_KEY;
const res = await fetch('https://api.mbapps.co.il/parse/files/contract.pdf', { method: 'POST', headers: { 'X-Parse-Application-Id': APP_ID, 'X-Parse-Master-Key': MASTER_KEY, 'Content-Type': 'application/pdf', }, body: fs.readFileSync('/path/to/contract.pdf'),});const data = await res.json();import os, requests
APP_ID = os.environ["APP_ID"]MASTER_KEY = os.environ["MASTER_KEY"]
with open("/path/to/contract.pdf", "rb") as fh: res = requests.post( "https://api.mbapps.co.il/parse/files/contract.pdf", headers={ "X-Parse-Application-Id": APP_ID, "X-Parse-Master-Key": MASTER_KEY, "Content-Type": "application/pdf", }, data=fh, )data = res.json()<?php$appId = getenv('APP_ID');$masterKey = getenv('MASTER_KEY');
$ch = curl_init();curl_setopt_array($ch, [ CURLOPT_URL => "https://api.mbapps.co.il/parse/files/contract.pdf", CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "X-Parse-Application-Id: $appId", "X-Parse-Master-Key: $masterKey", "Content-Type: application/pdf", ], CURLOPT_POSTFIELDS => file_get_contents("/path/to/contract.pdf"),]);$response = curl_exec($ch);curl_close($ch);$data = json_decode($response, true);201 Created →
{ "url": "https://api.mbapps.co.il/parse/files/aaaaaaa…/9f2c1b7e-contract.pdf", "name": "9f2c1b7e-contract.pdf"}| פרט | ערך |
|---|---|
| גודל מרבי | אין תקרה נאכפת. נבדקו העלאות של 11MB ו‑15MB — שתיהן נשמרו |
| סוג הקובץ | יש ולידציה בצד השרת — לפי התוכן (magic bytes) — ראו האזהרה למטה |
Content-Type |
אינו נבדק. PNG תקין שנשלח עם Content-Type: text/plain נשמר (201). שלחו את הטיפוס הנכון בכל זאת — הוא מה שהדפדפן יקבל בהורדה |
| שם הקובץ בנתיב | כל שם מתקבל — גם רווחים, סוגריים ועברית (בקידוד URL). השרת מוסיף קידומת ומקודד את השם ב‑url שחוזר |
| אימות | אינו נדרש להעלאה — X-Parse-Application-Id בלבד מספיק. ראו core-api-05 |
שלב 2 · שיוך לרשומה
Section titled “שלב 2 · שיוך לרשומה”curl -sS -X PUT "https://api.mbapps.co.il/parse/classes/Cases/aB3dEf9HiJ" \ -H "X-Parse-Application-Id: $APP_ID" \ -H "X-Parse-Master-Key: $MASTER_KEY" \ -H "Content-Type: application/json" \ -d '{"Attachment":{"__type":"File","name":"9f2c1b7e-contract.pdf","url":"https://api.mbapps.co.il/parse/files/aaaaaaa…/9f2c1b7e-contract.pdf"}}'const APP_ID = process.env.APP_ID, MASTER_KEY = process.env.MASTER_KEY;
const res = await fetch('https://api.mbapps.co.il/parse/classes/Cases/aB3dEf9HiJ', { method: 'PUT', headers: { 'X-Parse-Application-Id': APP_ID, 'X-Parse-Master-Key': MASTER_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ Attachment: { __type: 'File', name: '9f2c1b7e-contract.pdf', url: 'https://api.mbapps.co.il/parse/files/aaaaaaa…/9f2c1b7e-contract.pdf', }, }),});const data = await res.json();import os, requests
APP_ID = os.environ["APP_ID"]MASTER_KEY = os.environ["MASTER_KEY"]
res = requests.put( "https://api.mbapps.co.il/parse/classes/Cases/aB3dEf9HiJ", headers={ "X-Parse-Application-Id": APP_ID, "X-Parse-Master-Key": MASTER_KEY, "Content-Type": "application/json", }, json={ "Attachment": { "__type": "File", "name": "9f2c1b7e-contract.pdf", "url": "https://api.mbapps.co.il/parse/files/aaaaaaa…/9f2c1b7e-contract.pdf", }, },)data = res.json()<?php$appId = getenv('APP_ID');$masterKey = getenv('MASTER_KEY');
$ch = curl_init();curl_setopt_array($ch, [ CURLOPT_URL => "https://api.mbapps.co.il/parse/classes/Cases/aB3dEf9HiJ", CURLOPT_CUSTOMREQUEST => "PUT", CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "X-Parse-Application-Id: $appId", "X-Parse-Master-Key: $masterKey", "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode([ "Attachment" => [ "__type" => "File", "name" => "9f2c1b7e-contract.pdf", "url" => "https://api.mbapps.co.il/parse/files/aaaaaaa…/9f2c1b7e-contract.pdf", ], ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),]);$response = curl_exec($ch);curl_close($ch);$data = json_decode($response, true);לפני זה — ודאו שהשדה קיים ושהוא באמת מטיפוס File או PrivateFile:
curl -sS "https://api.mbapps.co.il/parse/schemas/Cases" \ -H "X-Parse-Application-Id: $APP_ID" -H "X-Parse-API-Key: $API_KEY"const APP_ID = process.env.APP_ID, API_KEY = process.env.API_KEY;
const res = await fetch('https://api.mbapps.co.il/parse/schemas/Cases', { headers: { 'X-Parse-Application-Id': APP_ID, 'X-Parse-API-Key': API_KEY, },});const data = await res.json();import os, requests
APP_ID = os.environ["APP_ID"]API_KEY = os.environ["API_KEY"]
res = requests.get( "https://api.mbapps.co.il/parse/schemas/Cases", headers={ "X-Parse-Application-Id": APP_ID, "X-Parse-API-Key": API_KEY, },)data = res.json()<?php$appId = getenv('APP_ID');$apiKey = getenv('API_KEY');
$ch = curl_init();curl_setopt_array($ch, [ CURLOPT_URL => "https://api.mbapps.co.il/parse/schemas/Cases", CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "X-Parse-Application-Id: $appId", "X-Parse-API-Key: $apiKey", ],]);$response = curl_exec($ch);curl_close($ch);$data = json_decode($response, true);ראו סכימות.
File מול PrivateFile
Section titled “File מול PrivateFile”File |
PrivateFile |
|
|---|---|---|
| מה קורה בשיוך | ה‑URL נכתב מחדש לכתובת ציבורית: https://db-static.mbapps.co.il/<APP_ID>/<name>, והערך מקבל "public": true |
ה‑URL נכתב מחדש ל‑…/parse/files/<APP_ID>/<name>/<Table>/<objectId>/<Field>, והערך מקבל "private": true |
| הגישה | פתוחה לכל מי שמחזיק את הכתובת — 200 בלי שום כותרת (S3 + CDN, Cache-Control: public, max-age=86400) |
מתווכת דרך הרשומה והשדה: Master Key, או Session Token של משתמש עם הרשאה על הרשומה. כל השאר — 302 לשער ההתחברות |
| מתאים ל | לוגו, תמונת מוצר, נכס שיווקי | חוזים, מסמכים חתומים, מסמכים חשבונאיים, כל דבר עם מידע אישי |
| דוגמאות במוצר | _User.profile_image, Products.PreviewImage, Products.Image1/Image2, Files.PublicFile |
PriceQuotes.SignedPdf, PriceQuotes.Signature, AccountingHeaders.File, Notes.File, Files.File, ConversationMessages.File |
הקריסה שבין שני השלבים
Section titled “הקריסה שבין שני השלבים”│
crash here │ ──> orphan file
▼
2 · PUT /parse/classes/{Table}/{objectId}
תהליך שקרס אחרי ההעלאה ולפני השיוך משאיר קובץ יתום: הוא קיים, הוא צורך מכסת אחסון, ואף רשומה אינה מצביעה עליו. אין דרך למצוא אותו דרך ה‑API — הוא אינו מופיע בשום שאילתת נתונים.
הדפוס העמיד: רשמו את ה‑url שחזר לפני ניסיון השיוך, כדי שיהיה מה לשייך או מה לנקות אחר כך.
import logging, requests
API = "https://api.mbapps.co.il/parse"H_BIN = {"X-Parse-Application-Id": APP_ID, "X-Parse-Master-Key": MASTER_KEY}H_JSON = {**H_BIN, "Content-Type": "application/json"}
def upload_and_attach(path: str, mime: str, table: str, object_id: str, field: str) -> dict: filename = path.rsplit("/", 1)[-1]
# 1 · העלאה with open(path, "rb") as fh: up = requests.post(f"{API}/files/{filename}", headers={**H_BIN, "Content-Type": mime}, data=fh, timeout=120) up.raise_for_status() stored = up.json() # {"name": "...", "url": "..."}
# תיעוד לפני השיוך — כדי שקובץ יתום לא ייעלם מהעין logging.info("uploaded %s -> %s", filename, stored["url"])
# 2 · שיוך body = {field: {"__type": "File", "name": stored["name"], "url": stored["url"]}} at = requests.put(f"{API}/classes/{table}/{object_id}", headers=H_JSON, json=body, timeout=30) if not at.ok: logging.error("attach failed for %s — orphan file remains at %s", object_id, stored["url"]) at.raise_for_status() return storedמה לא מקבל קבצים
Section titled “מה לא מקבל קבצים”| השכבה | ההתנהגות |
|---|---|
web2table / web2case |
לא תומכות בהעלאת קבצים — ושדה מסוג File בבקשה אינו “נזרק בשקט” אלא מפיל את כל הבקשה ב‑400, ומשאיר אחריו נזק: איש הקשר כן נוצר, ואם שלחתם אובייקט File (ולא מחרוזת) גם נוצרות עד 5 שורות יעד כפולות בגלל retry פנימי (web-forms-10, עדיין משתחזר ל‑28.08.2026). העלו בנפרד ושייכו אחר כך, או קבלו את הקובץ בשרת ביניים משלכם. הכלל חל גם על getlead |
| תחבורה משלו — base64 עם קידומת Data‑URI, או שם קובץ שכבר שמור במערכת. שימו לב לאסימטריה: העלאה רגילה מקבלת base64 בלי קידומת | |
| שדה String שנראה כמו URL | לפעמים עמוד מצפה ל‑URL גולמי במחרוזת ולא לאובייקט File. בדקו סכימה |
מחיקת קבצים
Section titled “מחיקת קבצים”הפעולה קיימת ועובדת, ודורשת Master Key או API Key:
curl -sS -X DELETE "https://api.mbapps.co.il/parse/files/9f2c1b7e-contract.pdf" \ -H "X-Parse-Application-Id: $APP_ID" \ -H "X-Parse-Master-Key: $MASTER_KEY"const APP_ID = process.env.APP_ID, MASTER_KEY = process.env.MASTER_KEY;
const res = await fetch('https://api.mbapps.co.il/parse/files/9f2c1b7e-contract.pdf', { method: 'DELETE', headers: { 'X-Parse-Application-Id': APP_ID, 'X-Parse-Master-Key': MASTER_KEY, },});import os, requests
APP_ID = os.environ["APP_ID"]MASTER_KEY = os.environ["MASTER_KEY"]
res = requests.delete( "https://api.mbapps.co.il/parse/files/9f2c1b7e-contract.pdf", headers={ "X-Parse-Application-Id": APP_ID, "X-Parse-Master-Key": MASTER_KEY, },)<?php$appId = getenv('APP_ID');$masterKey = getenv('MASTER_KEY');
$ch = curl_init();curl_setopt_array($ch, [ CURLOPT_URL => "https://api.mbapps.co.il/parse/files/9f2c1b7e-contract.pdf", CURLOPT_CUSTOMREQUEST => "DELETE", CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "X-Parse-Application-Id: $appId", "X-Parse-Master-Key: $masterKey", ],]);$response = curl_exec($ch);curl_close($ch);- הצלחה:
200עם גוף ריק — לא{}, לא הודעה. - אחרי המחיקה, קריאה ל‑URL של הקובץ תחת
/parse/files/מחזירה404 File not found. - שם קובץ שאינו קיים מוחזר גם הוא ב‑
200. הפעולה אידמפוטנטית ואינה מבחינה בין “נמחק” ל“לא היה”. - עם
X-Parse-Application-Idבלבד:403 {"error":"unauthorized: master key is required"}. - אין כלי מחיקת קבצים בשכבת ה‑MCP — שם קיימים רק
Upload-Public-Fileו‑Get-File-Content.
השם למחיקה הוא ה‑name שחזר מהשרת (עם הקידומת), לא שם הקובץ המקומי שלכם. שמרו אותו.
מכאן נובע כלל תכנון: תכננו לא לייצר קבצים יתומים — לא כי אי אפשר למחוק, אלא כי אי אפשר למצוא אותם: אין נקודת קצה שמחזירה רשימת קבצים, והם אינם מופיעים בשום שאילתת נתונים.
מכסת אחסון
Section titled “מכסת אחסון”האחסון נמדד לכל מערכת ומוצג בסביבת הפיתוח, במסך הפרויקט (“Storage used · X GB remaining”). לכל מערכת יש גם דגל outOfStorage.
קריאת מצב האחסון ושינוי הדגל הן פעולות תפעוליות מצד הספק — הן אינן חשופות ב‑API הזה, ולכן גם אי אפשר לבדוק מהקוד כמה מקום נשאר. תכננו בהתאם: אם המערכת מעלה קבצים בקצב, בקשו מהצוות ניטור.
דפוסי URL שתפגשו
Section titled “דפוסי URL שתפגשו”| התבנית | ההקשר |
|---|---|
https://api.mbapps.co.il/parse/files/<APP_ID>/<FILE_NAME> |
URL קובץ כפי שחוזר מההעלאה — סגור מאחורי שער ההתחברות |
https://db-static.mbapps.co.il/<APP_ID>/<FILE_NAME> |
File אחרי שיוך — עותק ציבורי ב‑CDN, מצורף לו "public": true |
…/parse/files/<APP_ID>/<FILE_NAME>/<Table>/<objectId>/<Field> |
PrivateFile אחרי שיוך — הנתיב נכתב מחדש ומצורף לו "private": true |
https://siteadmin.mbapps.co.il/uploads/… |
נכסי אתר — לוגואים, קבצי עמוד |
צ’קליסט
Section titled “צ’קליסט”- השדה קיים בסכימה, והוא
FileאוPrivateFile— לא String. - התוכן תואם לסיומת — אחרת
403 Invalid file type(ה‑Content-Typeעצמו אינו נבדק, אבל שלחו אותו נכון). - השיוך משתמש ב‑
nameו‑urlשחזרו מהשרת — שיוך עם השם המקומי מתקבל ב‑200, וההורדה מהכתובת שנכתבה מחדש מחזירה404 File not found. - מידע רגיש →
PrivateFile, לאFile— שדהFileהופך את הקובץ לציבורי. מחיקה מסירה אותו מעתה (403), אך ה‑CDN עשוי לשמור עותק עד כ‑24 שעות (api-b-01). - רשמתם את ה‑URL לפני השיוך, כדי שקריסה לא תשאיר קובץ אבוד ובלתי נראה.
- אם ההעלאה היא מקוד שרץ בדפדפן — זכרו שהיא עוברת גם בלי מפתח, כלומר גם לתוקף.
המשך מכאן
Section titled “המשך מכאן”- סוגי נתונים — הצורה המדויקת של ערך
File - סכימות — לוודא את טיפוס השדה לפני הכתיבה
- עבודה עם רשומות — פעולת ה‑
PUTשל השיוך - אימות והרשאות — למה העלאה אנונימית היא בעיה
- מגבלות ומכסות