Skip to main content
A buyer RFP is the standing ask you put to the market: what you want, for which advertiser, so sellers can answer with proposals. It belongs to one advertiser, never to an agency as a whole, and it stays on record so you can come back to it, change it while it is a draft, or copy it into your next one. It is not the seller-side RFP record described in Seller RFP lifecycle, and it is not a proposal, which is a seller’s answer. This page covers what you can do with an RFP today: draft it, change it, copy it and read it back. Nothing is sent to a seller yet. Opening an RFP to sellers comes in a later release, and campaigns keep working as they do now.
Buyer RFPs are part of the buyer beta (the V4 beta feature profile). An account that is not on the beta is refused with FEATURE_NOT_ENABLED on every call that reads or writes an RFP, and an account whose profile cannot be read is refused with SERVICE_UNAVAILABLE, never let through. Your profile is described in Accepting a plan sets what your account can do.

What an RFP holds

Only the name is required. The thinner the ask, the vaguer the proposals that will come back, so add what you know. flight, budgetRange, channels and geo use AdCP’s own shapes and names, so they will go to a seller as the matching AdCP offer filters (start_date and end_date, budget_range, channels, countries and regions) when sending ships. Every read returns the same names the save takes, plus these read-only fields:

Every RFP belongs to a plan

A plan is one planning cycle for one advertiser. It is not a billing plan such as Business Manager. When you create an RFP, Apostra makes the plan for you, so you never have to name one. The plan’s ID, planId, is the AdCP opportunity_id that every request under the plan carries to a seller, so a seller will see one conversation per plan. The ID is opaque and never changes. Apostra does not yet let you put a second RFP into an existing plan or name a plan yourself.

Create a draft

Call save_buyer_rfp with advertiserId, name and an idempotencyKey. Add any ask fields you have.
The result names the saved RFP under buyerRfp, with action: "created". The idempotencyKey is yours to choose, 16 to 255 letters, digits and _ . : -. Send the same key again and you get the same RFP back with replayed: true. Reuse a key for a different request and the call is refused with a conflict.

Change a draft

Call save_buyer_rfp with rfpId, the expectedRevision from your last read, and the fields to change. A field you leave out stays. null clears an optional field.
  • A stale expectedRevision is refused as a conflict that names the current revision. Read the RFP again and retry.
  • Only a draft changes. An RFP that is open or closed refuses the change, and the message tells you to copy it.
  • An update takes no idempotencyKey, no advertiserId and no duplicateFromRfpId: the revision is what makes a retry safe.

Copy an RFP into a new draft

Call save_buyer_rfp with duplicateFromRfpId and an idempotencyKey. The copy takes the source’s ask, gets a new RFP ID, and gets a new plan, because a re-run is a new planning cycle. Its name is the source’s name with ” (copy)”, or the name you send. The source is untouched. Duplicating takes no other ask fields: copy first, then change the copy.

Read RFPs back

  • get with kind: "buyer_rfp" and the id of an RFP returns it.
  • search with kind: "buyer_rfp" and filter.advertiserId lists that advertiser’s RFPs, newest first, in pages. Narrow it with filter.rfpStatus. It takes no text query. When a page has more, it returns a nextCursor.
You can only read and change the RFPs of advertisers your account can reach. An RFP that belongs to another account answers NOT_FOUND, the same as one that does not exist.

What is not here yet

  • Opening an RFP to sellers, extending it, closing it and reopening it.
  • Proposals arriving on an RFP, and turning them into campaigns.
  • Naming a plan, or putting more than one RFP in a plan.
When these ship, this page will say how they work.