קבצים ב‑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"}| פרט | ערך |
|---|---|
| גודל מרבי | 32MB לבקשה. 11MB ו‑15MB נשמרים; 33MB נדחה בשכבת הקצה ב‑413 Request Entity Too Large כדף HTML, בלי JSON. מעבר לתקרה: files/signed-url-put/, למטה |
| סוג הקובץ | יש ולידציה בצד השרת — לפי התוכן (magic bytes) — ראו האזהרה למטה |
Content-Type |
אינו נבדק. PNG תקין שנשלח עם Content-Type: text/plain נשמר (201). שלחו את הטיפוס הנכון בכל זאת — הוא מה שהדפדפן יקבל בהורדה |
| שם הקובץ בנתיב | כל שם מתקבל — גם רווחים, סוגריים ועברית (בקידוד URL). השרת מוסיף קידומת ומקודד את השם ב‑url שחוזר |
| אימות | אינו נדרש להעלאה: X-Parse-Application-Id בלבד מספיק. שלחו בכל זאת Session Token או API Key בכותרת, כי הדרישה עתידה להיאכף |
קבצים מעל התקרה · files/signed-url-put/
Section titled “קבצים מעל התקרה · files/signed-url-put/”מחזיר מדיניות העלאה חתומה, ומאפשר להעלות ישירות לאחסון בלי לעבור בתקרת ה‑32MB של הבקשה. הגוף:
{ "ContentType": "application/pdf", "ContentLength": 34603008 }curl -sS -X POST "https://api.mbapps.co.il/parse/files/signed-url-put/big-scan.pdf" \ -H "X-Parse-Application-Id: $APP_ID" \ -H "X-Parse-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{"ContentType":"application/pdf","ContentLength":34603008}'| התנאי | מה חוזר בלעדיו |
|---|---|
| אימות בבקשה (Session Token, API Key או Master Key) | {"code":400,"error":"Missing Authorization."}. X-Parse-Application-Id בלבד אינו מספיק כאן, בניגוד להעלאה הרגילה |
הרשאת allowAllFiles על המערכת |
{"code":400,"error":"App is not allowed to upload large files."} |
התשובה מכילה url, fields ו‑name. משם ההעלאה היא POST של multipart/form-data אל ה‑url שחזר, עם כל זוגות ה‑fields כשדות טופס ו‑file אחרון. בתום ההעלאה משייכים לרשומה כרגיל, עם ה‑name שחזר מהקריאה הזו.
ממשק המוצר עובר למסלול הזה מעל 25MB כברירת מחדל, וסף המעבר נקבע בדף עצמו (direct-files-limit). מתחתיו הוא מעלה ב‑POST /parse/files/<fileName> הרגיל.
שלב 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 |
פתיחת קובץ בבקשת POST
Section titled “פתיחת קובץ בבקשת POST”כתובת של PrivateFile משויך נפתחת גם ב‑POST, עם פרטי הגישה בגוף ה‑JSON במקום בכותרות:
curl -sS -X POST "https://api.mbapps.co.il/parse/files/$APP_ID/9f2c1b7e-contract.pdf/Cases/aB3dEf9HiJ/Attachment" \ -H "Content-Type: application/json" \ -d "{\"_MasterKey\":\"$MASTER_KEY\"}"| הפרמטר בגוף | המצב |
|---|---|
_MasterKey |
מחזיר את תוכן הקובץ |
_apiKey |
מחזיר את תוכן הקובץ |
_SessionToken |
צפוי בגרסה קרובה |
הכתובת הגולמית שחוזרת מההעלאה (…/parse/files/<APP_ID>/<name>, בלי הטבלה, הרשומה והשדה) אינה נפתחת כך: POST עליה מוחזר ב‑403 {"error":"unauthorized"}, ועם _ApplicationId בגוף ב‑404 Not Found. עליה עובדים בכותרות.
הקריסה שבין שני השלבים
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 פנימי. העלו בנפרד ושייכו אחר כך, או קבלו את הקובץ בשרת ביניים משלכם. הכלל חל גם על 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. - התוכן תואם לסיומת.
- השיוך משתמש ב‑
nameו‑urlשחזרו מהשרת. - מידע רגיש →
PrivateFile. - ה‑URL נרשם לפני השיוך.
- העלאה מהדפדפן עוברת גם בלי מפתח, כלומר גם לתוקף.
המשך מכאן
Section titled “המשך מכאן”- סוגי נתונים — הצורה המדויקת של ערך
File - סכימות · עבודה עם רשומות · אימות והרשאות
- מגבלות ומכסות