MishkanMishkan

API

Publier sur Mishkan depuis l'extérieur de l'app.

Mis à jour le 2026-09-11

Une clé API Mishkan permet à un studio, un script ou un serveur de lire le fil et d'y publier en votre nom. C'est une deuxième porte vers votre propre identité — pas un compte de service : une clé obéit à toutes vos règles, de votre niveau de vérification au coût en Mem d'une publication, en passant par les personnes que vous avez bloquées.

Tout ce qui suit est servi par https://api.mishkan.tech. Les réponses sont en JSON ; une erreur porte un statut HTTP et une seule chaîne « error ».

Obtenir une clé

Les clés se créent dans l'app : Profil → Clés API → +. En créer une demande le niveau 4 (vérification formelle), la même barre que les comptes business — une clé agit en votre nom hors de l'app, on ne la confie donc qu'à un compte qu'un admin a identifié. Lister et révoquer restent possibles à tous les niveaux.

  • Nom — pour vous y retrouver (« Studio Midbar »).
  • Publier en tant que — votre compte personnel, ou l'un de vos comptes business. Une clé épinglée sur un business ne peut publier QUE sous cette identité ; nommer une autre identité dans une requête est refusé.
  • Autoriser la publication — coupé, la clé ne peut que lire.

Le secret (msk_live_…) n'est affiché qu'une fois. Le serveur n'en garde que le SHA-256 : il est irrécupérable — une clé perdue se révoque et se remplace. Vous pouvez détenir jusqu'à 20 clés actives, chacune avec une expiration facultative allant jusqu'à dix ans.

Authentification

Envoyez la clé comme jeton bearer. L'en-tête X-API-Key est accepté aussi, pour les clients qui se réservent Authorization.

curl https://api.mishkan.tech/api/users/me \
  -H "Authorization: Bearer $MISHKAN_KEY"

Une clé inconnue, révoquée ou expirée répond le même 401 — volontairement : sonder l'API n'apprend rien à un attaquant.

Portées

Une clé porte posts:read, posts:write, notifications:write, ou n'importe quelle combinaison. Tout le reste — messages, Mem, vérification, administration, et la gestion des clés elle-même — reste réservé à une session signée dans l'app. Une clé qui fuite ne peut ni en créer une autre, ni lire vos conversations, ni vider votre solde.

  • posts:read — lire le fil, une publication, vos publications, vos comptes business.
  • posts:write — publier et supprimer.
  • notifications:write — prévenir le propriétaire de la clé, et lui seul. Jamais accordée par défaut : demandez-la à la création de la clé, ou activez-la ensuite depuis sa ligne dans l'app.

Points d'entrée

RoutePortéeNiveau requis
GET /api/users/meposts:read
GET /api/businessesposts:read
GET /api/citiesposts:read
GET /api/posts/mineposts:read
GET /api/feed?kind=visual|textposts:read1 (e-mail vérifié)
GET /api/posts/{id}posts:read1 (e-mail vérifié)
POST /api/postsposts:write3 pour publier sur le fil public
POST /api/posts/visualposts:write3 pour publier sur le fil public
POST /api/posts/youtubeposts:write3 pour publier sur le fil public
DELETE /api/posts/{id}posts:writeauteur ou admin
POST /api/notificationsnotifications:write— (prévient son propre propriétaire)

Publier dans le fil d'un groupe (conversation_id) remplace le gate de niveau par l'appartenance au groupe et sa politique de publication, et paie le prix du groupe à son propriétaire.

curl "https://api.mishkan.tech/api/feed?kind=text&limit=20" \
  -H "Authorization: Bearer $MISHKAN_KEY"

Publier

Une publication sur le fil public coûte 2 מ et demande la vérification mezouza (niveau 3). Le débit est remboursé si l'enregistrement échoue.

POST /api/posts publie du texte et des liens, en JSON :

ChampSignification
content4000 caractères maximum
link_urlURL http(s) absolue facultative, dépliée et affichée en carte
languagefr | en | he — le filtre de langue du fil
cityslug de ville — le filtre de ville du fil
business_idfacultatif ; doit correspondre à l'identité de la clé si elle est épinglée
conversation_idfacultatif ; publie dans le fil de ce groupe plutôt que dans le fil public
curl -X POST https://api.mishkan.tech/api/posts \
  -H "Authorization: Bearer $MISHKAN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content":"Chag sameach","language":"en"}'

