ProposalsProposals

Referencja API (v1)

Beta. Aktualny, czytelny dla maszyn opis tego API jest pod adresem https://api.proposals.co/api/public/v1/openapi.json. Jeśli ta strona i OpenAPI kiedyś się rozejdą, rację ma OpenAPI.

  • Adres bazowy: https://api.proposals.co/api/public/v1
  • Uwierzytelnianie: Authorization: Bearer prp_live_… przy każdym żądaniu (szczegóły)
  • Typ treści: application/json
  • Każdy POST wymaga nagłówka Idempotency-Key (szczegóły)
  • Język pól message i pytań: Accept-Language: pl daje polski, każda inna wartość angielski
  • Błędy: {"detail": {"code", "message", "params"}, "request_id"} (kody)
Metoda i ścieżka Zakres Zwraca
GET /brands brands:read Twoje marki
POST /proposals proposals:write 202 Proposal
GET /proposals/{id} proposals:read Proposal
GET /proposals proposals:read Strona listy ofert
POST /proposals/{id}/answers proposals:write 202 Proposal
POST /proposals/{id}/revise proposals:write 202 Proposal
PATCH /proposals/{id} proposals:write Proposal
POST /proposals/{id}/publish proposals:publish Proposal z public_url
POST /proposals/{id}/unpublish proposals:publish Proposal
POST /proposals/{id}/mark-sent proposals:write Proposal

Obiekt Proposal

Każdy endpoint oferty zwraca ten obiekt (listy zwracają jego krótszą wersję).

{
  "id": 123,
  "status": "ready_for_review",
  "questions": [],
  "error": null,
  "preflight": {
    "blockers": [],
    "warnings": [
      { "code": "price_without_tax_basis", "message": "Przy cenie nie ma informacji, czy to kwota netto, czy brutto." }
    ]
  },
  "revision": 7,
  "language": "pl",
  "currency": "PLN",
  "client": {
    "name": "Kwiaciarnia Lila",
    "website": "https://lila.example",
    "contact": { "name": "Anna Nowak", "email": "anna@lila.example" }
  },
  "brand": { "id": 42, "name": "Northwind Studio" },
  "facts": {
    "title": "Nowy sklep internetowy dla Kwiaciarni Lila",
    "scope": [
      "Sklep z sześcioma podstronami i wyborem daty dostawy",
      "Zdjęcia produktowe 40 bukietów",
      "Wsparcie przez pierwsze 30 dni po starcie"
    ],
    "price": "9 000 zł netto, 50% z góry, 50% po odbiorze",
    "valid_until": "2026-10-31"
  },
  "panel_url": "https://app.proposals.co/offers/123",
  "public_url": null,
  "engagement": {
    "sent_at": null,
    "first_opened_at": null,
    "last_opened_at": null,
    "opens": 0,
    "questions_asked": 0,
    "accepted_at": null
  },
  "deal_status": "open",
  "source": "api",
  "source_client": "Zapier – CRM",
  "external_reference": "crm-deal-731",
  "created_at": "2026-09-26T09:12:03Z",
  "updated_at": "2026-09-26T09:13:41Z",
  "next_poll_after_ms": null
}
Pole Uwagi
status generating, needs_input, ready_for_review, blocked, failed, draft, published, sent, opened, accepted. Znaczenie opisuje sekcja Status oferty.
questions Wypełnione, gdy status to needs_input. Każde pytanie ma kind (client, budget albo currency), treść question do pokazania użytkownikowi i options z tym, co znaleziono w notatkach.
error Wypełnione, gdy status to failed: code i zrozumiały dla człowieka message.
preflight Wynik kontroli przed publikacją. Każdy element blockers uniemożliwia publikację, a warnings jej nie blokują.
revision Numer rewizji szkicu. Odeślij go jako expected_revision, żeby mieć pewność, że zmieniasz wersję, którą ostatnio odczytano.
facts Wyliczane z samej oferty, bez AI. Korzystaj z nich, pisząc maila, i nie obiecuj niczego ponad nie.
panel_url Oferta w panelu Proposals. Wymaga zalogowania – to link dla Twojego zespołu, nie dla klienta.
public_url Link dla klienta. Do czasu publikacji ma wartość null; używaj go dokładnie w takiej postaci, w jakiej przyszedł.
engagement Co zrobił klient: kiedy oferta została wysłana i otwarta, ile razy, ile pytań zadał i kiedy ją zaakceptował.
deal_status open, won albo lost zgodnie z panelem, albo null. Przez API tylko do odczytu.
source, source_client Gdzie powstała oferta (panel, api, mcp) i przez który klucz lub aplikację.
next_poll_after_ms Tylko przy statusie generating: ile odczekać przed kolejnym odczytem.

