name: bc-marketing-manage-form-entries description: Use when staff search, filter, update, delete, or re-process existing academy FormEntry leads; do NOT use for creating leads, debugging storage_status failures, or public capture flows. requires: - breathecode-staff-api-index
Skill: Manage FormEntry Leads (Staff)
When to Use
- Use for searching, reading, updating, deleting, or re-processing existing academy
FormEntryrecords. - Use for listing won deals (
GET /v1/marketing/academy/lead/won). - Do NOT use for creating new leads — load
bc-marketing-create-form-entry. - Do NOT use for diagnosing
storage_status/ CRM failures — loadbc-marketing-debug-form-entry. - Do NOT use for attribution funnel analytics — load
bc-monitoring-read-report-acquisition.
Concepts
- Field semantics (
location,tags,automations,custom_fields, CRM requirements) live inbc-marketing-create-form-entry. storage_statusdiagnosis and retry guidance live inbc-marketing-debug-form-entry.- When ActiveCampaign double sync is enabled,
deal_statusandac_deal_*fields on GET may reflect salesperson CRM activity via reverse webhooks — not only staffPUTupdates. - Staff
PUTtriggersform_entry.changedplatform webhook but does not auto re-push to CRM. Useprocessto retry CRM sync.
Workflow
Set headers on all
/academy/routes:Authorization: Token <token>,Academy: <academy_id>, optionalAccept-Language(en,es) for translated errors.Search or list leads with filters (
GET /v1/marketing/academy/lead). Usestartandendfor date windows oncreated_at. Save leadidvalues from results.Get one lead by id (
GET /v1/marketing/academy/lead/<id>). UseFormEntryBigSerializerresponse for full diagnostics (storage_status,storage_status_text,ac_contact_id,ac_deal_*,deal_status).Update lead fields (
PUT /v1/marketing/academy/lead/<id>orPUT /v1/marketing/academy/lead?id=<id>). Does not auto-call CRM. Ifstorage_status=ERROR, fix fields then go to Step 5.Re-process CRM sync (
PUT /v1/marketing/academy/lead/process?id=<id>). Use after fixing data or for staff-created leads that were never processed. Addsync=trueto process immediately and returnstorage_statusin the response (default is async queue). See debug skill for when not to callprocess(DUPLICATED).Delete leads in bulk (
DELETE /v1/marketing/academy/lead?id=<id1>,<id2>). Requiresidquery param.List won leads (
GET /v1/marketing/academy/lead/won) withread_won_leadcapability. Supports same date and filter params as the main list.
Endpoints
All endpoints below require Authorization, Academy, and optional Accept-Language. Paths contain /academy/.
| Action | Method | Path | Capability | Notes |
|---|---|---|---|---|
| List / filter leads | GET | /v1/marketing/academy/lead |
read_lead |
Paginated (limit, offset). Default sort -created_at. |
| Get one lead | GET | /v1/marketing/academy/lead/<id> |
read_lead |
Returns full lead detail. |
| Update one lead | PUT | /v1/marketing/academy/lead/<id> |
crud_lead |
Triggers form_entry.changed. |
| Update multiple leads | PUT | /v1/marketing/academy/lead?id=<ids> |
crud_lead |
Comma-separated ids in query. |
| Delete leads | DELETE | /v1/marketing/academy/lead?id=<ids> |
crud_lead |
Bulk only via ?id= query. |
| Re-process CRM | PUT | /v1/marketing/academy/lead/process?id=<ids> |
crud_lead |
Default: queues persist_single_lead per lead. sync=true processes inline and returns results. |
| List won leads | GET | /v1/marketing/academy/lead/won |
read_won_lead |
Filters deal_status=WON. Paginated. |
List filters
| Query param | Filters on | Notes |
|---|---|---|
start |
created_at >= |
YYYY-MM-DD |
end |
created_at <= |
YYYY-MM-DD at midnight start-of-day — use the next calendar day to include the full last day |
storage_status |
storage_status |
e.g. ERROR, PENDING, PERSISTED, DUPLICATED |
deal_status |
deal_status |
Uppercased in lookup (WON, LOST) |
course |
course |
Comma-separated |
location / location_alias |
location |
Comma-separated |
deal_location |
ac_deal_location |
Comma-separated |
deal_course |
ac_deal_course |
Comma-separated |
ac_deal_id |
ac_deal_id |
Exact match |
utm_medium |
utm_medium |
icontains |
utm_url |
utm_url |
icontains |
utm_campaign |
utm_campaign |
icontains |
utm_source |
utm_source |
icontains |
utm_term |
utm_term |
icontains |
tags |
tag slugs | Comma-separated |
like |
name search | Full-name fuzzy match |
only_first |
— | true returns first match only (hook-oriented shape) |
limit, offset |
pagination | Default limit 20 |
Example request — list leads by date and status
GET /v1/marketing/academy/lead?start=2026-04-01&end=2026-04-08&storage_status=ERROR&limit=20&offset=0
Authorization: Token <token>
Academy: 4
Example response — paginated list (subset)
{
"count": 3,
"first": null,
"next": null,
"previous": null,
"last": null,
"results": [
{
"id": 219384,
"first_name": "Lucia",
"last_name": "Mendez",
"email": "lucia@example.com",
"phone": "+34600000000",
"course": "full-stack",
"location": "barcelona-spain",
"storage_status": "ERROR",
"storage_status_text": "You need to specify tags for this entry",
"deal_status": null,
"created_at": "2026-04-03T15:29:11.532Z"
}
]
}
Example request — get one lead
GET /v1/marketing/academy/lead/219384
Authorization: Token <token>
Academy: 4
Example response — single lead (subset)
{
"id": 219384,
"first_name": "Lucia",
"last_name": "Mendez",
"email": "lucia@example.com",
"phone": "+34600000000",
"course": "full-stack",
"location": "barcelona-spain",
"tags": "website-lead",
"storage_status": "ERROR",
"storage_status_text": "You need to specify tags for this entry",
"ac_contact_id": null,
"ac_deal_id": null,
"deal_status": null,
"won_at": null,
"referral_key": "partner-acme-01",
"utm_source": "facebook",
"utm_campaign": "barcelona-bootcamp-q2",
"custom_fields": {},
"created_at": "2026-04-03T15:29:11.532Z",
"updated_at": "2026-04-03T15:29:12.100Z"
}
Example request — update lead (fix tags before retry)
PUT /v1/marketing/academy/lead/219384
Authorization: Token <token>
Academy: 4
Content-Type: application/json
{
"tags": "website-lead",
"course": "full-stack"
}
Example response — update
Same shape as get-one response with updated fields.
| Query param | Notes |
|---|---|
id |
Required. Comma-separated lead ids. |
sync |
Optional. true, 1, or yes runs CRM sync inline instead of Celery queue. Response includes results with id, storage_status, storage_status_text per lead. |
Example request — re-process CRM
PUT /v1/marketing/academy/lead/process?id=219384
Authorization: Token <token>
Academy: 4
Example response — process queued
{
"details": "1 leads added to the processing queue"
}
Poll GET /v1/marketing/academy/lead/219384 until storage_status is PERSISTED, DUPLICATED, or stable ERROR (skip polling when sync=true was used).
Example request — re-process CRM synchronously
PUT /v1/marketing/academy/lead/process?id=219384&sync=true
Authorization: Token <token>
Academy: 4
Example response — process sync
{
"details": "1 leads processed",
"results": [
{
"id": 219384,
"storage_status": "PERSISTED",
"storage_status_text": ""
}
]
}
Example request — delete leads
DELETE /v1/marketing/academy/lead?id=219384,219385
Authorization: Token <token>
Academy: 4
Returns 204 No Content on success.
Example request — list won leads
GET /v1/marketing/academy/lead/won?start=2026-04-01&end=2026-04-30
Authorization: Token <token>
Academy: 4
Paginated list of leads with deal_status=WON.
Edge Cases
- Invalid filter values: returns empty
results— do not assume a bug; verify filter spelling and academy scope. - GET one wrong id: returns
404withlead-not-found. - PUT wrong id in bulk: returns
400withlead-not-foundif no matches. - Delete without
?id=: returns400— bulk delete requires query param ids. storage_status=ERROR: load debug skill; readstorage_status_text, fix fields, thenprocess.storage_status=DUPLICATED: do not callprocess— intentional dedup within ~30 minutes.- Date filter
endcaveat:end=2026-04-07excludes leads created after midnight on April 7; useend=2026-04-08to include all of April 7.
Checklist
- Sent
AuthorizationandAcademyheaders on every request. - Used correct date params (
start/end, notstarted_at/ended_at). - Applied
enddate midnight caveat when filtering by last day. - Retrieved lead
idbefore update, process, or delete. - Called
processafter staff create or after fixingERRORleads (unlessDUPLICATED). - Loaded debug skill if
storage_statusdid not reach expected terminal state afterprocess.