POST /api/posts/visual publie un reel ou un carrousel en multipart/form-data : 1 à 10 fichiers, dans l'ordre du formulaire, 30 Mo pour la requête entière. La légende va dans content (1000 caractères), et les mêmes champs facultatifs s'appliquent.

curl -X POST https://api.mishkan.tech/api/posts/visual \
  -H "Authorization: Bearer $MISHKAN_KEY" \
  -F "content=Kabbalat Shabbat, 18:30" \
  -F "media=@reel.mp4;type=video/mp4"

POST /api/posts/youtube publie un reel adossé à une vidéo YouTube. url désigne la vidéo ; link_url est un bouton « En savoir plus » distinct — renseignez les deux si vous voulez les deux.

curl -X POST https://api.mishkan.tech/api/posts/youtube \
  -H "Authorization: Bearer $MISHKAN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://youtu.be/abc123","link_url":"https://example.org"}'

Notifications

Une intégration qui s'arrête la nuit — plus de crédit, jeton expiré, quota atteint — ne le dit à personne : l'erreur part dans les logs d'un ordonnanceur, et on la découvre trois jours plus tard en s'étonnant que rien n'ait été publié. Avec notifications:write, une clé écrit dans le centre de notifications de son propriétaire, et le push part comme pour le reste.

Une clé prévient SON propriétaire, et personne d'autre. Le destinataire n'est pas un champ de la requête — il EST l'identité de la clé, il n'y a donc rien à falsifier pour atteindre quelqu'un d'autre. Ce canal ne peut pas devenir un mégaphone.

ChampSens
titleobligatoire, jusqu'à 120 caractères
bodyjusqu'à 500 caractères
sourcequi parle (60 caractères). Une clé épinglée sur un business prend le nom de ce business par défaut
levelinfo | warning | error — décide de l'icône, jamais du texte
urladresse http(s) facultative qu'ouvre la notification
decision_urladresse http(s) facultative : fait de la notification une demande de décision (ci-dessous)
dedupe_keyabsorbe la répétition d'une même alerte pendant 6 h
curl -X POST https://api.mishkan.tech/api/notifications \
  -H "Authorization: Bearer $MISHKAN_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Autopilot stopped",
    "body": "Anthropic credit exhausted — nothing published since 03:12.",
    "source": "TorahWithLLM",
    "level": "error",
    "url": "https://console.anthropic.com/settings/billing",
    "dedupe_key": "anthropic-credit"
  }'

202 quand elle part. Une répétition dédupliquée répond 200 avec deduped: true et l'identifiant de la notification déjà envoyée — la requête a réussi et rien n'est parti :

{ "delivered": true, "remaining_today": 59 }
{ "id": "…", "delivered": false, "deduped": true, "remaining_today": 59 }

dedupe_key est ce qui rend une boucle d'alerte supportable : un pilote qui retente toutes les dix minutes sans crédit enverrait sinon la même ligne 144 fois par jour. En dessous, un plancher dur de 60 notifications par compte et par 24 h, au-delà duquel la route répond 429 ; remaining_today revient à chaque appel pour lever le pied avant.

Le titre et le corps sont affichés tels quels, dans toutes les langues : le serveur ne sait pas ce que votre intégration voulait dire, et le deviner en trois langues serait une invention. Une clé épinglée sur un business notifie sous cette identité — l'alerte apparaît dans l'app sous ce compte, pas dans le centre personnel — et dedupe_key y est porté aussi, deux commerces employant la même clé décrivant deux pannes différentes.

Demandes de décision

Une intégration a parfois besoin d'une réponse humaine avant d'agir : publier ou non ce que le pilote vient de fabriquer, relancer ou abandonner, choisir entre trois voies. Avec decision_url, la notification n'ouvre pas une page : elle ouvre dans l'app un écran de décision — la question, ce qu'il y a à regarder s'il y a lieu (texte, images, vidéo), et les choix que VOUS définissez. Mishkan ne fait que lire l'adresse et y renvoyer le choix ; c'est l'intégration qui garde le contenu, écrit les libellés et applique la réponse. La relecture d'un article est un usage ; « Relancer le pilote ? » avec deux boutons en est un autre, complet.