Lista marek

GET /brands · zakres brands:read

curl https://api.proposals.co/api/public/v1/brands \
  -H "Authorization: Bearer $PROPOSALS_API_KEY"
{
  "items": [
    { "id": 42, "name": "Northwind Studio", "language": "en", "currency": "USD", "name_confirmed": true },
    { "id": 43, "name": "Northwind Polska", "language": "pl", "currency": "PLN", "name_confirmed": false }
  ]
}

Marka z name_confirmed: false może mieć szkice, ale jej oferty dostaną status blocked z kodem brand_name_unconfirmed, dopóki ktoś nie potwierdzi nazwy w panelu.

Utworzenie oferty

POST /proposals · zakres proposals:write · opcjonalny parametr wait_seconds (od 0 do 50)

curl -X POST https://api.proposals.co/api/public/v1/proposals \
  -H "Authorization: Bearer $PROPOSALS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: crm-deal-731-create" \
  -d '{
    "client": {
      "name": "Kwiaciarnia Lila",
      "website": "https://lila.example",
      "contact": { "name": "Anna Nowak", "email": "anna@lila.example" }
    },
    "context": "Notatki z rozmowy 26.09: chcą sprzedawać bukiety online z wyborem daty dostawy...",
    "brand_id": 43,
    "language": "pl",
    "pricing": {
      "amount": 9000,
      "currency": "PLN",
      "tax_basis": "net",
      "billing": "one_off",
      "schedule": "50% z góry, 50% po odbiorze"
    },
    "valid_until": "2026-10-31",
    "external_reference": "crm-deal-731"
  }'
Pole Wymagane Uwagi
client.name tak Od 1 do 255 znaków
client.website nie Publiczny adres https://. Proposals czyta stronę, żeby lepiej dopasować szkic.
client.contact nie name i email osoby, dla której jest oferta
context tak Notatki albo transkrypcja rozmowy, od 1 do 50 000 znaków. Traktowane jako informacje o sprzedaży, nigdy jako polecenia.
brand_id nie Z GET /brands. Bez niego używana jest Twoja domyślna marka.
language nie en albo pl
pricing nie Cena pokazywana w ofercie, szczegóły poniżej. Bez tego pola cena z notatek trafia do oferty dopiero po potwierdzeniu, tak jak w panelu.
valid_until nie Data ważności warunków, RRRR-MM-DD
external_reference nie Twój identyfikator tej szansy, do 128 znaków, unikalny w organizacji

Pola pricing:

Pole Uwagi
amount Większe od zera albo null, co oznacza „cena do ustalenia”. Nigdy nie jest zgadywane ani zamieniane na zero.
amount_max Opcjonalna górna granica widełek (musi być większa od amount)
currency PLN, USD, EUR albo GBP
tax_basis net, gross albo unknown (domyślnie)
billing one_off, monthly, quarterly, yearly, hourly, per_unit albo unknown (domyślnie)
schedule Harmonogram płatności opisany słowami, do 200 znaków

Cena z pricing ma pierwszeństwo przed kwotami z notatek. Jeśli w notatkach jest inna kwota, dostaniesz pytanie albo ostrzeżenie o rozbieżności.

