Create and update drafts
Create and update encrypted form drafts with the Qubeforms API. Save partial fields, authorize changes with draft tokens, handle expiration, and submit completed forms.
1. Create a partial draft
Drafts are available on all plans for active forms and allowed origins. POST named fields to /v1/{publicKey}/drafts; _cf_step defaults to 1. Include at least one normal field. Drafts save field data, not files, and do not send final notifications or consume monthly accepted-submission quota. They have separate request and storage limits.
curl --fail-with-body \
'https://api.qubeforms.io/v1/cf_pk_YOUR_KEY/drafts' \
--header 'Origin: https://www.example.com' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{"email":"alex@example.com","_cf_step":1,"fax_number":""}'2. Keep the returned credentials
Creation returns HTTP 201 with draft_id, draft_token, and expires_in in seconds. The default lifetime is 72 hours (259,200 seconds), measured from creation. Keep the token private and retain both values for updates and completion. This is an illustrative response.
{
"draft_id": "EXAMPLE_DRAFT_ID",
"draft_token": "cf_draft_EXAMPLE_TOKEN",
"expires_in": 259200
}3. Update the draft
Use PATCH or PUT with the same form key and the returned draft ID. Set QUBEFORMS_DRAFT_TOKEN to the returned token in this shell example. Updates recursively merge fields with saved data; omitted values remain, including nested array entries. The recorded step never decreases. A successful update returns HTTP 200 with draft_id, step, and saved: true. Updates do not extend expiration.
curl --fail-with-body --request PATCH \
'https://api.qubeforms.io/v1/cf_pk_YOUR_KEY/drafts/EXAMPLE_DRAFT_ID' \
--header 'Origin: https://www.example.com' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header "X-Qubeforms-Draft-Token: ${QUBEFORMS_DRAFT_TOKEN}" \
--data '{"message":"Please contact me.","_cf_step":2}'4. Complete through the submission endpoint
POST to /v1/{publicKey}/submissions with _cf_draft_id and _cf_draft_token in the body, plus final field values and fresh CAPTCHA data when required. Use multipart data if sending files. The final fields override matching saved fields through a recursive merge. A successful final submission consumes the draft. Wait for any pending save before finishing, and clear local credentials only after confirmed success.
5. Handle unavailable drafts
404 can mean a missing draft, wrong token, wrong form, completed draft, or a purged record. 410 means an expired draft still present in storage. 429 indicates a request or draft storage limit; honor Retry-After. Draft creation has no idempotency support, so repeating a create request can allocate another draft.
6. Build the visitor experience
There is no customer GET endpoint to fetch saved draft fields, and no automatic resume email. Keep display state in your application and explain whether it survives a reload. A selected file must stay in the browser until final upload and can require reselection. See Multi-step drafts, Multi-step forms in React, or Multi-step forms in Vue for integration walkthroughs and Draft saving and duplicate submissions for recovery.
Public form keys can appear in browser code. Read keys, management credentials, and webhook signing secrets must stay on the server.