curl -X POST https://api.mishkan.tech/api/notifications \
  -H "Authorization: Bearer $MISHKAN_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Publish this article?",
    "body": "Eisenkot leaves the coalition — reel and carousel ready.",
    "level": "info",
    "decision_url": "https://studio.example/api/review/42?t=<token>",
    "dedupe_key": "review-42"
  }'

L'adresse doit porter sa propre authentification (un jeton dans l'URL, signé par l'intégration) : l'app l'appelle telle quelle, sans clé. Elle répond en GET la demande en JSON, et en POST {"choice": "<id>"} la même demande, mise à jour. Le POST est idempotent : une demande déjà tranchée est rendue telle quelle, quel que soit le bouton.

{
  "state": "open",
  "title": "Publish this article?",
  "summary": "The standfirst, or the question in one sentence.",
  "message": "The long part, in **Markdown** — optional.",
  "source": "Midbar studio",
  "status": { "label": "Waiting for your approval. Nothing is published.", "tone": "pending" },
  "choices": [
    { "id": "approve", "label": "Approve and publish", "style": "primary",
      "confirm": "The article goes out to the site and Instagram. A few minutes." },
    { "id": "site_only", "label": "Publish on the site only" },
    { "id": "reject", "label": "Reject", "style": "destructive" }
  ],
  "media": [
    { "kind": "video", "url": "https://…/reel.mp4", "label": "Reel" },
    { "kind": "image", "url": "https://…/slide-1.jpg", "label": "Carousel" }
  ],
  "links": [ { "label": "Source", "url": "https://…" } ],
  "notes": [ "English edition to follow: …" ]
}
POST <decision_url>
{ "choice": "approve" }
ChampSens
stateopen (on attend un choix) ou closed (tranché : plus de boutons). Obligatoire
titlela question, ou le titre de ce qu'on examine. Obligatoire
summary, messagele court (texte) et le long (Markdown), facultatifs
statusoù en est la demande, dans vos mots ; tone : pending | success | danger | warning | neutral (l'icône)
choicesles boutons, dans votre ordre ; style : primary (plein) | destructive (rouge) | default ; confirm : demander confirmation avec ce texte avant d'envoyer
mediaimages et vidéos à regarder ; les images consécutives de même label se feuillettent
links, notesliens ouverts hors de l'app, lignes d'information

L'app relit l'état à chaque ouverture — une demande tranchée depuis un autre téléphone s'affiche close, avec la décision prise. Une demande ouverte ne se retire pas du centre de notifications : la ligne est la seule porte vers la question, et le serveur refuse tant que state vaut open (il le relit à l'adresse) ; close, ou service injoignable, elle se retire comme les autres. Les libellés sont les vôtres, affichés tels quels : l'app ne les traduit pas, écrivez-les dans la langue du destinataire. Donnez une dedupe_key par demande : deux questions la même heure doivent sonner deux fois.

Limites

LimiteValeur
Lectures~60 par minute, rafale de 30
Écritures~20 par minute, rafale de 5
Par adresse IP~300 par minute, rafale de 60
Taille d'une requête30 Mo (App Engine plafonne à 32 Mo)
Médias par publication10
Clés actives par compte20

Erreurs

{ "error": "missing the posts:write scope" }
StatutCe qu'il veut dire
401 invalid API keyinconnue, révoquée ou expirée — les trois se ressemblent volontairement
403 missing the posts:write scopela clé est en lecture seule
403 verification level 3 requiredle compte n'est pas vérifié mezouza, ce qu'exige le fil public
403 can only publish as the business account it is bound tola requête a nommé une autre identité que celle de la clé
402solde en מ insuffisant pour payer la publication
429quota dépassé — attendez avant de réessayer
429 at most 60 notifications per dayle plancher journalier de notifications — utilisez dedupe_key

Révoquer

Balayez la clé (iOS) ou touchez la corbeille (Android), ou appelez DELETE /api/api-keys/{id} avec un jeton de session. L'effet est immédiat. Les publications déjà faites restent en ligne : révoquer un identifiant n'est pas retirer ce qui a été publié avec.