Odpowiedź: 202 z obiektem Proposal w statusie generating i polem next_poll_after_ms. Z parametrem wait_seconds wywołanie czeka najwyżej tyle sekund i zwraca stan z tej chwili.

Błędy typowe dla tego wywołania: 409 external_reference_exists (z params.proposal_id), 404 dla brand_id, który nie należy do Ciebie, 429 too_many_generations oraz 402, gdy plan nie pozwala na kolejną ofertę.

Odczyt oferty

GET /proposals/{id} · zakres proposals:read

curl https://api.proposals.co/api/public/v1/proposals/123 \
  -H "Authorization: Bearer $PROPOSALS_API_KEY" \
  -H "Accept-Language: pl"

Zwraca obiekt Proposal. W trakcie pisania:

{ "id": 123, "status": "generating", "next_poll_after_ms": 4000, "...": "..." }

Gdy notatki wymagają wyjaśnienia:

{
  "id": 123,
  "status": "needs_input",
  "questions": [
    {
      "kind": "client",
      "question": "W notatkach pojawia się firma Centrum Ogrodnicze Lila, a klientem jest Kwiaciarnia Lila. Dla kogo jest ta oferta?",
      "options": { "client": "Kwiaciarnia Lila", "found": "Centrum Ogrodnicze Lila" }
    }
  ],
  "...": "..."
}

Dokładna zawartość options zależy od kind, a OpenAPI opisuje ją dla każdego rodzaju pytania.

Lista ofert

GET /proposals · zakres proposals:read

Parametr Uwagi
status Jeden ze statusów
updated_since Data i czas w formacie ISO 8601
external_reference Dokładne dopasowanie
source panel, api albo mcp
limit Najwyżej 50
cursor next_cursor z poprzedniej strony
curl "https://api.proposals.co/api/public/v1/proposals?source=api&limit=20" \
  -H "Authorization: Bearer $PROPOSALS_API_KEY"
{
  "items": [
    {
      "id": 123,
      "status": "opened",
      "client": { "name": "Kwiaciarnia Lila" },
      "source": "api",
      "external_reference": "crm-deal-731",
      "updated_at": "2026-09-27T14:02:11Z"
    }
  ],
  "next_cursor": "eyJpZCI6MTIzfQ"
}

Elementy listy to krótsza wersja obiektu Proposal (powyżej w skrócie, a pełną listę pól podaje OpenAPI). Na ostatniej stronie next_cursor ma wartość null.

Odpowiedzi na pytania i ponowienie

POST /proposals/{id}/answers · zakres proposals:write

Wyślij odpowiedzi użytkownika na pytania z questions. Każde pole jest opcjonalne – wysyłasz tylko to, o co zapytano.

Pole Odpowiada na pytanie rodzaju
confirm_client: true client: klient podany w żądaniu jest właściwy
client_name client: oferta jest dla tej firmy, a nie dla podanej wcześniej
pricing budget: właściwa cena, w tym samym kształcie co przy tworzeniu
currency currency: właściwa waluta
curl -X POST https://api.proposals.co/api/public/v1/proposals/123/answers \
  -H "Authorization: Bearer $PROPOSALS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: crm-deal-731-answers-1" \
  -d '{"confirm_client": true}'

Odpowiedź: 202 z obiektem Proposal ponownie w statusie generating.

Ponowienie po błędzie: gdy status to failed, po usunięciu przyczyny wyślij pustą treść ({}), żeby spróbować jeszcze raz.

Prośba o poprawkę

POST /proposals/{id}/revise · zakres proposals:write

curl -X POST https://api.proposals.co/api/public/v1/proposals/123/revise \
  -H "Authorization: Bearer $PROPOSALS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: crm-deal-731-revise-1" \
  -d '{"instruction": "Skróć sekcję o SEO i dodaj harmonogram na pięć tygodni.", "expected_revision": 7}'
Pole Uwagi
instruction Co zmienić, zwykłymi słowami, od 1 do 4000 znaków
expected_revision Opcjonalne. Jeśli szkic zmienił się od tej rewizji, dostaniesz 409 site_conflict i nic nie zostanie nadpisane.

