Listing deals
GET /v1/deals returns the deals available to your partner account — the global FounderPass catalog plus any live partner-private deals you've created — with every partner-level visibility rule applied (global rules, partner/partner-type targeting, channel exclusion, and your per-deal toggles). It is partner-scoped: it does not take an end user, so it never applies per-member region, user-type, or eligibility gates. Those are enforced per member at GET /v1/deals/{id}/instructions (and on the hosted end-user listing).
A simple call
curl "https://perks.founderpass.com/v1/deals?page=1&per_page=20" \
-H "Authorization: Bearer $TOKEN"
Narrowing the listing
Pass query parameters to narrow the listing.
curl "https://perks.founderpass.com/v1/deals?\
tier=premium&\
category=cloud-infrastructure" \
-H "Authorization: Bearer $TOKEN"
| Parameter | Type | Notes |
|---|---|---|
category | string | Category slug — see GET /v1/categories |
tier | enum | free or premium — filters by the deal's tier |
page, per_page | integer | Standard pagination (per_page max 100) |
There's no region or user_type parameter: this endpoint is partner-scoped (see above). To narrow by region, read each deal's available_regions and filter client-side (see the tip below).
:::tip Partner-level visibility + freemium gating are evaluated server-side
You don't need to combine filters with your own partner-level access logic. The deal listing already respects the partner-level visibility rules attached to a deal (global hide, partner/partner-type targeting, your per-deal toggles) and your partner's freemium settings. Deals an admin has marked api_excluded never appear in the Partner API on any path (list, detail, or instructions), even though they remain live in the hosted app. Per-member region/user-type/eligibility gates are not applied here — enforce those per member at GET /v1/deals/{id}/instructions.
:::
Eligibility criteria on each deal
List and detail responses include the deal's eligibility so you can display requirements or filter on your side:
eligibility_requirements— a human-readable string (e.g. "Must be a VC-backed company founded in the last 5 years"). Render it on the deal card or detail page.- Structured arrays for programmatic checks — empty means "no constraint on this dimension":
| Field | Example values |
|---|---|
eligibility_customer_type | new_customers_only, existing_customers_eligible |
eligibility_company_stage | pre_seed, seed, series_a_plus, smb, enterprise, any |
eligibility_company_size | 1_10, 11_50, 51_200, 200_plus |
eligibility_business_type | startup, agency, ecommerce, saas, creator, local_business, any |
eligibility_geography | ISO-3166-1 alpha-2 country codes — e.g. US, DE, GB (uppercase). Not AudienceRegion names — those apply only to regions / visibility rules, not to eligibility. |
eligibility_incorporation | registered_company_required, sole_traders_allowed |
eligibility_funding | vc_backed_only, accelerator_only, student_founders, coworking_members, portfolio_companies_only |
These are informational on the Partner API — surface them to set expectations, but the offering partner still performs the actual eligibility check at redemption.
Fetching a single deal
curl "https://perks.founderpass.com/v1/deals/{id}?user_tier=premium" \
-H "Authorization: Bearer $TOKEN"
Returns the full deal object. Optional user_tier query param enforces tier gating — if the user is free and the deal is premium, you'll get a 403 with reason: "tier_upgrade_required" and upgrade_required: true.
Getting redemption instructions
When a user clicks "Claim", fetch their redemption details. As a side effect the endpoint records an instructions_viewed event and auto-emits a deal_redeemed event for that user — once per (partner, user, deal), so you don't track redemptions on the API path yourself.
curl "https://perks.founderpass.com/v1/deals/{id}/instructions?user_id={user_uuid}" \
-H "Authorization: Bearer $TOKEN"
user_id is required as a query parameter — the server checks that user's tier against the deal's tier and returns 403 with reason: "tier_upgrade_required" and upgrade_required: true if they don't qualify.
:::info Status codes to handle
403{ "reason": "tier_upgrade_required", "upgrade_required": true }— the user's tier is below the deal's.404— the deal isn't redeemable for this user. The body tells you why for the gates you can act on:{ "reason": "region_restricted" }— the member'sregionsdon't satisfy the deal's region targeting: either they include a region the deal excludes, or they're absent from the deal's include-region list. Sync the member's actual region.{ "reason": "user_type_restricted" }— the deal is limited to certain user types the member isn't in.{ "reason": "eligibility_mismatch", "dimension": "eligibility_company_stage" }— the member's eligibility doesn't overlap the deal on the named dimension. Set that dimension on the member.- No
reason(bare{ "error": ... }) — the deal isn't available to your account (hidden,api_excluded, or not in your catalogue); nothing to fix at the member level.
409{ "deal_status": "coming_soon" }— the deal is announced but not yet redeemable; its redemption assets are withheld. Treat this as "skip/flag", not an error to retry. (GET /v1/dealsalready carries each deal'sstatus, so you can filtercoming_soonout before reaching this call.) :::
:::tip Pre-filter on region with available_regions
List and detail responses include available_regions — the regions a deal is redeemable in, derived from the deal's region restrictions, so you can filter deals to a member's region before attempting redemption rather than discovering a region_restricted 404. It has three states: omitted = available everywhere; a non-empty list = redeemable only in those regions; an empty list [] = the region rules resolve to no regions — don't treat [] as "everywhere". Note this is distinct from eligibility_geography (an eligibility dimension using ISO-3166 country codes); available_regions uses named regions and reflects the deal's geo visibility.
:::
:::tip Hide un-claimable deals with claimable_without_eligibility
List and detail responses also include claimable_without_eligibility (boolean). It's true when a default member — one you create with just an email — can redeem the deal right now with nothing extra supplied, and false when a default member can't: blocked by a per-member gate (a targeted region, or a non-default eligibility dimension such as eligibility_company_stage or eligibility_funding), or the deal isn't redeemable yet (coming_soon). Filter or flag on it instead of maintaining your own list of un-claimable deals. It's partner-relative (region default = your account's regions) and excludes tier (a premium-only deal is a separate tier_upgrade_required gate). A default member carries no user_types, so user-type-only gates don't make a deal un-claimable.
:::
Response
{
"instructions": "<ul><li>Visit the URL</li><li>Enter the promo code</li></ul>",
"promo_code": "FOUNDER20",
"redeem_url": "https://vendor.com/redeem",
"contact_email": "support@vendor.com"
}
:::tip Cache the listing per query, not per user
The listing is partner-scoped, so the same (tier, category, page) tuple produces the same response regardless of which member you're serving. Cache by tuple, not by user_id, to keep cardinality manageable. Redemption instructions, on the other hand, are per-user — don't cache those.
:::
See the full filter set and response schema in the Deals API Reference.