Wygeneruj klucz
W aplikacji przejdz do Settings -> API keys i utworz nowy klucz smm_....
Public API v1
Stabilny kontrakt publicznego API dla kolejkowania publikacji, uploadow mediow i zarzadzania
zadaniami publikacji. Jeden request moze obsluzyc wiele kont i platform, ze wspolna trescia,
nadpisaniami per target oraz natywnymi opcjami platform przez platform_options.
{
"publication_type": "post",
"targets": [42, 43],
"common_content": {
"caption": "Hello from the API",
"media_asset_ids": [1234]
},
"schedule_mode": "now"
}
Pierwszy request
API akceptuje token Bearer. Najprostszy przeplyw to wygenerowanie klucza API w aplikacji, wybranie przykladu pasujacego do procesu i wyslanie requestu z naglowkiem autoryzacji.
W aplikacji przejdz do Settings -> API keys i utworz nowy klucz smm_....
Skorzystaj z sekcji Przyklady: post, reel, story, film albo kolejka zadan.
Dodaj Authorization, a dla tworzenia publikacji takze Idempotency-Key.
Trzymaj klucz w menedzerze sekretow lub zmiennej srodowiskowej. Jesli klucz wycieknie, odwolaj go i wygeneruj nowy.
Authentication
API przyjmuje token Bearer: klucz API smm_... wygenerowany w UI albo JWT z sesji SPA.
Klucze API sa przypisane do organizacji, dlatego naglowek X-Organization-Id jest dla
nich opcjonalny.
Authorization: Bearer smm_REPLACE_ME
Klucz jest widoczny tylko raz przy tworzeniu. Jesli go utracisz, odwolaj stary klucz i wygeneruj nowy.
X-Organization-Id| Principal | Naglowek wymagany? |
|---|---|
API key smm_... |
Nie, klucze sa org-scoped. |
| JWT, uzytkownik jednej organizacji | Nie, organizacja jest wywnioskowana. |
| JWT, uzytkownik z co najmniej 2 aktywnymi czlonkostwami | Tak, ustaw X-Organization-Id. |
Brak naglowka dla multi-org JWT zwraca 401 organization_required.
Idempotency
Aby bezpiecznie ponawiac requesty po awariach sieci, dolacz naglowek Idempotency-Key.
Wartoscia moze byc UUID v4 albo string o dlugosci 16-128 znakow. Okno idempotencji trwa 24 godziny
od pierwszego sukcesu, a serwer odsyla wartosc w Idempotency-Key-Echo.
Idempotency-Key: f47ac10b-58cc-4372-a567-0e02b2c3d479
| Sytuacja | Odpowiedz |
|---|---|
| Identyczny payload w ciagu 24 godzin | 200 z cache'owanym body pierwszego sukcesu. |
| Inny request z tym samym kluczem jest nadal przetwarzany | 409 z error_code: idempotency_in_progress. |
| Ten sam klucz, inny payload | 422 z error_code: idempotency_key_conflict. |
| Niepoprawny format klucza | 400 z error_code: idempotency_key_invalid. |
Rate limits
Limity sa naliczane per klucz API albo per JWT subject z uzyciem sliding window. Udane odpowiedzi
zawieraja naglowki X-RateLimit-Limit, X-RateLimit-Remaining i
X-RateLimit-Reset. Po przekroczeniu budzetu API zwraca 429 z
error_code: rate_limited oraz Retry-After.
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 1718524800
Retry-After: 17
Errors
Kazda odpowiedz >= 400 zwraca kanoniczna koperte. request_id pochodzi z
naglowka X-Request-Id, a lista error_code jest zamknieta i zdefiniowana
w app.services._errors.ERROR_CODES.
{
"error_code": "validation_error",
"error_message": "Walidacja zadania nie powiodla sie",
"request_id": "1a2b3c...",
"details": null
}
| error_code | HTTP | Opis |
|---|---|---|
missing_bearer_token | 401 | Brak naglowka Authorization albo cookie. |
invalid_api_key | 401 | Klucz API jest odwolany albo nieznany. |
organization_required | 401 | Multi-org JWT musi ustawic X-Organization-Id. |
organization_forbidden | 403 | Principal nie nalezy do wybranej organizacji. |
tenant_forbidden | 403 | Operacja wymaga wyzszej roli OrgRole. |
validation_error | 422 | Walidacja body albo query w Pydantic nie powiodla sie. |
idempotency_in_progress | 409 | Ten sam Idempotency-Key jest nadal przetwarzany. |
idempotency_key_conflict | 422 | Klucz idempotencji zostal uzyty z innym payloadem. |
idempotency_key_invalid | 400 | Niepoprawny format Idempotency-Key. |
target_not_found | 404 | Jeden z targetow nie nalezy do tenanta. |
all_targets_failed | 422 | Wszystkie targety publikacji zakonczyly sie bledem. |
platform_does_not_support_type | 422 | Platforma konta nie obsluguje danego publication_type. |
schedule_required_for_custom | 422 | schedule_mode='custom' wymaga terminu publikacji. |
invalid_status_transition | 409 | Cancel albo retry zostal wywolany dla niezgodnego statusu joba. |
scheduled_at_in_past | 400 | PATCH scheduled_at wskazuje przeszlosc. |
media_too_large | 413 | Upload przekracza limit rozmiaru dla typu mediow. |
rate_limited | 429 | Przekroczono limit per klucz. |
account_not_found | 404 | account_id nie nalezy do wybranego tenanta. |
Reference
Wszystkie endpointy wymagaja naglowka Authorization: Bearer <token>. Endpointy
oznaczone jako idempotent honoruja naglowek Idempotency-Key.
| Method | URL | Role | Idempotent | Opis | Statusy |
|---|---|---|---|---|---|
| POST | /api/publications |
member+ | Tak | Tworzy unified publication i rozbija ja na jeden PublishJob per target. Body: PublicationCreate. |
200, 201, 207, 401, 403, 404, 409, 422, 429 |
| GET | /api/publications/capabilities |
viewer+ | - | Macierz platforma x typ, limity caption, limity mediow i opcje story. | 200, 401, 403, 429 |
| GET | /api/publications/preview-schedule |
viewer+ | - | Najwczesniejszy wolny slot dla account_id i content_type. |
200, 401, 403, 404, 429 |
| GET | /api/publish-jobs |
viewer+ | - | Stronicowana kolejka i historia. Filtry: status[], platform[], account_id[], daty, sort, limit, offset. |
200, 400, 401, 403, 429 |
| GET | /api/publish-jobs/{id} |
viewer+ | - | Pojedyncza projekcja joba PublishJobRead. |
200, 401, 403, 404, 429 |
| POST | /api/publish-jobs/{id}/cancel |
member+ | - | Przenosi queued albo scheduled job do statusu canceled. | 200, 401, 403, 404, 409, 429 |
| POST | /api/publish-jobs/{id}/retry |
member+ | - | Przenosi failed albo canceled job do kolejki. | 200, 401, 403, 404, 409, 429 |
| PATCH | /api/publish-jobs/{id} |
member+ | - | Edytuje caption, title albo termin queued lub scheduled joba. | 200, 400, 401, 403, 404, 409, 429 |
| POST | /api/publish-jobs/bulk/cancel |
member+ | - | Anuluje do 500 jobow. Body: { ids: number[] }. |
200, 401, 403, 422, 429 |
| POST | /api/publish-jobs/bulk/retry |
member+ | - | Ponawia do 500 jobow. Body: { ids: number[] }. |
200, 401, 403, 422, 429 |
| POST | /api/uploads/init |
member+ | - | Inicjuje presigned upload, single PUT albo multipart. | 201, 400, 401, 403, 413, 429 |
| POST | /api/uploads/{public_id}/complete |
member+ | - | Finalizuje presigned upload i oznacza MediaAsset jako ready. |
200, 400, 401, 403, 404, 429 |
| POST | /api/uploads/{public_id}/abort |
member+ | - | Anuluje upload w toku i oznacza asset jako failed. | 200, 401, 403, 404, 429 |
| POST | /api/uploads/direct |
member+ | - | Server-streamed upload multipart/form-data do 100 MB. |
201, 400, 401, 403, 413, 429 |
| POST | /api/uploads/from-url |
member+ | - | Server-side fetch i upload z zewnetrznego URL z whitelisty. | 200, 401, 403, 422, 429, 502 |
| GET | /api/api-keys |
member+ | - | Lista aktywnych kluczy API dla aktualnej organizacji. | 200, 401, 403, 429 |
| POST | /api/api-keys |
member+ | - | Generuje nowy klucz API. Token plaintext wraca tylko raz. | 201, 401, 403, 429 |
| DELETE | /api/api-keys/{key_id} |
member+ | - | Odwoluje klucz API. | 204, 401, 403, 404, 429 |
Schemas
Zrodlem kontraktu jest app.schemas.publication.PublicationCreate. Opcje wspolne
przekazuj przez common_content.platform_options, a opcje pojedynczego targetu przez
overrides[account_id].platform_options.
{
"publication_type": "post|story|reel|film",
"targets": ["<account_id>"],
"common_content": {
"caption": "Hello world",
"hashtags": ["smm", "automation"],
"media_asset_ids": ["<media_asset_id>"],
"title": "optional (required for film)",
"description": "optional",
"cover": { "kind": "frame_at_seconds", "t": 1.5 },
"platform_options": {
"youtube": { "privacy": "unlisted", "tags": ["api"] },
"tiktok": { "privacy_level": "SELF_ONLY", "disable_comment": true },
"x": { "content_type": "thread", "thread": ["tweet 1", "tweet 2"] },
"linkedin": { "content_type": "article", "url": "https://example.com" }
}
},
"overrides": {
"<account_id>": {
"caption_override": "...",
"hashtags_override": ["..."],
"media_override": ["<media_asset_id>"],
"story_options": { "link_sticker_url": "https://example.com" },
"cover_override": { "kind": "asset", "asset_id": 123 },
"title_override": "...",
"description_override": "...",
"tags_override": ["yt", "tags"],
"share_to_feed": true,
"platform_options": { "privacy": "private" },
"scheduled_at": "2026-07-01T15:00:00+02:00",
"target_ig": true,
"target_fb": false
}
},
"schedule_mode": "now|next_slot|custom",
"scheduled_at": "2026-07-01T15:00:00+02:00",
"scheduled_at_per_target": {
"<account_id>": "2026-07-01T15:00:00+02:00"
}
}
| Platform key | Common fields |
|---|---|
youtube | title, description, tags, category / category_id, privacy / privacy_status |
tiktok | content_type, privacy_level, disable_duet, disable_comment, disable_stitch, cover_ms, cover_index |
x / twitter | content_type, thread |
linkedin | content_type, url, title, visibility |
instagram_facebook | fb_title, fb_comment_text, is_trial, share_to_feed; uzyj target_ig / target_fb dla fan-out IG vs FB. |
{
"publication_id": "<uuid5 of (organization, idempotency_key)>",
"request_id": "<X-Request-Id>",
"jobs": [
{
"target_id": "<account_id>",
"job_id": "<publish_job_id | null>",
"status": "scheduled|queued|succeeded|failed",
"scheduled_at": "ISO-8601 | null",
"error_code": "string | null",
"error_message": "string | null"
}
],
"summary": { "error_code": "all_targets_failed" }
}
Live reference
Aktualna macierz platforma x typ publikacji i limity mediow sa wystawione przez
GET /api/publications/capabilities. Poniewaz macierz zmienia sie wraz z platformami
i limitami, kanoniczna odpowiedz powinna byc pobierana runtime.
Token sluzy tylko do tego requestu w przegladarce i nie jest zapisywany.
{
"platforms": {
"instagram_facebook": {
"supported_types": ["post", "reel", "story"],
"caption_limits": { "post": 2200, "reel": 2200, "story": 0 },
"platform_options": {
"content_type": { "type": "enum", "values": ["auto", "image", "video"] },
"fb_title": { "type": "string", "max_length": 255 }
}
}
},
"media_limits": {
"post": { "types": ["image/jpeg", "image/png", "image/webp"], "max_mb": 10, "count_max": 10 },
"reel": { "types": ["video/mp4", "video/quicktime"], "max_mb": 500, "count_max": 1 }
},
"story_options": {
"instagram_facebook": {
"link_sticker_url": { "type": "url", "max_length": 2000 }
}
}
}
Examples
Zastap smm_REPLACE_ME realnym kluczem API. Requesty kieruj do
https://app.mumro.io.
curl -X POST https://app.mumro.io/api/publications \
-H "Authorization: Bearer smm_REPLACE_ME" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: f47ac10b-58cc-4372-a567-0e02b2c3d479" \
-d '{
"publication_type": "post",
"targets": [42, 43],
"common_content": {
"caption": "Hello from the API",
"hashtags": ["smm", "automation"],
"media_asset_ids": [1234]
},
"schedule_mode": "now"
}'
import os, uuid, requests
resp = requests.post(
"https://app.mumro.io/api/publications",
headers={
"Authorization": f"Bearer {os.environ['SMM_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"publication_type": "post",
"targets": [42, 43],
"common_content": {
"caption": "Hello from Python",
"hashtags": ["smm", "automation"],
"media_asset_ids": [1234],
},
"schedule_mode": "now",
},
timeout=30,
)
resp.raise_for_status()
print(resp.json())
const idempotencyKey = crypto.randomUUID();
const resp = await fetch("https://app.mumro.io/api/publications", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SMM_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify({
publication_type: "post",
targets: [42, 43],
common_content: {
caption: "Hello from Node",
hashtags: ["smm", "automation"],
media_asset_ids: [1234],
},
schedule_mode: "now",
}),
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
console.log(await resp.json());
curl -X POST https://app.mumro.io/api/publications \
-H "Authorization: Bearer smm_REPLACE_ME" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: f47ac10b-58cc-4372-a567-0e02b2c3d479" \
-d '{
"publication_type": "reel",
"targets": [55],
"common_content": {
"caption": "Reel for next slot",
"media_asset_ids": [9001]
},
"schedule_mode": "next_slot"
}'
curl -X POST https://app.mumro.io/api/publications \
-H "Authorization: Bearer smm_REPLACE_ME" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"publication_type": "post",
"targets": [42, 77, 88],
"common_content": {
"caption": "Launch update",
"media_asset_ids": [1234, 1235],
"platform_options": {
"x": {
"content_type": "thread",
"thread": ["Launch update", "More details in the link"]
},
"linkedin": {
"content_type": "article",
"url": "https://example.com/launch",
"title": "Launch update"
},
"tiktok": {
"content_type": "carousel",
"privacy_level": "SELF_ONLY",
"disable_comment": false
}
}
},
"overrides": {
"77": {
"caption_override": "LinkedIn-specific copy",
"platform_options": { "visibility": "PUBLIC" }
}
},
"schedule_mode": "now"
}'
curl -X POST https://app.mumro.io/api/publications \
-H "Authorization: Bearer smm_REPLACE_ME" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"publication_type": "film",
"targets": [201],
"common_content": {
"title": "API release walkthrough",
"description": "Long-form release video",
"media_asset_ids": [555],
"platform_options": {
"youtube": {
"privacy": "unlisted",
"category": "28",
"tags": ["mumro", "api", "release"]
}
}
},
"schedule_mode": "custom",
"scheduled_at": "2026-07-15T18:00:00+02:00"
}'
curl -X POST https://app.mumro.io/api/publications \
-H "Authorization: Bearer smm_REPLACE_ME" \
-H "Content-Type: application/json" \
-d '{
"publication_type": "story",
"targets": [101],
"common_content": { "media_asset_ids": [7777] },
"overrides": {
"101": {
"story_options": { "link_sticker_url": "https://example.com/landing" }
}
},
"schedule_mode": "now"
}'
curl -G https://app.mumro.io/api/publish-jobs \
-H "Authorization: Bearer smm_REPLACE_ME" \
--data-urlencode "status=queued" \
--data-urlencode "status=scheduled" \
--data-urlencode "platform=instagram_facebook" \
--data-urlencode "limit=20" \
--data-urlencode "offset=0"
curl -X POST https://app.mumro.io/api/publish-jobs/123/cancel \
-H "Authorization: Bearer smm_REPLACE_ME"
curl -X POST https://app.mumro.io/api/publish-jobs/123/retry \
-H "Authorization: Bearer smm_REPLACE_ME"