Odpowiedź: 202 z obiektem Proposal w statusie generating. Po zakończeniu revision jest wyższy. Poprawka zmienia tylko szkic, więc jeśli oferta jest już opublikowana, klient zobaczy zmianę po ponownej publikacji.

Zmiana odbiorcy i warunków

PATCH /proposals/{id} · zakres proposals:write · bez Idempotency-Key

curl -X PATCH https://api.proposals.co/api/public/v1/proposals/123 \
  -H "Authorization: Bearer $PROPOSALS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"recipient": {"name": "Anna Nowak", "email": "anna@lila.example"}, "valid_until": "2026-11-15"}'
Pole Uwagi
recipient name i email osoby, dla której jest oferta
language en albo pl
currency PLN, USD, EUR albo GBP
valid_until RRRR-MM-DD
external_reference Do 128 znaków, unikalny w organizacji
client_name Nazwa firmy klienta
client_website Publiczna strona klienta, http albo https
service_type Co sprzedajesz, w kilku słowach
next_step O co oferta prosi klienta: accept, call albo variant
pricing Potwierdzone warunki, w tym samym kształcie co przy tworzeniu; jego currency musi się zgadzać z currency, gdy wysyłasz oba

Odpowiedź: zaktualizowany obiekt Proposal.

Publikacja

POST /proposals/{id}/publish · zakres proposals:publish

curl -X POST https://api.proposals.co/api/public/v1/proposals/123/publish \
  -H "Authorization: Bearer $PROPOSALS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: crm-deal-731-publish" \
  -d '{"confirm": true, "expected_revision": 7}'
Pole Uwagi
confirm Musi mieć wartość true
expected_revision Opcjonalne; publikuje tylko wtedy, gdy szkic nadal ma tę rewizję

Odpowiedź: obiekt Proposal ze statusem published i polem public_url:

{ "id": 123, "status": "published", "public_url": "https://app.proposals.co/northwind-polska/kwiaciarnia-lila?k=7Qm2xR", "...": "..." }

Jeśli kontrola przed publikacją znajdzie blokadę, dostaniesz 422 preflight_blockers i nic nie zostanie opublikowane. API nie ma opcji wymuszenia publikacji. Publikacja przez integrację wysyła powiadomienie właścicielowi oferty, ale niczego nie wysyła klientowi.

Cofnięcie publikacji

POST /proposals/{id}/unpublish · zakres proposals:publish · pusta treść

curl -X POST https://api.proposals.co/api/public/v1/proposals/123/unpublish \
  -H "Authorization: Bearer $PROPOSALS_API_KEY" \
  -H "Idempotency-Key: crm-deal-731-unpublish"

Odpowiedź: obiekt Proposal. Publiczny link przestaje działać dla klienta, a szkic zostaje w panelu.

Oznaczenie wysyłki

POST /proposals/{id}/mark-sent · zakres proposals:write

Wywołaj to po tym, jak Ty albo Twoje narzędzie pocztowe wyśle link klientowi. Od tej chwili liczy się czas do follow-upu, tak samo jak w panelu.

curl -X POST https://api.proposals.co/api/public/v1/proposals/123/mark-sent \
  -H "Authorization: Bearer $PROPOSALS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: crm-deal-731-mark-sent" \
  -d '{"sent_at": "2026-09-26T10:05:00Z"}'
Pole Uwagi
sent_at Opcjonalna data i czas w formacie ISO 8601; domyślnie teraz

Odpowiedź: obiekt Proposal ze statusem sent (albo dalszym) i ustawionym engagement.sent_at. Oferta jest zapisana jako wysłana poza Proposals.

Zacznij dziś

Wyślij następną ofertę jako stronę, na którą łatwo powiedzieć „tak”

Wklej notatki, sprawdź szkic, wyślij jeden link.

14 dni za darmo · 3 oferty w cenie · Bez karty