# Lucra > Lucra is where brands run creator-powered conversion ads: recruit creators, brief them, approve their videos, launch the best work as paid ads on Google, Meta, and TikTok, and pay creators on verified performance. The REST API lives at https://api.onlucra.com/v1; send `Lucra-Version: 2026-10-01` and `Authorization: Bearer ` on every request. OpenAPI: https://api.onlucra.com/v1/openapi.json. TypeScript SDK: `npm install @lucra/sdk`, one typed method per endpoint named by its operationId (e.g. `lucra.programs.list()`). Every endpoint with its request and response fields: https://www.onlucra.com/docs/api-reference. IDs are prefixed and opaque; lists are newest first with an opaque cursor; money is integer cents with a lowercase currency; enums can gain values. The MCP server lives at https://api.onlucra.com/api/mcp and exposes one tool per endpoint (except file upload). It takes OAuth only: the client signs in to Lucra and a member grants read, or read and write, access to one account; API keys are refused. Every page below is also Markdown at its URL plus `.md`, and https://www.onlucra.com/llms-full.txt holds them all. ## Documentation - [Quickstart](https://www.onlucra.com/docs): What Lucra does, and where to start. - [Workspace setup](https://www.onlucra.com/docs/workspace-setup): Your brand profile, ad accounts, store and team. - [Programs & reviews](https://www.onlucra.com/docs/programs): Create a program, add creators and review their videos. - [Creators & products](https://www.onlucra.com/docs/creators-products): Invite creators, manage your roster and send samples. - [Paid ads](https://www.onlucra.com/docs/paid-ads): Run creator videos as ads and see what they drive. - [Tracking](https://www.onlucra.com/docs/tracking): Install the Lucra Pixel and credit creators for sales on your site. - [Creator setup](https://www.onlucra.com/docs/creator-setup): Sign up, connect your socials and verify with Stripe. - [Find & submit work](https://www.onlucra.com/docs/creator-work): Join programs, submit videos and request samples. - [Earnings](https://www.onlucra.com/docs/creator-earnings): How you earn, when you get paid, and withdrawing. - [Referrals](https://www.onlucra.com/docs/referrals): Earn by inviting brands and creators to Lucra. - [Partner workspaces](https://www.onlucra.com/docs/partners): Run client brands and a creator roster, and set your rates. - [Pricing & billing](https://www.onlucra.com/docs/billing): Fees, paying, and your ledger. - [FAQ](https://www.onlucra.com/docs/faq) ## Reference - [Introduction](https://www.onlucra.com/docs/api-introduction): Your first request, IDs, lists, errors and retries. - [SDK](https://www.onlucra.com/docs/sdk): The TypeScript client: calls, pagination, errors and embeds. - [Authentication](https://www.onlucra.com/docs/authentication): API keys, OAuth tokens and the accounts they reach. - [Access](https://www.onlucra.com/docs/access): What read and write keys and grants can do. - [OAuth](https://www.onlucra.com/docs/oauth): Let other Lucra accounts connect your app. - [Webhooks](https://www.onlucra.com/docs/webhooks): Events Lucra sends your server, and verifying them. - [Sandbox](https://www.onlucra.com/docs/sandbox): Test an integration with simulated ad platforms and no payments. - [Account](https://www.onlucra.com/docs/api-account): The account a key belongs to, and its setup. - [Brands](https://www.onlucra.com/docs/api-brands): Create and manage a partner's client brands. - [Creators](https://www.onlucra.com/docs/api-creators): Creators you work with, and a partner's roster. - [Connections](https://www.onlucra.com/docs/api-connections): Ad accounts, stores, socials, payouts and cards. - [Programs](https://www.onlucra.com/docs/api-programs): Programs, pay, applications and agreements. - [Submissions](https://www.onlucra.com/docs/api-submissions): Creators' videos and posts, and reviewing them. - [Files](https://www.onlucra.com/docs/api-files): Upload videos, images and PDFs. - [Ads](https://www.onlucra.com/docs/api-ads): Campaigns, ad sets, ads and daily results. - [Attribution](https://www.onlucra.com/docs/api-attribution): Pixels, server events, conversions and creator codes. - [Payments](https://www.onlucra.com/docs/api-payments): Paying creators, retainers, balances and withdrawals. - [Commerce](https://www.onlucra.com/docs/api-commerce): Products and samples. - [Messages](https://www.onlucra.com/docs/api-messages): Conversations with creators. - [Partners](https://www.onlucra.com/docs/api-partners): Run Lucra for client brands and creators, like Stripe Connect. - [Embeds](https://www.onlucra.com/docs/embeds): Put Lucra's screens inside your app. - [All endpoints](https://www.onlucra.com/docs/api-reference): Every endpoint and field. ## Agents - [Overview](https://www.onlucra.com/docs/agents): Let AI agents work in Lucra, within limits you set. - [MCP server](https://www.onlucra.com/docs/mcp): Connect an AI client with OAuth. - [llms.txt](https://www.onlucra.com/docs/llms-txt): These docs, indexed for coding agents. --- # Quickstart > What Lucra does, and where to start. Source: https://www.onlucra.com/docs Lucra runs creator programs, payments and ads. Brands post programs, creators make videos for them, and approved videos run as ads. Lucra tracks what each video drives and pays the creator for it. Pick your path below. When you sign up you choose **Brand**, **Agency**, **Developer** or **Creator**; Agency and Developer both open a partner account. ## Brands 1. [Set up your workspace](https://www.onlucra.com/docs/workspace-setup): your profile, team and ad accounts. 2. [Create a program](https://www.onlucra.com/docs/programs) and add creators to it. 3. Approve their videos and [run them as ads](https://www.onlucra.com/docs/paid-ads). 4. [Install the pixel](https://www.onlucra.com/docs/tracking) to credit creators for sales on your site. ## Creators 1. [Connect a social account and Stripe](https://www.onlucra.com/docs/creator-setup). 2. [Join programs and submit videos](https://www.onlucra.com/docs/creator-work). 3. [Get paid](https://www.onlucra.com/docs/creator-earnings) as your work performs. ## Partners Agencies and developers run Lucra for client brands and a roster of creators. Start with [Partner workspaces](https://www.onlucra.com/docs/partners), or [Partners in the API](https://www.onlucra.com/docs/api-partners) if you're building a product on Lucra. ## Developers Create a key in **Settings → Developers → API**, then install the SDK: ```bash npm install @lucra/sdk ``` List your programs: ```ts import { Lucra } from "@lucra/sdk" const lucra = new Lucra(process.env.LUCRA_API_KEY!) const { data: page } = await lucra.programs.list() console.log(page.data) ``` Next, read the [API introduction](https://www.onlucra.com/docs/api-introduction) and the [SDK guide](https://www.onlucra.com/docs/sdk). --- # Workspace setup > Your brand profile, ad accounts, store and team. Source: https://www.onlucra.com/docs/workspace-setup Set up your brand once, before your first program: your profile, your ad accounts and store, and your team. ## Brand profile Add your brand's name and a square logo in **Settings → Organization**. Creators see them on briefs, invites and reports. ## Integrations Connect your ad accounts and store in **Settings → Integrations**. Lucra runs ads on your own ad accounts, so connect the platforms you advertise on. | Integration | Used for | | ----------- | ------------------------------------------------------------ | | Google Ads | Demand Gen campaigns | | Meta Ads | Brand ads and Partnership Ads | | TikTok Ads | Brand ads and creator-account ads | | Snapchat | Brand ads | | Shopify | Products, sample orders and [tracking](https://www.onlucra.com/docs/tracking) | If a connection expires, reconnect it from the same row. Pause a platform's campaigns before you disconnect it. ## Team Invite teammates in **Settings → Team**. **Owners** and **Admins** can do everything. **Members** see everything and message creators, but can't change programs, campaigns, products, billing or settings. ## Partners A partner (an agency) can run programs and ads for you. Find one in **Explore → Agencies**, then control what it can do in **Settings → Partner access**. See [Partner workspaces](https://www.onlucra.com/docs/partners). --- # Programs & reviews > Create a program, add creators and review their videos. Source: https://www.onlucra.com/docs/programs A program is what you offer creators: a brief, what to submit, and how you pay. Creators join it, submit videos, and you review them. ## Create a program Go to **Programs → Launch Program** and set: - **Product**: what creators promote. Approved creators can request samples of it. - **Access**: open to applications, or invite only. - **Submissions**: how many videos each creator makes, and whether they upload them or post them. - **Brief**: a welcome message and any files. - **Ads**: run approved videos as your brand, or as the creator. - **Pay**: per approved video, a share of ad spend, or per organic post. You need a payment method on file before you launch. Add one in **Finance**. ## Add creators Add creators you already work with from **Creators**, or from **Add creators** on the program. They join approved and get notified. To bring in someone new, use **Creators → Invite Creator** and pick the program. They join it when they accept. In an open program, creators can also apply. Approve or reject applications from the program's **Creators** tab, one at a time or in bulk. ## Review videos Each submitted video waits for your review: - **Approve**: it's ready to [run as an ad](https://www.onlucra.com/docs/paid-ads). - **Request revision**: the creator resubmits with your note. - **Reject**: it's declined. --- # Creators & products > Invite creators, manage your roster and send samples. Source: https://www.onlucra.com/docs/creators-products Your roster is every creator who works with you. Invite creators here, see how they're doing, and send them products to feature. ## Invite creators In **Creators → Invite Creator**, send an email invite or copy a reusable link. Pick the programs to add them to; they join approved when they accept. Creators already working with you can be added to more programs at any time, one at a time or in bulk. ## Your roster **Creators** lists each creator's status, programs, videos waiting on you, and recent activity. Open one to see their profile and past work. ## Products Sync your catalog from Shopify, or add products by hand. A product can have an access code, which creators redeem instead of placing an order. Attach products to a program to offer them as samples. ## Samples Creators in a program can request samples of its products. When you approve a request, Lucra sends the product's access code or a free Shopify checkout. You can also send a sample to a creator directly. --- # Paid ads > Run creator videos as ads and see what they drive. Source: https://www.onlucra.com/docs/paid-ads Run approved creator videos as ads on your own ad accounts, and see what each creator's work drives. Connect your ad accounts in [Settings → Integrations](https://www.onlucra.com/docs/workspace-setup#integrations) first. ## Launch a campaign In **Paid Ads**, pick approved videos and the platforms to run on: Google, Meta, TikTok or Snapchat. Lucra builds a conversion campaign on each, optimized for the event you choose, like a purchase or a sign-up. Ads run as your brand, or as the creator when the program says so. On Meta, creator-run ads are Partnership Ads the creator approves. On TikTok, the creator approves their account for your ads once, ads-only, and Lucra publishes each video under their account without touching their profile. ## Results Spend, clicks and conversions sync from each platform into **Analytics**, broken down by creator and video. To credit sales on your own site too, [install the pixel](https://www.onlucra.com/docs/tracking). ## Billing You pay the ad platform for media directly, through your ad account. Lucra bills its fee and the creators' share of spend. See [Pricing & billing](https://www.onlucra.com/docs/billing). --- # Tracking > Install the Lucra Pixel and credit creators for sales on your site. Source: https://www.onlucra.com/docs/tracking The Lucra Pixel records what visitors do on your site and credits each sale or sign-up to the creator who sent them. Lucra uses it to report what creators drive, and to pay them for it. Start by installing the pixel. Then decide which events count as [conversions](#conversions). ## Install On Shopify? Skip to [Shopify](#shopify); there's no code to add. Create a pixel in **Settings → Lucra Pixel** with the domains it runs on. Paste its snippet before `` on every page: ```html ``` It records a `page_view` on every page, including navigations in a single-page app. The pixel shows **Verified** once the first event arrives. ### Recommended: load it from your own domain Proxy `/lucra/*` on your site to `https://a.onlucra.com/*`, then use the first-party snippet from the same page: ```html ``` This does two things. The pixel keeps visitors' attribution in a first-party `_lucra` cookie on your domain, which Safari keeps past its 7-day limit on script storage. And it loads like your own script, so blockers that filter third-party scripts leave it alone. Next.js, in `next.config.js`: ```js module.exports = { async rewrites() { return [{ source: "/lucra/:path*", destination: "https://a.onlucra.com/:path*" }] }, } ``` Vercel, in `vercel.json`: ```json { "rewrites": [{ "source": "/lucra/:path*", "destination": "https://a.onlucra.com/:path*" }] } ``` Cloudflare, a Worker on the route `example.com/lucra/*`: ```js export default { fetch(request) { const url = new URL(request.url) return fetch(`https://a.onlucra.com${url.pathname.slice(6)}${url.search}`, request) }, } ``` nginx: ```text location /lucra/ { proxy_pass https://a.onlucra.com/; proxy_ssl_server_name on; } ``` ## Shopify In **Settings → Lucra Pixel**, choose **Install on Shopify**. This installs the Lucra Shopify app (or asks your store to approve tracking if it's already connected). It adds the pixel to your store, records page views, product views, carts and checkouts, and confirms every paid order from Shopify's servers. It runs only where the visitor allows marketing and analytics tracking. ## Track events The pixel records page views on its own. Call `lucra.track` for everything else, like a purchase on your order page: ```js lucra.track("purchase", { value: 4999, // cents currency: "USD", orderId: "1042", discountCode: "SPRING10", contents: [{ id: "jacket-01", quantity: 1, price: 4999 }], user: { email: "jo@example.com" }, }) ``` Call it once the script has loaded. Code that may run first can queue the call instead: `lucra.push(["track", "purchase", { … }])`. Use a standard event name where one fits: | Event | When | | ---------------- | --------------------- | | `page_view` | Sent for you | | `view_content` | Viewed a product | | `search` | Searched | | `add_to_cart` | Added to cart | | `begin_checkout` | Started checkout | | `purchase` | Bought | | `lead` | Became a lead | | `sign_up` | Created an account | | `start_trial` | Started a trial | | `subscribe` | Subscribed | | `contact` | Contacted you | Name your own events in snake_case, like `booked_demo`. Every field is optional: | Field | What | | ----------------------------------- | ---------------------------------------------------------------------------- | | `value`, `currency` | In cents, with an ISO 4217 code. Send both or neither. | | `orderId` | Your order's ID. | | `eventId` | Your own ID, to match a [server copy](#server-events). Generated if left out. | | `discountCode` | The order's discount code, recorded with the event. | | `productId`, `variantId`, `quantity` | One product. | | `contents` | Up to 100 items: `{ id, quantity, price }`, price in cents. | | `custom` | Up to 25 of your own fields: strings, numbers or booleans. | | `user` | `email`, `phone`, `firstName`, `lastName`, `city`, `state`, `zip`, `country`, `externalId`. | ## Identify Once you know who the visitor is, send their details with every later event: ```js lucra.identify({ email: "jo@example.com", externalId: "user_123" }) ``` Details are hashed on arrival; see [Data & privacy](#data--privacy). ## Consent Consent is implied: the pixel records with consent unless you or the visitor say otherwise, so most US sites need nothing. Two defaults follow the law for you: - Visitors in the EU, EEA, UK and Switzerland start at `unknown` (opt-in is required there) until you call `lucra.consent("granted")`. - A browser sending Global Privacy Control starts at `denied` (an opt-out under California's CCPA). With a consent banner, pass its answer and update it when the visitor changes it; what you pass always wins: ```js lucra.push(["init", { siteId: "pxl_0Q2m8VxR4sTnYb7KcLd3Ew", consent: "unknown" }]) lucra.consent("granted") // or "denied" ``` Until consent is `granted`, events carry nothing that identifies the visitor: no visitor ID, personal details, click IDs or IP, and stored attribution is cleared. They still count, with their place and device. ## Server events Send conversions from your server with an API key, so ad blockers and closed tabs can't lose them: ```bash curl https://api.onlucra.com/v1/pixels/pxl_0Q2m8VxR4sTnYb7KcLd3Ew/events \ -H "Authorization: Bearer $LUCRA_API_KEY" \ -H "Lucra-Version: 2026-10-01" \ -H "Content-Type: application/json" \ -d '{ "name": "purchase", "eventId": "order-1042", "orderId": "1042", "value": 4999, "currency": "USD", "pageUrl": "https://shop.example.com/thanks", "lucraId": "sub_14XA4WGiSOmaVI3B4QLm4Fs", "user": { "email": "jo@example.com" } }' ``` To count a conversion once when both fire, pass the same `eventId` to `lucra.track` in the browser and to your server. The server copy confirms the browser event and fills in what it lacked (value, order, customer details), and only server-confirmed events pay creators by default. A server event credits a creator only if it carries what the visitor came with: `lucraId` from the landing URL, and `campaign` from its `lucra_campaign`. All fields are in the [API reference](https://www.onlucra.com/docs/api-attribution#server-events). ## Creator links Links on Lucra ads carry `lucra_id`, the video the visitor came from, which credits its creator, and `lucra_campaign`, the paid campaign. The pixel also reads UTM tags and ad click IDs (`gclid`, `fbclid`, `ttclid` and others), and remembers the latest creator link for the visitor. ## Conversions A conversion is the goal you count: a `purchase`, a `sign_up`, or your own event, narrowed by page (`pageUrlContains`) or a minimum value. Create them in **Settings → Lucra Pixel** or with `POST /v1/conversions`. An event is credited to a creator when the visitor converts within the conversion's attribution window (30 days by default, up to 90) of landing from their video. Events with no landing time, like a server event, are credited too. ### Pay per conversion Programs and retainers can pay creators for conversions credited to them: ```json { "conversion": "conv_1fK7pQ2wXy9LmN4bVc8RtZa", "amount": 500 } ``` That's $5 per conversion; `{ "conversion": "conv_…", "percent": 10 }` pays 10% of each conversion's value instead (an affiliate commission). A retainer can pay only on conversions, like $5 per sign-up each week; on a program, a conversion rule pays alongside its usual rule. Lucra pays them like other results, under the `cap`. See [pay](https://www.onlucra.com/docs/api-payments#pay). - Only server-confirmed events pay: [server events](#server-events) and Shopify orders. Set `payBrowserEvents: true` on a conversion to pay on browser-only events too, but anyone with your pixel ID can send those. - A `percent` counts only events valued in USD for now. - A credited conversion counts toward every program and retainer of that creator that pays on it, except one from a paid campaign that [credits creators with Lucra](https://www.onlucra.com/docs/api-ads#how-creators-are-credited): that campaign pays it. ## Reports Each conversion shows where it happens: by creator, country, region, city, in-app browser (TikTok, Instagram…), device, source (ad or direct) and day, with counts and value. In the API, that's `GET /v1/conversions/:id/report`. ## Data quality The pixel's page shows, of the last 7 days' events, how many had consent, came from your server, carried customer details or ad click IDs, were credited to a creator, and have a location. More of each means better matching and fewer missed conversions. ## Data & privacy - Personal details (email, phone, name, address, `externalId`) are normalized and SHA-256 hashed on arrival, never stored raw. Already-hashed values are kept as sent. - The raw IP address is kept 13 months, then cleared. - Location (country, region, city), network, device and in-app browser are recorded for every event, with or without consent, since they don't identify anyone. - Visitors in a pixel's excluded countries aren't recorded at all. - With consent `denied` or `unknown`, nothing that identifies the visitor is sent. ## Forward to ad platforms Lucra never sends your events to ad platforms. To forward them, for example to the Meta Conversions API, listen for the `conversion.recorded` [webhook](https://www.onlucra.com/docs/webhooks) and fetch its `pixel_event`. The hashes are normalized the way Meta, TikTok and Google expect. ```ts const event = await fetch(`https://api.onlucra.com/v1/pixel-events/${data.pixel_event}`, { headers: { Authorization: `Bearer ${process.env.LUCRA_API_KEY}`, "Lucra-Version": "2026-10-01" }, }).then((response) => response.json()) if (event.consent === "granted") { await fetch(`https://graph.facebook.com/v21.0/${META_PIXEL_ID}/events?access_token=${META_TOKEN}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ data: [ { event_name: "Purchase", // purchase → Purchase, sign_up → CompleteRegistration, lead → Lead… event_time: Math.floor(Date.parse(event.occurredAt) / 1000), event_id: event.eventId, // matches Meta's browser pixel when it sent the same ID action_source: event.actionSource, event_source_url: event.pageUrl, user_data: { em: event.user.email, ph: event.user.phone, fn: event.user.firstName, ln: event.user.lastName, ct: event.user.city, st: event.user.state, zp: event.user.zip, country: event.user.country, external_id: event.user.externalId, fbc: event.clickIds.fbc, fbp: event.clickIds.fbp, client_ip_address: event.ipAddress, client_user_agent: event.userAgent, }, custom_data: { value: event.value / 100, currency: event.currency, order_id: event.orderId }, }, ], }), }) } ``` Every platform's conversion API takes the same pieces; only the names change. Send each one the click ID and cookie it set: | Platform | Event name for `purchase` / `sign_up` | From `clickIds` | User data | | --- | --- | --- | --- | | Meta Conversions API | `Purchase` / `CompleteRegistration` | `fbc`, `fbp` | `user.*`, `ipAddress`, `userAgent` | | TikTok Events API | `CompletePayment` / `CompleteRegistration` | `ttclid`, `ttp` | `user.email`, `user.phone`, `user.externalId`, `ipAddress`, `userAgent` | | Snapchat Conversions API | `PURCHASE` / `SIGN_UP` | `sccid` (as `sc_click_id`), `scid` (as `sc_cookie1`) | `user.email`, `user.phone`, `ipAddress`, `userAgent` | | Google Ads (offline conversions) | your conversion action | `gclid`, `gbraid`, `wbraid` | `user.email`, `user.phone` (enhanced conversions) | The pixel picks these up from ad links (`fbclid`, `gclid`, `ttclid`, `ScCid`) and the platforms' own cookies (`_fbp`, `_fbc`, `_ttp`, `_scid`), so they're there whenever the visitor came from an ad. Only forward events whose `consent` is `granted`. --- # Creator setup > Sign up, connect your socials and verify with Stripe. Source: https://www.onlucra.com/docs/creator-setup Sign up as a **Creator**, then finish four short steps so brands can find you and pay you. ## Get started 1. Answer a few questions about you. 2. Connect **TikTok** or **Instagram**. 3. Verify your identity with Stripe, so you can be paid. 4. Add a photo, your name and your city. ## Your profile Brands see your photo, name, location and past work when they decide who to invite. ## Connected accounts Connecting a social account verifies your handle and lets you submit posts you've already made. If a connection expires, reconnect it from its menu. --- # Find & submit work > Join programs, submit videos and request samples. Source: https://www.onlucra.com/docs/creator-work Find a program in **Explore**, join it, then submit videos for the brand to review. ## Find work **Explore** lists the programs you can join. Open one to read the brief and see the pay. Press **Apply** or **Join program** to join. The program's terms are named under the button; pressing it accepts them. ## Submit a video Press **Submit Work**, pick the program, then upload a video or choose a post you've already made. | Requirement | Value | | ----------- | ------------------------------ | | Format | MP4 or MOV, up to 500 MB | | Length | At least 5 seconds | | Shape | Vertical 9:16, at least 720 × 1280 | If the brand asks for changes, send a revision. Revisions don't count toward the program's video limit. ## Samples Request samples of the products a program offers. You'll get an access code or a free checkout link. --- # Earnings > How you earn, when you get paid, and withdrawing. Source: https://www.onlucra.com/docs/creator-earnings Every program shows what it pays before you join. You earn as your work is approved or performs, and the money lands in your Stripe balance. ## How you earn A program pays per approved video, a share of ad spend, or per organic post (by its views). You get the full amount shown, retainers included; Lucra's fee is charged to the brand. ## When you get paid You're paid as soon as the brand's payment goes through. Each earning moves through three statuses: | Status | Meaning | | --------- | ------------------------------------ | | Pending | The brand's payment is going through | | Available | On its way to your Stripe balance | | Paid | In your Stripe balance | Earnings reach your balance once your identity is verified; until then they keep adding up. ## Withdraw Your balance stays in Lucra until you withdraw it. Withdraw anytime from **Finance**, with no minimum. Standard withdrawals are free; instant withdrawals cost 1.5% (at least 50¢). --- # Referrals > Earn by inviting brands and creators to Lucra. Source: https://www.onlucra.com/docs/referrals Invite brands and creators to Lucra and earn a share of what they bring in. Get your link from **Referrals** in your account menu; the same link works for brands and creators. | You refer | You earn | | --------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | A creator | 5% of their earnings for 365 days | | A brand | 50% of Lucra's fee on their spend for 365 days | A brand referral is paid from Lucra's fee, so the brand's bill doesn't change. --- # Partner workspaces > Run client brands and a creator roster, and set your rates. Source: https://www.onlucra.com/docs/partners A partner runs creator programs and ads for client brands, with its own roster of creators. Agencies and developers both use a partner account: pick **Agency** or **Developer** when you sign up. A brand account can turn on partner tools in **Settings → Partner profile**. ## Work with a brand Either side can send an invite. Once the other accepts, the partner works inside the brand's account. The brand manages the relationship in **Settings → Partner access**, and either side can end it at any time. In a client's account, a partner can review videos, add creators from its roster, and run programs and campaigns. A partner's campaign goes live without waiting on the brand. ## Your roster Your roster is the creators you represent. Add them from your partner workspace, then choose which client brands each one works with. ## Earnings A partner earns two ways. Set both in **Settings → Partner profile → Clients, creators and rates**, as a default and per brand or creator. | Rate | What you earn | | ----------------- | ------------------------------------------------------------------------------------------------------------------- | | Creator take rate | A share of the earnings of creators you bring: 5% by default, up to 20% | | Brand fee | A share of a client brand's ad spend or attributed sales: 2% of ad spend by default | ## Build on Lucra Developers can do all of this through the API, and put Lucra's screens in their own product. See [Partners in the API](https://www.onlucra.com/docs/api-partners) and [Embeds](https://www.onlucra.com/docs/embeds). --- # Pricing & billing > Fees, paying, and your ledger. Source: https://www.onlucra.com/docs/billing You pay creators through Lucra and buy media on your own ad accounts. **Settings → Billing** shows your plan, Lucra's fee and any subscription. ## Pricing Every brand pays the same: | Item | Cost | | ------------------ | --------------------------------------------------------------------------- | | Lucra fee | 1.25% of the first $100,000 a month, 1% up to $300,000, 0.75% up to $1,000,000, 0.5% above, of ad spend, or of attributed sales for revenue-share programs. Each slice of the month's total, across all your campaigns, is charged at its own rate | | Partner management | 2% of managed ad spend, by default, if a partner runs your ads | | Ad spend | Paid to the ad platform directly | | Paying creators | Free | ## Paying Save a card in **Finance** and usage is charged to it once a day, or as soon as you owe $500. If a charge is declined three days in a row, your campaigns and programs pause until you update the card. A plan with a subscription bills that card monthly or yearly, with invoices in **Settings → Billing**. ## Ledger **Finance** lists every charge, creator payment and refund, with its status. --- # FAQ Source: https://www.onlucra.com/docs/faq ## How much does Lucra cost? The standard plan has no subscription. You pay creators what your programs promise, plus Lucra's fee: 1.25% of the first $100,000 a month, 1% up to $300,000, 0.75% up to $1,000,000, 0.5% above, of ad spend, or of attributed sales for revenue-share programs. Media goes straight to the ad platform. See [Pricing & billing](https://www.onlucra.com/docs/billing). ## When do creators get paid? As soon as the brand's payment goes through, earnings go straight to the creator's Stripe balance. It stays there until they withdraw it to their bank, whenever they choose (instantly, for a fee). See [Earnings](https://www.onlucra.com/docs/creator-earnings). ## Which platforms work with Lucra? Ads run on Google, Meta, TikTok and Snapchat. Creators connect Instagram or TikTok. Shopify brings in products, sample orders and tracking. ## How is a sale credited to a creator? Every ad maps to the video it came from, so its spend and conversions trace back to that creator. On your own site, the [Lucra Pixel](https://www.onlucra.com/docs/tracking) credits sales from creators' links and codes too. ## Can I use Lucra from my own code? Yes. Programs, creators, payments and ads are all in the [API](https://www.onlucra.com/docs/api-introduction), and AI assistants can connect over [MCP](https://www.onlucra.com/docs/mcp). --- # Introduction > Your first request, IDs, lists, errors and retries. Source: https://www.onlucra.com/docs/api-introduction One REST API runs everything in Lucra, and the Lucra app is built on it. Call it from your server with the [SDK](https://www.onlucra.com/docs/sdk) or plain HTTP, or connect an AI assistant over [MCP](https://www.onlucra.com/docs/mcp). ## Your first request Create a key in **Settings → Developers → API**, then call `GET /v1/account` to see the account it belongs to: ```bash curl https://api.onlucra.com/v1/account \ -H "Authorization: Bearer $LUCRA_API_KEY" \ -H "Lucra-Version: 2026-10-01" ``` Every request sends two headers: - `Authorization: Bearer …` with an API key or an OAuth access token. See [Authentication](https://www.onlucra.com/docs/authentication). - `Lucra-Version`, so your integration keeps working as the API changes. The SDK sends it for you. The same call with the SDK: ```ts import { Lucra } from "@lucra/sdk" const lucra = new Lucra(process.env.LUCRA_API_KEY!) const { data: account } = await lucra.account.retrieve() ``` ## IDs Every ID starts with a prefix naming its resource, like `prog_00lhuAJjQgdtNt5WnqTBc95` for a program. Treat the rest as opaque. | Prefix | Resource | | --- | --- | | `acct_` | account | | `ad_` | ad | | `adauth_` | ad authorization | | `adset_` | ad set | | `adtpl_` | ad template | | `agac_` | agreement acceptance | | `agr_` | agreement | | `appl_` | application | | `camp_` | campaign | | `conn_` | connection | | `conv_` | conversion | | `crtr_` | creator | | `emb_` | embed | | `ern_` | earning | | `evt_` | webhook event | | `file_` | file | | `key_` | key | | `msg_` | message | | `pay_` | payment | | `post_` | post | | `pout_` | payout | | `prod_` | product | | `prog_` | program | | `pxev_` | pixel event | | `pxl_` | pixel | | `ret_` | retainer | | `smpl_` | sample | | `sub_` | submission | | `thr_` | thread | | `txn_` | transaction | | `wdr_` | withdrawal | ## Objects ```json { "id": "prog_00lhuAJjQgdtNt5WnqTBc95", "name": "Spring launch", "status": "open", "version": 3, "createdAt": "2026-10-01T15:04:05.000Z" } ``` - A reference to another object is its bare ID, in a field named for it: `program`, `creator`, `account`. - Money is an integer number of cents, next to a `currency`. Rates are percents. - Times are UTC ISO 8601, in fields ending in `At`. - Enums can gain values, so handle ones you don't recognize. ## Lists A list returns `data` and a `nextCursor`. Pass that cursor back as `cursor` to get the next page; it's `null` on the last one. `limit` sets the page size (default 25, up to 100). ```json { "data": [{ "id": "prog_00lhuAJjQgdtNt5WnqTBc95" }], "nextCursor": "eyJpZCI6…" } ``` Filter by a field's name, with commas for several values, like `status=pending,approved`. Every list also takes `createdAfter` and `createdBefore`. The SDK's [`paginate`](https://www.onlucra.com/docs/sdk#every-page) walks every page for you. Most objects aren't deleted. Set their `status` to `archived` instead; lists leave archived objects out unless you filter for them. ## Updates `PATCH` changes only the fields you send. To make sure nobody changed the object since you read it, send its `version`; if it's stale you get `409 version_conflict`, and you read it again. ## Idempotency Send an `Idempotency-Key` header on a write you might retry. A retry with the same key returns the first response instead of writing twice. Writes that move money require one: payments, retainer offers and withdrawals. ## Errors A failed request returns a problem with a stable `code` to branch on, a readable `detail`, and a `fix` when there's a clear next step: ```json { "status": 422, "code": "invalid_request", "detail": "type: expected one of \"organic\"|\"paid_ads\"", "fix": "Fix `type` in the request body.", "retryable": false, "errors": [{ "field": "type", "message": "Expected one of \"organic\"|\"paid_ads\"" }] } ``` | Status | Means | | ------ | ------------------------------------------------------------------ | | 401 | Missing, invalid or expired key or token | | 403 | The key can't do this, or can't reach that account | | 404 | Not found, or not visible to this key | | 409 | A conflict, or a setup step left first (`requirement_…` codes) | | 422 | Invalid request; its errors array lists every bad field | | 429 | Rate limited; wait `Retry-After` seconds | `retryable` says whether sending the same request again can succeed. Every problem also has a `requestId`; include it when you contact support. ## OpenAPI The full OpenAPI document is at `https://api.onlucra.com/v1/openapi.json`. Every endpoint, with its fields, is in [All endpoints](https://www.onlucra.com/docs/api-reference). --- # SDK > The TypeScript client: calls, pagination, errors and embeds. Source: https://www.onlucra.com/docs/sdk `@lucra/sdk` is the TypeScript client for the Lucra API. It has a typed method for every endpoint, sends the version header for you, and throws one error type. It runs anywhere `fetch` does: Node 18+, Bun, Deno and Workers. ## Install ```bash npm install @lucra/sdk ``` ## Create a client Create a client with an API key from **Settings → Developers → API**, or an [OAuth](https://www.onlucra.com/docs/oauth) access token. Keep either on your server. ```ts import { Lucra } from "@lucra/sdk" const lucra = new Lucra(process.env.LUCRA_API_KEY!) ``` ## Make a call Methods are named `lucra..`, with actions `list`, `retrieve`, `create` and `update`. This gets one program: ```ts const { data: program } = await lucra.programs.retrieve({ path: { id: "prog_00lhuAJjQgdtNt5WnqTBc95" }, }) ``` Every method takes one object with up to three parts, and resolves to `{ data, response }`: - `path`: the IDs in the URL, like `id` in `/v1/programs/:id`. - `query`: filters and paging for lists. - `body`: the fields to create or change. This pauses the program, sending its `version` so it fails if someone else changed it first: ```ts await lucra.programs.update({ path: { id: program.id }, body: { status: "paused", version: program.version }, }) ``` Nested resources follow the path: `lucra.balance.transactions.list(…)` calls `GET /v1/balance/transactions`. Every model is exported as a type, like `Program`, `Creator` or `Payment`. ## Lists A list's `data` is one page: its items are in `data.data`, and `data.nextCursor` points to the next page. This gets the first page of submissions waiting for review: ```ts const { data: page } = await lucra.submissions.list({ query: { program: "prog_00lhuAJjQgdtNt5WnqTBc95", status: "pending" }, }) console.log(page.data, page.nextCursor) ``` ## Every page `paginate` walks every page for you, fetching the next one as you loop. Pass filters as its second argument: ```ts import { paginate } from "@lucra/sdk" const pending = paginate((options) => lucra.payments.list(options), { query: { status: "pending_approval" }, }) for await (const payment of pending) console.log(payment.id, payment.net) ``` ## Act for an account A [partner](https://www.onlucra.com/docs/api-partners) acts for a client brand (`acct_…`) or a roster creator (`crtr_…`). `lucra.as(id)` returns the same client, sending that account in the `Lucra-Account` header: ```ts const brand = lucra.as("acct_07RodMW6bE7BBO6e1I3UzSi") const { data: page } = await brand.programs.list() ``` To act for one account everywhere, pass it when you create the client: `new Lucra(key, { account: "acct_…" })`. ## Errors A failed request throws a `LucraError`. Branch on `code`, which is stable; show `message` or `fix` to people: ```ts import { LucraError } from "@lucra/sdk" try { await lucra.programs.retrieve({ path: { id: "prog_00lhuAJjQgdtNt5WnqTBc95" } }) } catch (error) { if (!(error instanceof LucraError)) throw error if (error.code === "program_not_found") console.log("No such program") else console.error(error.status, error.code, error.message, error.fix) } ``` `LucraError` has `status`, `code`, `message` (the API's `detail`), `fix`, `retryable`, `requestId`, and `errors`, one per invalid field. A network failure is thrown as it is, not as a `LucraError`. See [Errors](https://www.onlucra.com/docs/api-introduction#errors) for the codes. ## Retries Send an `Idempotency-Key` header on a write you might retry, so a retry never writes twice. Writes that move money require one. Use an ID from your own system, like an order ID: ```ts await lucra.payments.create({ body: { creator: "crtr_026UYfiw1Y6OvUpuKWiOpeL", net: 5000, note: "Launch bonus" }, headers: { "Idempotency-Key": "order-1042-bonus" }, }) ``` ## Embeds The same package puts Lucra's screens in your app. Open an embed on your server with `lucra.embeds.create`, then mount it in the browser with `@lucra/sdk/embed`, or with React components from `@lucra/sdk/react`: ```tsx import { loadLucra, LucraProvider, LucraWallet } from "@lucra/sdk/react" // Your server route calls lucra.embeds.create and returns its clientSecret. const lucra = loadLucra({ fetchClientSecret: () => fetch("/api/lucra-embed", { method: "POST" }).then((r) => r.json()).then((r) => r.clientSecret), }) export const Wallet = () => ( ) ``` [Embeds](https://www.onlucra.com/docs/embeds) covers the whole flow. ## Other languages Generate a client from the [OpenAPI document](https://api.onlucra.com/v1/openapi.json), or call the API over HTTP. --- # Authentication > API keys, OAuth tokens and the accounts they reach. Source: https://www.onlucra.com/docs/authentication Every request carries a bearer credential: an API key for your own account, or an OAuth access token for an account that connected your app. ## API keys Owners and admins create keys in **Settings → Developers → API**. Give each key a name and its [access](https://www.onlucra.com/docs/access), `read` or `write`. You can also set its webhook URL and the origins allowed to load [embeds](https://www.onlucra.com/docs/embeds). A key starts with `lucra_live_` and is shown once. Keep it on your server, never in a browser or app. ```bash curl https://api.onlucra.com/v1/account \ -H "Authorization: Bearer $LUCRA_API_KEY" \ -H "Lucra-Version: 2026-10-01" ``` Rotating or revoking a key takes effect immediately. Give each service or agent its own key, so you can revoke one without the others. ## Which accounts a key reaches A brand's key reaches only that brand. A [partner's](https://www.onlucra.com/docs/api-partners) key reaches: - the partner's own account, - every active client brand, including brands that become clients later, - every creator on its roster. To act for one of them, send its ID in the `Lucra-Account` header (`acct_…` or `crtr_…`), or use `lucra.as(id)` in the [SDK](https://www.onlucra.com/docs/sdk#act-for-an-account). Leave the header out to act for the key's own account. An ID the key can't reach returns `403 account_not_reachable`; `GET /v1/brands` lists the brands it can. ## OAuth Use [OAuth](https://www.onlucra.com/docs/oauth) when your app acts for Lucra accounts you don't own. People sign in to Lucra and approve your app, instead of handing you a key. The access token then works like a key with the access they granted. AI clients on the [MCP server](https://www.onlucra.com/docs/mcp) sign in this way too. --- # Access > What read and write keys and grants can do. Source: https://www.onlucra.com/docs/access Every API key and OAuth grant has one of two access levels. Each endpoint in the reference is marked with the level it needs. | Access | Allows | | ------- | ---------------- | | `read` | Every read | | `write` | Reads and writes | Start a new integration or an AI agent on `read`, and move to `write` once you trust it with every write, including ones that move money. ## OAuth tokens An [OAuth](https://www.onlucra.com/docs/oauth) grant is `read` or `write` too, and a token never does more than the person who granted it. Owners and admins can do everything; other members read everything but payments, and can't make changes. ## Acting as a creator A partner acts for a roster creator with `Lucra-Account: crtr_…`. That needs a `write` key, even for reads, and works only on endpoints marked for creators. See [Partners](https://www.onlucra.com/docs/api-partners#act-as-a-creator). ## What stays in the app Managing your team, API keys and OAuth apps happens in the Lucra app. Card and bank details, identity checks and platform sign-ins happen on hosted pages, so they never pass through your server. `POST /v1/connections` returns the page to send the person to, and a [webhook](https://www.onlucra.com/docs/webhooks) tells you when they're done. --- # OAuth > Let other Lucra accounts connect your app. Source: https://www.onlucra.com/docs/oauth Use OAuth when your app acts for Lucra accounts you don't own. A member signs in to Lucra and approves your app, and you get an access token instead of asking them for an [API key](https://www.onlucra.com/docs/authentication#api-keys). For your own account, a key is simpler. The flow is the standard authorization code flow with PKCE: register your app once, send people to authorize it, exchange the code for tokens, and refresh them. ## Register your app Owners and admins create an app in **Settings → Developers → OAuth apps** with **Create OAuth app**: | Field | Rule | | ------------- | ---------------------------------------------------------------------------- | | Name | Up to 200 characters; it can't contain "Lucra" | | Callback URLs | One per line, up to 20; HTTPS only, no `localhost`, credentials or fragment | | Access | **Read**, or **Read and write** | Lucra then shows the client ID (`lucra_…`) and client secret (`lucra_oauth_…`). The secret is shown once; keep it on your server. Every app is confidential and authenticates to the token endpoint with HTTP Basic. Any Lucra account can authorize it. **Copy setup instructions** on the app copies its client ID, endpoints and callbacks, never the secret. ## Authorize Send the person to the authorization URL. Generate a fresh `state` and PKCE code verifier each time: ```bash https://api.onlucra.com/api/auth/oauth2/authorize ?response_type=code &client_id=lucra_… &redirect_uri=https://app.example.com/oauth/callback &scope=read%20write%20offline_access &resource=https://api.onlucra.com/v1 &state=… &code_challenge=… &code_challenge_method=S256 ``` They sign in if they need to, then approve on the consent screen. If they belong to more than one account, they pick which one your app gets, and the grant is for that account. Creators can't authorize apps. Lucra then redirects to your `redirect_uri` with `code` and `state`. The `redirect_uri` must match a registered callback exactly. Check that `state` matches the one you sent. `resource` names the API the token is for. Leave it out and the grant is for `/v1`. The code, its tokens and every refresh keep that audience, so the token request can leave it out too. ## Exchange the code The code lasts 10 minutes and works once. ```bash curl https://api.onlucra.com/api/auth/oauth2/token \ -u "$LUCRA_CLIENT_ID:$LUCRA_CLIENT_SECRET" \ -d grant_type=authorization_code \ -d code=$CODE \ -d code_verifier=$CODE_VERIFIER \ -d redirect_uri=https://app.example.com/oauth/callback \ -d resource=https://api.onlucra.com/v1 ``` ```json { "access_token": "eyJ…", "token_type": "Bearer", "expires_in": 3600, "expires_at": 1791306245, "refresh_token": "…", "scope": "read write offline_access" } ``` ## Call the API Use the access token like a key: ```bash curl https://api.onlucra.com/v1/account \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Lucra-Version: 2026-10-01" ``` It acts for the account that authorized your app. If that account is a [partner](https://www.onlucra.com/docs/api-partners), send `Lucra-Account: acct_…` to reach one of its client brands, as with a partner key. With the SDK, pass the access token where you'd pass a key: `new Lucra(accessToken)`. ## Refresh Access tokens last an hour. Swap the refresh token for a new pair: ```bash curl https://api.onlucra.com/api/auth/oauth2/token \ -u "$LUCRA_CLIENT_ID:$LUCRA_CLIENT_SECRET" \ -d grant_type=refresh_token \ -d refresh_token=$REFRESH_TOKEN ``` Store the new refresh token and drop the old one: reusing a refresh token ends the grant. A refresh token lasts 30 days, so refresh at least that often. A grant lasts until the person disconnects your app, leaves the account, or a year passes. Then they authorize again. ## Scopes | Scope | Grants | | ---------------- | -------------------------------------------------------- | | `read` | Every read, like a `read` key | | `write` | Reads and writes, like a `write` key | | `offline_access` | A refresh token, so your app stays connected | Ask only for what your app registered: a **Read** app can't ask for `write`. A token never does more than the person's own role; members who aren't owners or admins read everything but payments. See [Access](https://www.onlucra.com/docs/access). ## Revoke Revoking a refresh token ends the grant, like the person disconnecting your app: your app leaves their **Settings → Connected apps** and every token from that grant stops working. Revoking an access token ends only that token. ```bash curl https://api.onlucra.com/api/auth/oauth2/revoke \ -u "$LUCRA_CLIENT_ID:$LUCRA_CLIENT_SECRET" \ -d token=$REFRESH_TOKEN \ -d token_type_hint=refresh_token ``` People disconnect your app in **Settings → Connected apps**, and its tokens stop working at once. Editing your app, rotating its secret or revoking it disconnects everyone; they authorize again. ## Errors | Result | Means | | ---------------------------- | ----------------------------------------------------------- | | `error=access_denied` | The person declined, or can't grant access to that workspace | | `invalid_grant` | The code expired or was used, or the refresh token was reused | | `invalid_client` | Unknown client, or a missing or wrong client secret | | `unsupported_grant_type` | Only `authorization_code` and `refresh_token`; for machine-to-machine access use an [API key](https://www.onlucra.com/docs/authentication#api-keys) | | `401 invalid_key` | The token expired or was revoked; refresh or authorize again | | `403 permission_denied` | A `read` token called a write | | `403 account_not_reachable` | `Lucra-Account` isn't one the token reaches | | `403 internal_route` | The endpoint is only for Lucra's apps | ## Discovery Server metadata is at `https://api.onlucra.com/.well-known/oauth-authorization-server/api/auth`, and the API's protected-resource metadata at `https://api.onlucra.com/.well-known/oauth-protected-resource/v1`. A `401` from `/v1` names it in `WWW-Authenticate` (`resource_metadata`). Clients that can't register ahead, like [MCP](https://www.onlucra.com/docs/mcp) clients, register themselves at `POST https://api.onlucra.com/api/auth/oauth2/register`; such a client works with the first account that authorizes it. --- # Webhooks > Events Lucra sends your server, and verifying them. Source: https://www.onlucra.com/docs/webhooks Lucra POSTs an event to your server when something changes, so you don't have to poll. Webhooks belong to an API key: each key has at most one webhook URL. ## Set up Add a webhook URL when you create the key in **Settings → Developers → API**, or have the key set its own: ```ts const { data } = await lucra.keys.current.update({ body: { webhookUrl: "https://example.com/lucra/webhooks" }, }) ``` The response's `webhookSecret` (`whsec_…`) is the signing secret, shown only once; store it to [verify signatures](#verify-the-signature). Setting a URL again issues a new secret, and `null` stops events. `GET /v1/keys/current` reads the current URL. ## Which events a key gets A key gets events about its own account. A partner's key also gets events about its client brands, and every event about a creator on its roster (in `data.creator`), wherever it happens: their applications, submissions, messages, retainers, payments, connections and payout setup. An event about a creator alone has `account: null`. Missed some? `GET /v1/events` lists the events sent to the key's webhook, newest first, each with its `delivery` status. It holds only events from while the key had a webhook URL. ## Events An event names what changed and the IDs involved; fetch the details from the API. Ignore types you don't handle, since new ones are added. ```json { "id": "evt_0u5s9xZIdD99WBwmDjJPYc3", "type": "submission.approved", "account": "acct_04Jm0JWUg20EhYo2lyNIEHo", "data": { "program": "prog_00lhuAJjQgdtNt5WnqTBc95", "submission": "sub_14XA4WGiSOmaVI3B4QLm4Fs" } } ``` Some steps happen on a hosted page: a platform sign-in, payout setup or a card. Send the person to the URL from `POST /v1/connections`, then wait for `connection.connected` or `creator.payouts_updated`. | Event | When | `data` | | ------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------- | | `application.created` | A creator applies to a program | `program`, `application` | | `application.invited` | A creator is invited and must accept the program's agreements (`agreementsRequired`) | `program`, `application`, `creator` | | `application.approved` | An application is approved | `program`, `application` | | `application.rejected` | An application is rejected | `program`, `application` | | `application.withdrawn` | A creator withdraws an application | `program`, `application` | | `agreement.accepted` | A creator accepts the account's agreements | `creator`, `program` when for one | | `submission.created` | A creator submits work | `program`, `submission` | | `submission.approved` | A submission is approved | `program`, `submission` | | `submission.revision_requested` | A reviewer asks for changes | `program`, `submission` | | `submission.rejected` | A submission is rejected | `program`, `submission` | | `submission.live` | A submission goes live | `program`, `submission` | | `file.ready` | An uploaded file finishes processing | `file` | | `file.failed` | An uploaded file can't be used | `file` | | `campaign.published` | A campaign goes live on its platform | `campaign` | | `campaign.partially_failed` | A launch succeeds on some platforms and fails on others | `campaign` | | `campaign.failed` | A launch fails | `campaign` | | `campaign.paused` | A campaign is paused | `campaign` | | `campaign.archived` | A campaign is archived | `campaign` | | `campaign.needs_attention` | A campaign needs action on its ad platform | `campaign` | | `payment.created` | A payment to a creator is created | `payment`, `creator` | | `payment.pending_approval` | A payment waits for the brand to approve it | `payment`, `creator` | | `payment.succeeded` | A payment to a creator succeeds | `payment`, `creator` | | `payment.failed` | A payment to a creator fails | `payment` | | `payment.disputed` | A payment is disputed before it's sent to the creator | `payment`, `creator` | | `payment.refunded` | A payment is refunded in full | `payment` | | `payout.paid` | Earnings reach a creator's (or this account's) balance | `payout` | | `payout.failed` | Sending earnings to a balance fails | `payout` | | `withdrawal.created` | A withdrawal (the account's or a roster creator's) is on its way | `withdrawal` | | `withdrawal.paid` | A withdrawal (the account's or a roster creator's) reached the bank | `withdrawal` | | `withdrawal.failed` | A withdrawal (the account's or a roster creator's) failed | `withdrawal` | | `retainer.accepted` | A creator accepts a retainer | `retainer` | | `retainer.rejected` | A creator turns a retainer down | `retainer` | | `retainer.canceled` | A retainer is canceled | `retainer` | | `retainer.ended` | A retainer ends | `retainer` | | `sample.created` | A creator requests a sample | `sample` | | `sample.approved` | A sample request is approved | `sample` | | `sample.rejected` | A sample request is rejected | `sample` | | `sample.shipped` | The store's order for a sample is created | `sample`, `creator` | | `pixel.verified` | A pixel receives its first event | `pixel` | | `conversion.recorded` | An event counts as one of the account's conversions | `conversion`, `pixel_event`, `submission` when credited to a video | | `message.created` | A message arrives in one of the account's threads | `message`, `thread` | | `brand.created` | A partner provisions a brand | `brand` | | `creator.created` | A partner adds a creator to its roster | `creator` | | `creator.payouts_updated` | A creator's payout setup changes: verified, needs action, or ready | `creator` | | `connection.connected` | A platform account is connected | `connection`, `creator` for a creator's | | `connection.disconnected` | A platform account is disconnected | `connection`, `creator` for a creator's | | `connection.expired` | A platform account needs reconnecting | `connection`, `creator` for a creator's | | `ad_authorization.requested` | A brand asks a creator to authorize a submission as an ad | `ad_authorization`, `submission`, `creator` | | `ad_authorization.granted` | The creator grants it | `ad_authorization`, `submission`, `creator` | | `ad_authorization.revoked` | It is revoked | `ad_authorization`, `submission`, `creator` | ## Verify the signature Every delivery has a `Lucra-Signature` header: `t=,v1=`, where `v1` is an HMAC-SHA256 of `.` keyed with your signing secret. Check it against the raw request body, before parsing it, and reject old timestamps: ```ts import { createHmac, timingSafeEqual } from "node:crypto" export function verifyLucraWebhook( body: string, header: string, secret: string ) { const { t, v1 } = Object.fromEntries( header.split(",").map((part) => part.split("=")) ) if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false const expected = createHmac("sha256", secret) .update(`${t}.${body}`) .digest("hex") return ( v1?.length === expected.length && timingSafeEqual(Buffer.from(v1), Buffer.from(expected)) ) } ``` ## Retries Answer with a `2xx` within 10 seconds; do slow work after you respond. Failed deliveries retry for 3 days. Events can repeat or arrive out of order, so deduplicate on `id`. --- # Sandbox > Test an integration with simulated ad platforms and no payments. Source: https://www.onlucra.com/docs/sandbox The sandbox is a separate copy of Lucra for building and testing an integration. It runs the same API as live. Ad platforms and the brand's card are simulated, so nothing spends real money and no payout is sent. | | Live | Sandbox | | --- | --- | --- | | App | `https://www.onlucra.com` | `https://sandbox.onlucra.com` | | API | `https://api.onlucra.com/v1` | `https://api-sandbox.onlucra.com/v1` | | Keys | `lucra_live_…` | `lucra_sandbox_…` | ## Get started 1. Sign up at sandbox.onlucra.com. Choose **Brand**, or **Agency** or **Developer** to test as a [partner](https://www.onlucra.com/docs/api-partners). 2. Create a key in **Settings → Developers → API**, and set its webhook URL there or with `PATCH /v1/keys/current`. 3. Point your client at the sandbox API. With the [SDK](https://www.onlucra.com/docs/sdk), pass `baseUrl: "https://api-sandbox.onlucra.com"` with your sandbox key. Sandbox accounts, keys and data are separate from live: a live key doesn't work in the sandbox, and a sandbox key doesn't work on live. When you're ready, create a live key and switch the base URL. Nothing carries over. ## What works as on live Everything that stays inside Lucra behaves exactly as on live, with the same requests, responses, errors and [webhooks](https://www.onlucra.com/docs/webhooks): - Partner accounts and the brands and creators they manage (`/v1/brands`, `/v1/creators`) - Programs, applications, agreements and submissions, including uploading videos with `/v1/files` - Campaigns, ad sets, ads and their changes, against the simulated platforms below - Analytics, ad authorizations, messages, embeds and the [Lucra Pixel](https://www.onlucra.com/docs/tracking) Emails and invites are sent for real, so use addresses you control. ## Simulated ad platforms **Connecting.** `POST /v1/connections` for Meta, TikTok, Google or Snapchat opens a simulated sign-in page instead of the platform's. Choose **Allow** (or **Cancel** to test a refusal) and the connection finishes as on live. Then pick its ad account, and its page, pixel or identity, from `GET /v1/connections/:id/setup`, and save them with `PATCH /v1/connections/:id`. **Campaigns.** A campaign is set up on the simulated platform as soon as it's created, and launching, pausing, budget changes and archiving apply right away. Each ad gets a platform ID, as on live. **Results.** Every running ad reports simulated daily results from the day it was created: spend, impressions, clicks, conversions, revenue and paid views. The same ad and day always read the same numbers, so your tests can assert on them. They reach `GET /v1/analytics` on the same 30-minute sync as live, and usage fees are charged to the simulated card (or credit) as on live, so they show in `usageFees` too. **Ads Lucra didn't launch.** Each simulated ad account also holds three ads of its own, so `GET /v1/ads?source=external` and `source=external` analytics have data to test with. ## Test values | To test | Use | | --- | --- | | Paid ads | Any simulated ad account: one per platform, in USD | | A brand's card | `POST /v1/connections` with `card`: saves a simulated Visa ending 4242 at once. Usage fees charged to it always succeed. | | A TikTok Spark code ([boosting a post](https://www.onlucra.com/docs/api-ads#boost-an-existing-tiktok-post)) | `SANDBOX-VALID` is accepted. `SANDBOX-VALID-` is accepted for that post, to match a link you also send. `SANDBOX-EXPIRED`, `SANDBOX-INVALID` and `SANDBOX-WRONG-ACCOUNT` are refused with `422 spark_code_refused`, as TikTok refuses them. | | A post link | Any full TikTok post link. It waits for a code, which you send from its `requestUrl` | ## Not in the sandbox These reach outside Lucra, so they return `403 feature_unavailable` in the sandbox: - Payouts and withdrawals - Stripe pages, such as payout setup and card checkout - Shopify stores and samples - Creators connecting their own Instagram or TikTok - Slack --- # Account > The account a key belongs to, and its setup. Source: https://www.onlucra.com/docs/api-account The account is the brand or partner a key belongs to. `GET /v1/account` is a good first call: it tells you the account's `type` (`brand` or `partner`), its payment method and payout setup, its fees, and how many brands and creators it has. ```ts const { data: account } = await lucra.account.retrieve() ``` ## Payment method A brand needs a payment method before it can create programs or pay creators. Until it has one, `paymentMethod` is `null` and those writes return `409 requirement_payment_method`. To add one, send a brand member to the URL from `POST /v1/connections` with `provider: "card"`. ## Update it `PATCH /v1/account` changes the account's `name`, `description` and logo (`profilePicture`, a [file](https://www.onlucra.com/docs/api-files) uploaded with `purpose=profile_picture`). A partner also sets its [take rates](https://www.onlucra.com/docs/api-partners#take-rates) here. ## Endpoints - `GET /v1/account` (read, SDK `lucra.account.retrieve`): Get the key's own account - `PATCH /v1/account` (write, SDK `lucra.account.update`): Update the key's own account --- # Brands > Create and manage a partner's client brands. Source: https://www.onlucra.com/docs/api-brands A [partner](https://www.onlucra.com/docs/api-partners) manages client brands. `GET /v1/brands` lists the ones its key reaches; act for one by sending its ID in `Lucra-Account`. A brand's own key gets an empty list. ## Create a brand A partner creates a brand with `POST /v1/brands`. It becomes an active client right away, so every key of the partner reaches it. Send an `Idempotency-Key` so a retry doesn't create it twice: ```ts const { data: brand } = await lucra.brands.create({ body: { name: "Acme", email: "ops@acme.com", brandFee: { of: "ad_spend", percent: 10 } }, headers: { "Idempotency-Key": "acme-onboarding" }, }) ``` Leave out `brandFee` to use your default from `GET /v1/account`. ## Payment method The brand pays for its own programs and creator payments. Until a brand member adds a card at the URL from `POST /v1/connections` (`provider: "card"`), those writes return `409 requirement_payment_method`. The [`onboarding` embed](https://www.onlucra.com/docs/embeds) walks them through it. ## Update a brand `PATCH /v1/brands/:id` sets its `name`, `description`, logo (`profilePicture`) and `brandFee`. A new fee applies to spend from then on. ## Endpoints - `GET /v1/brands` (read, SDK `lucra.brands.list`): List the brands this key reaches, and a partner's brands whose relationship ended - `GET /v1/brands/:id` (read, SDK `lucra.brands.retrieve`): Get a brand - `POST /v1/brands` (write, SDK `lucra.brands.create`): Create a brand a partner manages - `PATCH /v1/brands/:id` (write, SDK `lucra.brands.update`): Update a brand a partner manages --- # Creators > Creators you work with, and a partner's roster. Source: https://www.onlucra.com/docs/api-creators An account sees the creators it works with: creators in its programs and, for a partner, the creators on its roster. ## List creators `GET /v1/creators` filters by `q` (a name, email or handle), `status`, `program`, `relationship` and `visibility`: ```ts const { data: page } = await lucra.creators.list({ query: { program: "prog_00lhuAJjQgdtNt5WnqTBc95", q: "sam" }, }) ``` ## Add a creator to your roster A partner adds a creator by email with `POST /v1/creators`: ```ts const { data: creator } = await lucra.creators.create({ body: { email: "sam@example.com", name: "Sam Rivera", visibility: "private" }, }) ``` What happens next depends on the creator: - **New to Lucra**: they get an account to claim later, and you can act for them right away with `Lucra-Account: crtr_…`. - **Already on Lucra, or left your roster before**: they're emailed an invite and show as `invited`. You can act for them once they accept. - **Already on your roster**: you get them back with `200`. A creator can be on one partner's roster at a time. ## Manage the roster Choose which client brands a roster creator works with by sending the full list. A brand left out stops working with them: ```ts await lucra.creators.update({ path: { id: "crtr_026UYfiw1Y6OvUpuKWiOpeL" }, body: { brands: ["acct_07RodMW6bE7BBO6e1I3UzSi"] }, }) ``` Send `{ "status": "removed" }` to take a creator off the roster, or `takeRate` to set your cut of their pay. ## Edit a creator's profile Acting as the creator (`Lucra-Account: crtr_…`), `PATCH /v1/creators/:id` with their own ID edits their `name`, `bio`, `interests` and location. - **Location**: search the city catalog with `GET /v1/cities?q=…`, then send the picked city as `locationDetails`, with its `displayName` as `location`. - **Photo**: send `profilePicture`, a `profile_picture` [file](https://www.onlucra.com/docs/api-files) uploaded as the creator. Or send `pictureFrom: "instagram"` or `"tiktok"` to use a connected account's photo. ## Visibility `private` hides a creator from brands browsing Explore; `network` lets any brand find them. Either way, they can apply to programs open to the network. ## Getting paid A creator is paid to their own Stripe balance once `payouts.identity.status` is `verified`. Until then they keep earning. To finish setup, send them to the URL from `POST /v1/connections` with `provider: "stripe"`. The [`creator.payouts_updated` webhook](https://www.onlucra.com/docs/webhooks) tells you when their setup changes. ## Endpoints - `GET /v1/creators` (read, SDK `lucra.creators.list`): List the account's creators and, for a partner, its roster - `GET /v1/creators/:id` (read, SDK `lucra.creators.retrieve`): Get one of the account's creators, or, as a creator, yourself - `POST /v1/creators` (write, SDK `lucra.creators.create`): Add a creator to a partner's roster; one already on it is returned with 200, and one with a Lucra account is invited (status invited) until they accept - `PATCH /v1/creators/:id` (write, SDK `lucra.creators.update`): Update a creator: a brand's labels and status, a partner's roster creator, or, as a creator, your own profile - `GET /v1/cities` (read, SDK `lucra.cities.list`): Search cities by name, exact matches first --- # Connections > Ad accounts, stores, socials, payouts and cards. Source: https://www.onlucra.com/docs/api-connections A connection links an outside account to Lucra: a brand's ad accounts and Shopify store, or a creator's Instagram and TikTok. Stripe payout setup and a brand's card are connections too. | `provider` | Who | Used for | | -------------------------------------- | ------- | --------------------------------------------------- | | `meta`, `tiktok`, `google`, `snapchat` | Brand | Ad accounts [campaigns](https://www.onlucra.com/docs/api-ads) run on | | `shopify` | Brand | Products and sample orders | | `card` | Brand | The payment method for programs and payments | | `instagram`, `tiktok` | Creator | Posts for organic programs | | `stripe` | Creator or partner | Identity and bank details for payouts | ## Connect an account People sign in to the platform or Stripe themselves, never to Lucra. `POST /v1/connections` returns the hosted page to send them to: ```ts const { data } = await lucra.as("crtr_026UYfiw1Y6OvUpuKWiOpeL").connections.create({ body: { provider: "tiktok", returnUrl: "https://app.example.com/settings" }, }) // Redirect the creator to data.url ``` - Call it as the brand, or as a partner with `Lucra-Account` set to a client brand or roster creator. - Shopify also needs `shop`, the store's `.myshopify.com` domain. A creator's first `stripe` setup needs their payout `country`, one of the [supported payout countries](https://www.onlucra.com/docs/api-payments#supported-payout-countries). - When they finish, they come back to your `returnUrl` with `status=connected` or `status=error` added. An https `returnUrl` must be on your key's embed origins. Then wait for the [`connection.connected` webhook](https://www.onlucra.com/docs/webhooks) (or `creator.payouts_updated` for Stripe), or read `GET /v1/connections`. `connection.expired` means the person needs to connect again the same way. ## Pick an ad account After an ad platform sign-in, the connection is `setup_needed` until you pick the ad account to use. `GET /v1/connections/:id/setup` lists the ad accounts, pages and identities the person can choose from. Pass a pick, like `businessCenterId`, to list what's under it. Save the picks with `PATCH /v1/connections/:id` and `selection`. Disconnect with `PATCH /v1/connections/:id` and `status: "disconnected"`. An ad platform with live campaigns can't be disconnected. ## Posts and syncs As a creator, `GET /v1/connections/:id/posts` lists the posts Lucra mirrors from their account, and `POST /v1/connections/:id/syncs` refreshes them now. For a brand's Shopify connection, the same sync imports the catalog into [products](https://www.onlucra.com/docs/api-commerce). ## Endpoints - `GET /v1/connections/:id` (read, SDK `lucra.connections.retrieve`): Get a connection - `GET /v1/connections/:id/setup` (read, SDK `lucra.connections.setup.retrieve`): List an ad platform's accounts, pages and identities to pick from, and the ones picked - `GET /v1/connections` (read, SDK `lucra.connections.list`): List connected ad platforms, Shopify and creator social accounts - `POST /v1/connections/:id/syncs` (write, SDK `lucra.connections.syncs.create`): Sync a connected shop's catalog, or, as a creator, mirror a social account's posts now - `POST /v1/connections` (write, SDK `lucra.connections.create`): Connect a platform, payouts or a card: returns the hosted page to send the person to - `PATCH /v1/connections/:id` (write, SDK `lucra.connections.update`): Pick a brand's ad account, page and identity after signing in, or disconnect a connection - `GET /v1/connections/:id/posts` (read, SDK `lucra.connections.posts.list`): List the posts Lucra mirrors from a creator's connected account, newest first - `POST /v1/connections/:id/logos` (write, SDK `lucra.connections.logos.create`): Upload a square logo (PNG or JPEG, at least 128 × 128, up to 5 MB) to a Google Ads connection --- # Programs > Programs, pay, applications and agreements. Source: https://www.onlucra.com/docs/api-programs A program is a brief with pay terms. Creators join it through an **application**, [submit work](https://www.onlucra.com/docs/api-submissions), and get paid on approval or on how their work performs. ## Create a program A program needs a `name`, a `type`, a `visibility` and its `pay`. This one pays $8 per 1,000 views on creators' posts, up to $500 each: ```json { "name": "Spring launch", "type": "organic", "visibility": "network", "brief": "Show the jacket on a real trail run.", "pay": { "rules": [{ "per": "view", "amount": 800, "unit": 1000 }], "cap": 50000, "schedule": { "after": "measurement", "days": 7 } }, "promoted": { "products": ["prod_04bst9MGXqblfqHhbR1iGGL"] }, "agreements": ["agr_0HxY6vD1bN4tK8qLm2Rz9sW"] } ``` - `type` is `organic` (paid on creators' own posts) or `paid_ads` (paid on ads made from their work). - `visibility` is `invite` (only creators you add) or `network` (any creator can apply). - `promoted.products` are the products creators can request samples of. - `agreements` are the [agreements](#agreements) creators accept by joining. The brand needs a payment method first, or you get `409 requirement_payment_method`. `PATCH /v1/programs/:id` changes any of these. Every change in one request applies together, or none does. Set `status` to `paused`, `open` or `archived`. ## Pay Each type pays per result in its own way: | Type | Paid per result | When | | ---------- | --------------------------------------------- | ---------------------------- | | `paid_ads` | A percent of ad spend or revenue, or per lead | As results come in | | `organic` | Per 1,000 views, or per post | After a 1–30 day measurement | On top of that, add flat pay per approved submission, per install, per click, or per [conversion](https://www.onlucra.com/docs/api-attribution#pay-per-conversion) on the brand's site, all under a `cap`. The full `pay` object is described in [Payments](https://www.onlucra.com/docs/api-payments#pay). Pay that could never be paid as set is refused with [`422 invalid_pay`](https://www.onlucra.com/docs/api-payments#pay-that-cant-be-paid). Changing a program's pay creates a new version. Creators already in the program move to it on their next action there. To set one creator's pay, send `pay` on `PATCH /v1/applications/:id`. ## Applications An application is a creator's place in a program. There are two ways in: - **A brand adds a creator** with `{ "program", "creator" }`. They join approved and are notified. - **A creator applies** to a `network` program with `{ "program" }`, acting as themselves. ```ts await lucra.applications.create({ body: { program: "prog_00lhuAJjQgdtNt5WnqTBc95", creator: "crtr_026UYfiw1Y6OvUpuKWiOpeL" }, }) ``` Review an application with `PATCH /v1/applications/:id` and `status`: `approved`, `rejected`, or `pending` to reopen it. Applying or joining is how a creator accepts the program's `agreements`; there's no separate signing step. Show the agreements under your button, like "By applying you agree to …". A creator a brand added still has to accept while `agreementsRequired` is `true`: they do it with `PATCH /v1/applications/:id` and `{ "status": "approved" }`. ## Agreements Publish a PDF ([uploaded](https://www.onlucra.com/docs/api-files) with `purpose=agreement`) with `POST /v1/agreements`, then list it in a program's `agreements`. `GET /v1/agreements/acceptances` lists who accepted what. ## Posts `GET /v1/posts` lists the creators' posts your organic programs pay for, with their views and earnings. ## Endpoints - `GET /v1/applications` (read, SDK `lucra.applications.list`): List applications - `GET /v1/applications/:id` (read, SDK `lucra.applications.retrieve`): Get an application - `POST /v1/applications` (write, SDK `lucra.applications.create`): Invite a creator to a program, or apply as one - `PATCH /v1/applications/:id` (write, SDK `lucra.applications.update`): Approve, reject or reopen an application, or set the creator's pay; as the creator, withdraw it or accept an invitation (approved) - `GET /v1/agreements/acceptances` (read, SDK `lucra.agreements.acceptances.list`): List creators' acceptances of your agreements; as a creator, the ones you accepted and the ones waiting on you - `GET /v1/agreements` (read, SDK `lucra.agreements.list`): List agreements - `GET /v1/agreements/:id` (read, SDK `lucra.agreements.retrieve`): Get an agreement - `GET /v1/agreements/:id/versions` (read, SDK `lucra.agreements.versions.list`): List an agreement's versions, newest first - `GET /v1/agreements/:id/versions/:number` (read, SDK `lucra.agreements.versions.retrieve`): Get a version of an agreement by its number - `POST /v1/agreements` (write, SDK `lucra.agreements.create`): Publish an agreement - `POST /v1/agreements/:id/versions` (write, SDK `lucra.agreements.versions.create`): Publish a new version of an agreement - `PATCH /v1/agreements/:id` (write, SDK `lucra.agreements.update`): Change where an agreement is required, or archive it - `GET /v1/posts` (read, SDK `lucra.posts.list`): List creators' posts in organic programs - `GET /v1/posts/:id` (read, SDK `lucra.posts.retrieve`): Get a post with its claim evidence - `PATCH /v1/posts/:id` (write, SDK `lucra.posts.update`): Tag or archive a post - `GET /v1/programs` (read, SDK `lucra.programs.list`): List programs: your own (archived ones only with status=archived), or with scope=network the open programs on the Lucra Network - `GET /v1/programs/:id` (read, SDK `lucra.programs.retrieve`): Get a program - `POST /v1/programs` (write, SDK `lucra.programs.create`): Create a program - `PATCH /v1/programs/:id` (write, SDK `lucra.programs.update`): Update a program; every change in one request applies together, or none does --- # Submissions > Creators' videos and posts, and reviewing them. Source: https://www.onlucra.com/docs/api-submissions A submission is a creator's video in a program. In an organic program it's also linked to the post where it went live, and Lucra measures that post's views. ## Submit work First [upload the video](https://www.onlucra.com/docs/api-files) with `purpose=submission`. Then submit it, acting as the creator: ```ts await lucra.as("crtr_026UYfiw1Y6OvUpuKWiOpeL").submissions.create({ body: { program: "prog_00lhuAJjQgdtNt5WnqTBc95", file: "file_03d3QVSYynIFwgmYzYd7opT", note: "First cut", }, }) ``` - In an organic program, add `postUrl`: the Instagram or TikTok post the video went live as. - To resubmit after a revision request, send `revisionOf` with the original `sub_…` ID. - Submitting accepts any agreements the brand requires for submissions. ## Review Set `status` to `approved`, `rejected` or `revision_requested`. Add a `note` the creator sees: ```ts await lucra.submissions.update({ path: { id: "sub_14XA4WGiSOmaVI3B4QLm4Fs" }, body: { status: "revision_requested", note: "Trim the intro to three seconds." }, }) ``` An approved video can run as an [ad](https://www.onlucra.com/docs/api-ads). A post in an organic program is approved or rejected once its measurement ends; approving it pays the creator on its measured views. Then release the payment with `{ "payment": "approved" }`. Only the brand's owners and admins, or its own key, can do that. ## Brand videos A brand can add its own finished video to its library: upload it with `purpose=ad_creative`, then create a submission with `{ "file", "name" }` and no `program`. ## Endpoints - `GET /v1/submissions` (read, SDK `lucra.submissions.list`): List submissions - `GET /v1/submissions/:id` (read, SDK `lucra.submissions.retrieve`): Get a submission - `POST /v1/submissions` (write, SDK `lucra.submissions.create`): Submit work as a creator, or add a brand's own video, or an existing TikTok post to boost, to its library - `PATCH /v1/submissions/:id` (write, SDK `lucra.submissions.update`): Review, tag, file or archive a submission, or approve paying for a post; as its creator, withdraw it --- # Files > Upload videos, images and PDFs. Source: https://www.onlucra.com/docs/api-files Upload a file by sending its bytes with a `purpose`, and use the file ID it returns, like a submission's `file` or an account's `profilePicture`. ## Upload Send the raw bytes as the request body. `purpose` and `name` go in the query: ```bash curl -X POST "https://api.onlucra.com/v1/files?purpose=product_image&name=hero.jpg" \ -H "Authorization: Bearer $LUCRA_API_KEY" \ -H "Lucra-Version: 2026-10-01" \ --data-binary @hero.jpg ``` With the SDK, pass a `Blob` or `File` as the body: ```ts import { readFile } from "node:fs/promises" const { data: file } = await lucra.files.create({ query: { purpose: "product_image", name: "hero.jpg" }, body: new Blob([await readFile("hero.jpg")]), }) ``` Upload as the account that will use the file. A creator's video is uploaded acting as the creator (`Lucra-Account: crtr_…`). ## Purposes | `purpose` | What | | ------------------------- | -------------------------------------- | | `submission` | A creator's video for a program | | `ad_creative` | A brand's own video for its library | | `product_image` | A product image | | `profile_picture` | A logo or profile photo | | `program_brief` | A PDF brief | | `program_reference_video` | An example video for creators | | `agreement` | A PDF agreement | | `chat_attachment` | A file attached to a message | ## Large files For a large video, reserve the upload instead: send `size` and `contentType` (and optionally `checksum`, its SHA-256) with an empty body. The response's `upload` says where to `PUT` the bytes. Then finish with `PATCH /v1/files/:id` and `{ "status": "processing" }`. ## Processing Images are ready at once. Videos stay `processing` while Lucra checks and prepares them; poll `GET /v1/files/:id`, or wait for the `file.ready` [webhook](https://www.onlucra.com/docs/webhooks). A submission's video, or a program's example video, can be used while it's still `processing`: it shows once processing finishes. Using any other file before it's `ready` returns `409 file_processing`. ## Endpoints - `POST /v1/files` (write, SDK `lucra.files.create`): Upload a file - `GET /v1/files` (read, SDK `lucra.files.list`): List files - `GET /v1/files/:id` (read, SDK `lucra.files.retrieve`): Get a file - `PATCH /v1/files/:id` (write, SDK `lucra.files.update`): Finish or delete an upload --- # Ads > Campaigns, ad sets, ads and daily results. Source: https://www.onlucra.com/docs/api-ads Run approved submissions as ads on the brand's own ad accounts, and see what each creator's work drives. Connect an ad account first with a [connection](https://www.onlucra.com/docs/api-connections). ## Create a campaign A campaign runs on one Meta, TikTok, Google or Snapchat connection. Its `creatives` are approved submissions, and their creators earn what their [program pays](https://www.onlucra.com/docs/api-programs#pay). This one spends $50 a day: ```json { "name": "Spring launch – Meta", "connection": "conn_02PEZI7pHWLT3UpakbRmfNB", "destination": { "type": "website", "value": "https://acme.com/spring" }, "budget": { "daily": 5000 }, "schedule": { "startDate": "2026-10-10" }, "audience": { "countries": ["US"], "ageMin": 21 }, "creatives": ["sub_14XA4WGiSOmaVI3B4QLm4Fs"], "ad": { "primaryText": "Made for spring.", "callToAction": "shop_now" } } ``` Lucra sets the campaign up on the ad platform right away; it's `processing` until that's done. By default it then turns on. Send `"launch": false` to leave it `paused`, or `scheduledLaunchAt` to turn it on later. Change its `status` to `live`, `paused` or `archived` with `PATCH /v1/campaigns/:id`. [Webhooks](https://www.onlucra.com/docs/webhooks) report when publishing finishes (`campaign.published`) or needs attention. ## How creators are credited When creators earn a share of revenue or a fixed amount per lead, `attribution` sets what counts. `lucra` counts your [Lucra Pixel](https://www.onlucra.com/docs/tracking#conversions)'s matches of one conversion from each ad's link; `platform` counts what the ad platform reports. New campaigns default to `lucra` when your pixel is verified and the destination is a website: ```json { "attribution": { "source": "lucra", "conversion": "conv_1fK7pQ2wXy9LmN4bVc8RtZa" } } ``` Leave out `conversion` to use an active purchase (revenue share) or lead (per lead) conversion; Lucra creates one if you have none. It can't change once the campaign is on its ad platform. Ad-spend pay always counts the platform's spend. ## Ad defaults `PATCH /v1/ad-defaults` sets what new campaigns start with: objective, Meta Advantage+ audience, naming and tracking. ## Tracking `tracking` sets click tracking on a campaign: `urlParameters` on every platform, a Google `trackingUrlTemplate`, and TikTok click and impression trackers. You can change it on a live campaign. Every ad's link also carries `lucra_id` (its video) and `lucra_campaign` (the campaign), which the Lucra Pixel reads; Lucra sets them, replacing any already in the URL. ## Boost an existing TikTok post Run a post that's already on TikTok as a Spark Ad, with no upload. Add it to the brand's library with `POST /v1/submissions`, by the creator's Spark code, the post's full link, or both: ```json { "post": { "connection": "conn_02PEZI7pHWLT3UpakbRmfNB", "sparkCode": "#Abc123…" } } ``` With a code, the post is ready at once: its `existingPost.ready` is `true` and `existingPost.authorizedUntil` says when the code ends. Then put its `sub_` ID in a TikTok campaign's `creatives`, on the same connection. A campaign runs either existing posts or uploaded videos, and existing posts run only on TikTok. With just a link, Lucra asks the creator for a code. The submission's `adAuthorization` is the request (`platform: "tiktok"`). Read it with `GET /v1/ad-authorizations/:id` for its `requestUrl`: send that link to the creator, who pastes their code there, with no Lucra account needed. A creator who connected that TikTok account to Lucra also gets the request in the app. When the code arrives, you get `ad_authorization.granted` and the post is ready. If the creator sends you the code instead, send it with `POST /v1/submissions` again, or `PATCH /v1/ad-authorizations/:id` with `{ "status": "granted", "sparkCode": "…" }`. Three days before a code ends, Lucra asks the creator for a new one and sends `ad_authorization.requested`. An existing post never pays its creator through Lucra: it isn't part of a program. ## Ad sets and ads `GET /v1/ad-sets` and `GET /v1/ads` list what's running. `PATCH` renames, pauses, resumes or archives them. Meta and TikTok ad sets also take `bidding` and `audience`, including saved audiences (`GET /v1/ad-audiences`) and interests (`GET /v1/ad-interests`). Copy a campaign, ad set or ad with `POST …/copies`. ## Ad fields `GET /v1/ad-fields` lists every setting each platform takes, with the platform's own name and docs. ## Run as the creator To run a submission as a Meta Partnership Ad under the creator's own account, request an authorization with `POST /v1/ad-authorizations`. The creator grants it, and you get `ad_authorization.granted`. On TikTok, a campaign with `identity: "creator"` runs under the creator's whole account, which they approve once to your Business Center; until they have, the campaign save answers 422 saying what's missing. ## Ads Lucra didn't launch Lucra also reads back the other ads in each connected ad account: the last 90 days on the first 30-minute sync after the account connects, then on every sync like Lucra's own. List them with `GET /v1/ads?source=external`: each has `source: "external"`, its platform ID, and its campaign and ad set on the platform (`platformCampaign`, `platformAdSet`). They're read-only, and they're never billed, settled or paid out. Their `status` is `unknown`: Lucra reports their results, not their state. ## Analytics `GET /v1/analytics` returns daily spend, revenue, clicks, conversions and `paidViews` between `from` and `to`. Paid views are each platform's own video view count: Meta's 3-second views, TikTok's video plays, Snapchat's 2-second views and Google's TrueView views. Split each day with `group`, by `campaign`, `submission`, `creator`, `ad` or `platform`. `source=external` reads the ads Lucra didn't launch instead, and `source=all` both: ```ts const { data } = await lucra.analytics.retrieve({ query: { from: "2026-10-01", to: "2026-10-07", group: "creator" }, }) ``` ## Endpoints - `GET /v1/analytics` (read, SDK `lucra.analytics.retrieve`): Get daily ad results, or creators' organic results, optionally filtered and split by campaign, submission, creator, ad or platform; as a creator, your own work's ad results across brands - `POST /v1/campaigns` (write, SDK `lucra.campaigns.create`): Create and launch a campaign - `POST /v1/campaigns/:id/copies` (write, SDK `lucra.campaigns.copies.create`): Copy a campaign with its ad sets and ads, as a new campaign - `GET /v1/campaigns` (read, SDK `lucra.campaigns.list`): List campaigns - `GET /v1/campaigns/:id` (read, SDK `lucra.campaigns.retrieve`): Get a campaign - `PATCH /v1/campaigns/:id` (write, SDK `lucra.campaigns.update`): Change a campaign, its ad sets' budgets, or its status: launch, pause, resume, retry or archive - `GET /v1/ad-fields` (read, SDK `lucra.adFields.list`): List the settings each ad platform takes for campaigns, ad sets and ads - `GET /v1/ad-defaults` (read, SDK `lucra.adDefaults.retrieve`): Get what the account's new campaigns start with - `PATCH /v1/ad-defaults` (write, SDK `lucra.adDefaults.update`): Change what the account's new campaigns start with - `GET /v1/ad-audiences` (read, SDK `lucra.adAudiences.list`): List an ad account's saved audiences (Meta custom and lookalike audiences, TikTok audiences) - `GET /v1/ad-interests` (read, SDK `lucra.adInterests.list`): Search the interests an ad set can target on Meta or TikTok - `GET /v1/ad-sets` (read, SDK `lucra.adSets.list`): List ad sets (Meta ad sets, TikTok and Google ad groups, Snapchat ad squads) - `GET /v1/ad-sets/:id` (read, SDK `lucra.adSets.retrieve`): Get an ad set - `PATCH /v1/ad-sets/:id` (write, SDK `lucra.adSets.update`): Rename, pause, resume, archive or re-budget an ad set on its platform - `POST /v1/ad-sets/:id/copies` (write, SDK `lucra.adSets.copies.create`): Copy an ad set (and run it paused) in its campaign or another one - `GET /v1/ads` (read, SDK `lucra.ads.list`): List ads: the ones Lucra launched, or with `source=external` the other ads in your connected ad accounts - `GET /v1/ads/:id` (read, SDK `lucra.ads.retrieve`): Get an ad - `PATCH /v1/ads/:id` (write, SDK `lucra.ads.update`): Rename, pause, resume or archive an ad on its platform - `POST /v1/ads/:id/copies` (write, SDK `lucra.ads.copies.create`): Copy an ad (and run it paused) in its ad set or another one - `GET /v1/ad-authorizations` (read, SDK `lucra.adAuthorizations.list`): List ad authorizations: a brand's requests, or, as a creator, the ones asked of you - `GET /v1/ad-authorizations/:id` (read, SDK `lucra.adAuthorizations.retrieve`): Get an ad authorization - `POST /v1/ad-authorizations` (write, SDK `lucra.adAuthorizations.create`): Ask a submission's creator to authorize it as a Meta partnership ad, or an existing TikTok post's creator for a Spark code - `PATCH /v1/ad-authorizations/:id` (write, SDK `lucra.adAuthorizations.update`): Grant an ad authorization as its creator (on TikTok, with the post's Spark code), or revoke it --- # Attribution > Pixels, server events, conversions and creator codes. Source: https://www.onlucra.com/docs/api-attribution Track what creators drive on a brand's own site. A **pixel** records visitors' events, a **conversion** decides which events count, and a link from a creator's video credits them. The [tracking guide](https://www.onlucra.com/docs/tracking) covers installing the pixel in the browser. Start with `POST /v1/pixels`, then add a conversion for the event you care about, like a purchase. ## Pixels `POST /v1/pixels` creates one for the domains it may send from: ```json { "name": "Shop", "domains": ["https://shop.example.com"], "excludedCountries": ["DE"] } ``` ```json { "id": "pxl_0Q2m8VxR4sTnYb7KcLd3Ew", "name": "Shop", "domains": ["https://shop.example.com"], "excludedCountries": ["DE"], "status": "verified", "lastSeenAt": "2026-10-07T15:04:05.000Z", "snippet": "", "firstParty": { "path": "/lucra", "target": "https://a.onlucra.com", "snippet": "" }, "quality": { "events": 1840, "consented": 1622, "fromServer": 212, "withUserData": 240, "withClickIds": 610, "credited": 388, "located": 1835 }, "version": 2, "createdAt": "2026-10-01T15:04:05.000Z", "updatedAt": "2026-10-02T09:12:44.000Z" } ``` - `domains` are origins with no path, like `https://shop.example.com`. - `excludedCountries` are two-letter codes; visitors there aren't recorded. - `status` is `waiting` until the first event arrives, then `verified`; `archived` pixels accept no events. - `firstParty` is the recommended install: proxy `path` on your site to `target` and use its `snippet`. - `quality` counts the last 7 days' events. `PATCH /v1/pixels/:id` renames it, changes `domains` or `excludedCountries`, or archives it with `status: "archived"`. The `pixel.verified` [webhook](https://www.onlucra.com/docs/webhooks) fires on its first event. A connected Shopify store can install the pixel without code from **Settings → Lucra Pixel** in the app. ## Server events `POST /v1/pixels/:id/events` (`lucra.pixels.events.create` in the SDK) records an event from the brand's server, so ad blockers and closed tabs can't lose it. It returns `202`: ```json { "name": "purchase", "eventId": "order-1042", "orderId": "1042", "value": 4999, "currency": "USD", "pageUrl": "https://shop.example.com/thanks", "lucraId": "sub_14XA4WGiSOmaVI3B4QLm4Fs", "landedAt": "2026-10-05T18:20:00.000Z", "user": { "email": "jo@example.com", "phone": "+1 555 010 4477" }, "clickIds": { "fbc": "fb.1.1759685000000.IwAR2x", "fbp": "fb.1.1759600000000.1234567890" }, "ipAddress": "203.0.113.7", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 26_0 like Mac OS X) …" } ``` ```json { "eventId": "order-1042", "duplicate": false } ``` | Field | What | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | `name` | A standard event (`page_view`, `view_content`, `search`, `add_to_cart`, `begin_checkout`, `purchase`, `lead`, `sign_up`, `start_trial`, `subscribe`, `contact`) or your own, in snake_case. | | `eventId` | Send the browser event's ID to count it once. A repeat returns `duplicate: true` and confirms the browser event. | | `occurredAt` | Defaults to now; within the last 7 days. | | `value`, `currency` | In cents, with an ISO 4217 code. Send both or neither. | | `orderId` | Your order's ID. | | `pageUrl` | A page on the pixel's domains. Defaults to its first domain. | | `consent` | `granted` (default), `denied` or `unknown`. Without `granted`, leave out `lucraId`, `landedAt`, `user`, `clickIds`, `ipAddress` and `userAgent`. | | `lucraId` | `lucra_id` from the landing URL: the video the visitor came from. | | `discountCode` | The order's discount code, recorded with the event. | | `landedAt` | When the visitor arrived from the video or code, for attribution windows. | | `user` | `email`, `phone`, `firstName`, `lastName`, `city`, `state`, `zip`, `country`, `externalId`. Hashed (SHA-256) on arrival; hashes are kept as sent. | | `clickIds` | `fbc`, `fbp`, `gclid`, `gbraid`, `wbraid`, `ttclid`, `ttp`, `sccid`, `scid`. | | `ipAddress`, `userAgent` | The visitor's. The IP is kept 13 months. | | `location` | `country`, `region`, `city`, `postalCode`, like the shipping address. | | `actionSource` | `website` (default), `app`, `physical_store`, `phone_call`, `chat`, `email`, `system_generated` or `other`. | | `properties` | `productId`, `variantId`, `quantity`, `contents` (`{ id, quantity, price }`, up to 100) and `custom` (up to 25 fields). | A pixel takes up to 1,200 events a minute; past that you get `429`. ## Pixel events `GET /v1/pixel-events` lists what the pixels received, newest first. Filter by `pixel`, `conversion`, or `attributed=true` for events from a creator's video. `GET /v1/pixel-events/:id` returns one, with what ad platforms' conversion APIs take: ```json { "id": "pxev_3Hd8kQm1ZrT6yWv2NbLx0Pa", "pixel": "pxl_0Q2m8VxR4sTnYb7KcLd3Ew", "name": "purchase", "occurredAt": "2026-10-07T15:04:05.000Z", "receivedAt": "2026-10-07T15:04:05.412Z", "sentFrom": "server", "actionSource": "website", "consent": "granted", "value": 4999, "currency": "usd", "orderId": "1042", "pageUrl": "https://shop.example.com/thanks", "referrerUrl": null, "submission": "sub_14XA4WGiSOmaVI3B4QLm4Fs", "landedAt": "2026-10-05T18:20:00.000Z", "clickIds": { "fbc": "fb.1.1759685000000.IwAR2x" }, "user": { "email": "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8" }, "ipAddress": "203.0.113.7", "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 26_0 like Mac OS X) …", "properties": {}, "conversions": ["conv_1fK7pQ2wXy9LmN4bVc8RtZa"], "source": "paid_ad", "creator": "crtr_026UYfiw1Y6OvUpuKWiOpeL", "discountCode": null, "location": { "country": "US", "region": "California", "city": "Oakland", "postalCode": "94612", "timezone": "America/Los_Angeles", "latitude": 37.8, "longitude": -122.27 }, "network": { "asn": 7922, "organization": "Comcast" }, "device": { "type": "mobile", "os": "iOS", "browser": "Safari", "inApp": "tiktok", "language": "en-US" } } ``` - `sentFrom` is `server` for server events and browser events a server copy confirmed. - `source` is `paid_ad` (an ad's link) or `direct`; `creator` is who's credited. - `user` holds SHA-256 hashes, normalized for Meta, TikTok and Google. ## Conversions A conversion counts the events that matter, like purchases over $20: ```json { "name": "Purchase", "eventName": "purchase", "pageUrlContains": "/thanks", "minValue": { "amount": 2000, "currency": "USD" }, "attributionWindowDays": 30, "payBrowserEvents": false } ``` ```json { "id": "conv_1fK7pQ2wXy9LmN4bVc8RtZa", "name": "Purchase", "eventName": "purchase", "pageUrlContains": "/thanks", "minValue": { "amount": 2000, "currency": "usd" }, "attributionWindowDays": 30, "payBrowserEvents": false, "status": "active", "recorded": 412, "attributed": 96, "version": 1, "createdAt": "2026-10-01T15:04:05.000Z", "updatedAt": "2026-10-01T15:04:05.000Z" } ``` - `eventName` is a standard event or your own. - `pageUrlContains` and `minValue` narrow it; a minimum value matches only events in its currency. - `attributionWindowDays` (1–90, default 30) credits the creator when the visitor converts within that many days of landing from their video. Events without a landing time, like server events, are credited too. - `payBrowserEvents` lets browser-only events pay creators. Off by default, since anyone with the pixel ID can send browser events. - `recorded` counts matching events; `attributed`, those credited to a creator. `PATCH /v1/conversions/:id` changes it or archives it; events already recorded keep counting as they did. Each matching event sends `conversion.recorded` with `conversion`, `pixel_event` and, when credited to a video, `submission`. ### Pay per conversion Add a conversion rule to a program's or a retainer's [pay](https://www.onlucra.com/docs/api-payments#pay): ```json { "rules": [ { "conversion": "conv_1fK7pQ2wXy9LmN4bVc8RtZa", "amount": 500 }, { "conversion": "conv_7Rt2LmQ9xWb4NcV1kZp8YsD", "percent": 10 } ], "cap": 100000, "schedule": { "every": "week" } } ``` - `amount` pays a flat amount in cents per conversion; `percent` pays a share of its value, like an affiliate commission. A `percent` counts only events valued in USD for now. - Only conversions credited to the creator count: through their video's `lucra_id` link, within the conversion's window. One counts toward every program and retainer of that creator that pays on it. - Only server-confirmed events pay, unless the conversion has `payBrowserEvents: true`. - A retainer can pay only on conversions. A program pays them alongside its usual rule. ## Report `GET /v1/conversions/:id/report?groupBy=creator,country` breaks a conversion down by up to 3 of `creator`, `country`, `region`, `city`, `inApp`, `deviceType`, `source` and `day`. Narrow it with `from`, `to`, and `credited=true` for creators' only. ```json { "data": [ { "creator": "crtr_026UYfiw1Y6OvUpuKWiOpeL", "country": "US", "conversions": 58, "value": 289942, "currency": "usd" }, { "creator": null, "country": "CA", "conversions": 21, "value": 104979, "currency": "usd" } ] } ``` - `POST /v1/pixels` (write, SDK `lucra.pixels.create`): Create a pixel - `POST /v1/pixels/:id/events` (write, SDK `lucra.pixels.events.create`): Send a conversion from the brand's server; 202 once it's recorded for attribution - `GET /v1/pixels` (read, SDK `lucra.pixels.list`): List pixels - `GET /v1/pixels/:id` (read, SDK `lucra.pixels.retrieve`): Get a pixel and whether it's verified - `PATCH /v1/pixels/:id` (write, SDK `lucra.pixels.update`): Rename a pixel, change its domains or excluded countries, or archive it - `POST /v1/conversions` (write, SDK `lucra.conversions.create`): Create a conversion: the events that count as a sign-up, a purchase or your own goal - `GET /v1/conversions` (read, SDK `lucra.conversions.list`): List conversions, newest first - `GET /v1/conversions/:id` (read, SDK `lucra.conversions.retrieve`): Get a conversion and how many events counted as it - `PATCH /v1/conversions/:id` (write, SDK `lucra.conversions.update`): Change a conversion or archive it; events already recorded keep counting as they did - `GET /v1/conversions/:id/report` (read, SDK `lucra.conversions.report.retrieve`): Break a conversion down by creator, place, in-app browser, source or day, with counts and value - `GET /v1/pixel-events` (read, SDK `lucra.pixelEvents.list`): List the events your pixels received, newest first - `GET /v1/pixel-events/:id` (read, SDK `lucra.pixelEvents.retrieve`): Get one pixel event, with what ad platforms' conversion APIs take --- # Payments > Paying creators, retainers, balances and withdrawals. Source: https://www.onlucra.com/docs/api-payments Brands pay creators through Lucra. As soon as the brand's payment goes through, the creator's share goes to their own Stripe balance. It stays there until they withdraw it to their bank, whenever they choose (instantly, for a fee). Amounts are in cents. On money objects, `amount` is the gross, `fee` comes out of it, and `net` is what's left. Every write that moves money needs an `Idempotency-Key`. ## Fees These are the standard plan's defaults. An account's plan or agreed terms can differ; **Settings → Billing** shows its own. | Fee | Default | Paid by | | --------------------- | -------------- | --------- | | Payment to a creator | 1.50% of the payment | The brand | | Lucra fee | 1.25% of the first $100,000 a month, 1% up to $300,000, 0.75% up to $1,000,000, 0.5% above, of ad spend, or of attributed sales for revenue-share programs | The brand | | Retainer pay | 1.50% of the payment | The brand | | Standard withdrawal | Free | | | Instant withdrawal | 1.5% (min 50¢) | Creator | | Payout converted from USD | 1% | Creator | ## Pay a creator Send the creator and the `net` they should receive. The brand is charged that plus Lucra's 1.50% (`fee`), so the creator gets the full `net`: ```ts await lucra.payments.create({ body: { creator: "crtr_026UYfiw1Y6OvUpuKWiOpeL", net: 50000, note: "Launch bonus" }, headers: { "Idempotency-Key": "launch-bonus-crtr_026UYfiw1Y6OvUpuKWiOpeL" }, }) ``` The payment is `pending` until Stripe confirms the charge, then `succeeded`, or `failed` with a `payment.failed` webhook. A retry with the same `Idempotency-Key` returns the same payment and never charges twice. A payment waiting on the brand (`pending_approval`) is released with `PATCH /v1/payments/:id` and `{ "status": "approved" }`. Until it's sent to the creator (within minutes of the charge), the brand can dispute a payment with `{ "status": "disputed", "reason": "…" }`; once sent, the payment is final. ## Pay One `pay` object describes how programs and retainers pay. This one guarantees $500 a month, plus $8 per 1,000 views and $50 per approved video, up to $2,000: ```json { "base": 50000, "rules": [ { "per": "view", "amount": 800, "unit": 1000 }, { "on": "approved_submission", "amount": 5000 } ], "cap": 200000, "schedule": { "every": "month" } } ``` - `base` is guaranteed each period. Programs don't take one. - `rules` pay per result (`per`), per event (`on`), a percent of ad spend or revenue (`percentOf`), or per [conversion](https://www.onlucra.com/docs/api-attribution#pay-per-conversion) credited to the creator (`conversion`, with an `amount` or a `percent` of its value). - `cap` is the most paid per period. - `schedule` says when it pays: `{ "every": "week" }` (or `day`, `month`, `quarter`, `event`), or `{ "after": "measurement", "days": 7 }` for organic programs. - `approval: "manual"` holds each period's payment for you to approve (retainers only). ### Pay that can't be paid Pay is checked when it's saved. If it couldn't be paid as set, you get `422 invalid_pay` listing every problem: | `code` | When | | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `pay_invalid_value` | An `amount` under 1 cent, a `percent` not above 0 and at most 100, or a `unit` under 1. | | `pay_cap_zero` | `cap` is 0. Send `null` for no cap. | | `pay_cap_below_amount` | `cap` is below an amount paid in one go (an `on` rule's amount, a `per` rule's amount for one unit, or `base`), so it could never be paid. | | `pay_cap_required` | An organic program without a `cap`. | | `pay_base_needs_period` | A `base` on a program, or with an `event` or `measurement` schedule. A base needs a schedule of every `day`, `week`, `month`, or `quarter`. | | `pay_schedule_mismatch` | A retainer not paid every `day`, `week`, `month`, or `quarter`; a program on another schedule than its type's; or an organic program paying on ad results (ad spend, attributed revenue, impressions, clicks, installs, leads, purchases), which organic programs never have. | | `pay_duplicate_rule` | Two rules on the same results (the same metric and `scope`), which would pay them twice; combine them into one. | | `pay_scope_invalid` | A `scope.campaign` that isn't one of the brand's campaigns, or for a program, not one built from the program's work. A new program has none yet. | | `pay_exceeds_budget` | A single amount larger than the program's `budget.amount`. | | `pay_primary_missing` | A program without the rule its type pays per result (see [programs](https://www.onlucra.com/docs/api-programs#pay)). | | `pay_primary_changed` | Program pay, or a creator's pay, that changes what the program pays per result on (for example ad spend to leads). Change the amount instead. | | `pay_program_unsupported` | `"approval": "manual"` on a program, or pay per approved video on an organic program. | ## Retainers A retainer is recurring pay for one creator. Offer one with `POST /v1/retainers` and `{ "creator", "pay", "deliverables" }`, with an `Idempotency-Key`. A retainer can also pay only on conversions, like an affiliate deal: `{ "rules": [{ "conversion": "conv_1fK7pQ2wXy9LmN4bVc8RtZa", "amount": 500 }], "schedule": { "every": "week" } }`. The creator accepts or rejects it with `PATCH /v1/retainers/:id` and `{ "status": "accepted" }` (or `"rejected"`). A partner answers for its roster creator with `Lucra-Account: crtr_…`. Each period pays its `base` up front and earned results at the end. Cancel with `{ "status": "canceling" }`; the current period is still paid. ### Failed charges A declined charge retries after 1, 2, 2, and 2 days, then the retainer goes `past_due` until the brand updates its payment method. Program pay retries the same way. ## Balance `GET /v1/balance` shows a creator's earnings, or a partner's commissions ready to withdraw. Brands keep no balance: each charge goes to their saved card or bank. ## Payouts Every few minutes, Lucra sends each paid-for earning to the creator's Stripe balance, against the brand's charge, once they've verified their identity. `GET /v1/payouts` lists them. Nothing is paid out to a bank on its own: the balance stays until its owner withdraws it. ## Supported payout countries Payouts run on one of two rails, set by the payout country when the creator first sets up payouts. - **Stripe Connect**: Austria, Belgium, Bulgaria, Canada, Croatia, Cyprus, Czechia, Denmark, Estonia, Finland, France, Germany, Greece, Hungary, Iceland, Ireland, Italy, Latvia, Liechtenstein, Lithuania, Luxembourg, Malta, Netherlands, Norway, Poland, Portugal, Romania, Slovakia, Slovenia, Spain, Sweden, Switzerland, United Kingdom, United States. Earnings go to the creator's (or partner's) Stripe balance, which they withdraw whenever they choose. - **Stripe Global Payouts**: Australia, India, Indonesia, Israel, Japan, Jamaica, Mexico, Monaco, Morocco, New Zealand, San Marino, Singapore, Trinidad and Tobago, for creators only. Lucra keeps the earnings and pays them to the creator's bank every Monday in one payout, less the cross-border fee. They finish setup on Stripe's own page (the `url` from `POST /v1/connections`, even with `checkout: elements`), there's no withdrawing in between, and `GET /v1/withdrawals` lists the weekly payouts. Global Payouts countries appear only once Lucra switches them on. `GET /v1/payouts/countries` lists the countries open now. A country that isn't open is a 422 (`payout_country_unavailable`). The `country` enum on `POST /v1/connections` (provider `stripe`) lists every country on both rails. Creators anywhere else can't be paid out yet. ## Withdrawals `POST /v1/withdrawals` moves a balance to the bank anytime, with `speed` set to `standard` (free) or `instant`. Leave out `amount` to withdraw everything. A creator adds their bank account on their first withdrawal. Global Payouts creators are paid every Monday instead, so their withdrawals return `weekly_payouts_only`. ## Earnings `GET /v1/earnings` lists what a creator earned, with its status: `pending` (until the brand's payment goes through), `available`, `paid`, or `held` while a dispute is reviewed. ## Endpoints - `GET /v1/balance` (read, SDK `lucra.balance.retrieve`): Get the balance: the account's commissions, or a creator's earnings - `GET /v1/balance/tax` (read, SDK `lucra.balance.tax.retrieve`): Get US tax reporting: whether a taxpayer ID is on file, and what Lucra paid each year - `GET /v1/balance/transactions` (read, SDK `lucra.balance.transactions.list`): List every ledger line on the account - `POST /v1/payments` (write, SDK `lucra.payments.create`): Pay a creator - `GET /v1/payments` (read, SDK `lucra.payments.list`): List payments to creators - `GET /v1/payments/:id` (read, SDK `lucra.payments.retrieve`): Get a payment to a creator - `PATCH /v1/payments/:id` (write, SDK `lucra.payments.update`): Approve or dispute a payment to a creator - `GET /v1/payouts/countries` (read, SDK `lucra.payouts.countries.retrieve`): The payout countries that can be chosen now: a creator's (Stripe Global Payouts' too while it's on), or this brand's or partner's - `GET /v1/payouts` (read, SDK `lucra.payouts.list`): List payouts: to creators from this brand's earnings, received by this account, or (as a creator) the creator's own - `GET /v1/payouts/:id` (read, SDK `lucra.payouts.retrieve`): Get a payout - `GET /v1/earnings` (read, SDK `lucra.earnings.list`): List a creator's earnings - `GET /v1/earnings/:id` (read, SDK `lucra.earnings.retrieve`): Get one of a creator's earnings - `POST /v1/withdrawals` (write, SDK `lucra.withdrawals.create`): Withdraw from the balance to the bank account or debit card (not for Global Payouts creators, paid every Monday) - `GET /v1/withdrawals` (read, SDK `lucra.withdrawals.list`): List withdrawals from the balance, or a Global Payouts creator's weekly payouts - `GET /v1/withdrawals/:id` (read, SDK `lucra.withdrawals.retrieve`): Get a withdrawal - `GET /v1/retainers` (read, SDK `lucra.retainers.list`): List retainers - `GET /v1/retainers/:id` (read, SDK `lucra.retainers.retrieve`): Get a retainer - `POST /v1/retainers` (write, SDK `lucra.retainers.create`): Offer a creator a retainer - `PATCH /v1/retainers/:id` (write, SDK `lucra.retainers.update`): Cancel a retainer, or, as the creator, accept or reject an offer or cancel --- # Commerce > Products and samples. Source: https://www.onlucra.com/docs/api-commerce Products are what a brand promotes; samples get them into creators' hands. ## Products A brand's catalog comes from its [Shopify connection](https://www.onlucra.com/docs/api-connections) or is added by hand with `POST /v1/products`. Prices are in cents. Set an `accessCode` to let creators redeem a product with a code instead of an order. Attach products to a program in its `promoted.products`. Acting as a creator, `GET /v1/products` lists the products of the programs they're approved in; those are the ones they can request. ## Samples A creator requests a sample with `{ "product", "program" }`. The brand approves or rejects it with `PATCH /v1/samples/:id`, and approving it places the order or shares the access code. A brand can also send a sample directly: ```ts await lucra.samples.create({ body: { creator: "crtr_026UYfiw1Y6OvUpuKWiOpeL", product: "prod_04bst9MGXqblfqHhbR1iGGL", method: "order", }, }) ``` `method` is `order` (a free Shopify order) or `access_code`. The creator reads the code with `GET /v1/samples/:id?expand=accessCode`. ## Endpoints - `GET /v1/products` (read, SDK `lucra.products.list`): List products; as a creator, the ones attached to the programs you're approved in, which you can ask a sample of - `POST /v1/products` (write, SDK `lucra.products.create`): Add a product to the catalog - `GET /v1/products/:id` (read, SDK `lucra.products.retrieve`): Get a product - `PATCH /v1/products/:id` (write, SDK `lucra.products.update`): Edit, archive or set the access code of a product - `GET /v1/samples` (read, SDK `lucra.samples.list`): List samples - `POST /v1/samples` (write, SDK `lucra.samples.create`): Request a sample as a creator, or send one to a creator as the brand - `GET /v1/samples/:id` (read, SDK `lucra.samples.retrieve`): Get a sample; its creator can reveal an access code with expand=accessCode - `PATCH /v1/samples/:id` (write, SDK `lucra.samples.update`): Approve or reject a sample request --- # Messages > Conversations with creators. Source: https://www.onlucra.com/docs/api-messages Conversations between an account and the creators it works with. Everything an inbox needs is in the API, so you can build your own. ## Start a conversation `POST /v1/threads` opens a direct conversation with a creator you work with, or returns the existing one. Include a first message if you like: ```ts const { data: thread } = await lucra.threads.create({ body: { kind: "direct", with: "crtr_06ZqNevhUeDnqxzC6VkeuQr", message: { body: "Welcome aboard!" } }, }) ``` `kind: "program"` opens a program's group chat instead, with `program` set to its ID. ## Read and reply `GET /v1/threads` lists conversations, most recently active first. `GET /v1/messages?thread=thr_…` reads one. Reply with `POST /v1/messages`: ```ts await lucra.messages.create({ body: { thread: thread.id, body: "Loved the first cut!" } }) ``` `PATCH /v1/messages/:id` edits or deletes a message you sent (send its `version`), sets a reaction like `{ "reaction": { "emoji": "🎉", "active": true } }`, or reports a message. One change per request. ## Notes - A partner does all of this as a roster creator with `Lucra-Account: crtr_…`. - The `message.created` [webhook](https://www.onlucra.com/docs/webhooks) tells you when a message arrives. - Read state, muting and live updates are yours to keep in your own app: track what each of your users has seen by message ID. ## Endpoints - `GET /v1/messages` (read, SDK `lucra.messages.list`): List messages - `POST /v1/messages` (write, SDK `lucra.messages.create`): Send a message - `PATCH /v1/messages/:id` (write, SDK `lucra.messages.update`): Edit or delete a message the caller sent (at its version), react to a message, or report it; one change per request - `GET /v1/threads` (read, SDK `lucra.threads.list`): List the conversations the account (or, acting as a creator, the creator) takes part in, most recently active first - `POST /v1/threads` (write, SDK `lucra.threads.create`): Open a direct conversation with an account or creator you work with (an account can also message any creator it finds in the marketplace), a program's group chat or a Lucra Support thread; an existing one is returned - `GET /v1/threads/:id` (read, SDK `lucra.threads.retrieve`): Get a conversation; its messages are GET /v1/messages?thread= --- # Partners > Run Lucra for client brands and creators, like Stripe Connect. Source: https://www.onlucra.com/docs/api-partners A partner account runs Lucra for other businesses: agencies managing client brands, and developers building creator payments, programs or ads into their own product. It works much like Stripe Connect. Client brands are connected accounts, roster creators are payout recipients, and the `Lucra-Account` header plays the part of `Stripe-Account`. ## Set up 1. Sign up as an **Agency** or **Developer**; both are partner accounts. An existing brand can turn on partner tools in **Settings → Partner profile**. 2. Create a `write` key in **Settings → Developers → API**. 3. Set your [take rates](#take-rates). 4. Create [brands](https://www.onlucra.com/docs/api-brands) and add [creators](https://www.onlucra.com/docs/api-creators). Finish their setup with `POST /v1/connections`, and listen for [webhooks](https://www.onlucra.com/docs/webhooks). ## What your key reaches A partner key reaches its own account, every active client brand (including brands added later), and every roster creator. You don't list accounts on the key. Pick one per request with `Lucra-Account`, or `lucra.as(id)` in the SDK: ```ts const { data: page } = await lucra.brands.list() const brand = lucra.as(page.data[0].id) const { data: programs } = await brand.programs.list() ``` A brand whose relationship with you ended stays readable with `status: "ended"`, but can't be changed. ## Take rates A partner earns from the creators it brings and the brands it manages. Set the defaults with `PATCH /v1/account`: ```ts await lucra.account.update({ body: { creatorTakeRate: { percent: 15 }, brandFee: { of: "ad_spend", percent: 10 }, }, }) ``` - `creatorTakeRate` is your cut of each payment to a roster creator, up to 20%. Override it for one creator with `takeRate` on `PATCH /v1/creators/:id`. - `brandFee` is charged on the `ad_spend` or `gmv` of the brands you manage. Override it for one brand with `PATCH /v1/brands/:id`. ## Act as a creator Send `Lucra-Account: crtr_…` to do what a roster creator would: edit their profile, apply to or join programs, upload files, submit work, request samples, answer retainer offers, and read their payments and messages. Acting as a creator needs a `write` key. Endpoints that allow it are marked in the reference. ```ts await lucra .as("crtr_026UYfiw1Y6OvUpuKWiOpeL") .applications.create({ body: { program: "prog_00lhuAJjQgdtNt5WnqTBc95" } }) ``` ## Your own UI, or ours Build every screen yourself on the API, or drop in Lucra's onboarding, programs, wallet and other screens with [embeds](https://www.onlucra.com/docs/embeds). --- # Embeds > Put Lucra's screens inside your app. Source: https://www.onlucra.com/docs/embeds Embeds put Lucra's own screens inside your app: onboarding, programs, payments, ads, messages and the creator wallet. Each one runs in an iframe served by Lucra, so card, bank and sign-in details never touch your page. ## How it works 1. Add your site's origin, like `https://app.example.com`, to your key's **Embed origins** in **Settings → Developers → API**. 2. On your server, open an embed for a brand or creator with `POST /v1/embeds`. 3. In the browser, mount it with `@lucra/sdk/embed` or `@lucra/sdk/react`. Or skip the frontend and send the person to the embed's hosted `url`. ## Open an embed on your server Act for the brand (`acct_…`) or roster creator (`crtr_…`) the screens are for, list the components to allow, and return the `clientSecret` to your frontend: ```ts const { data: embed } = await lucra.as("crtr_026UYfiw1Y6OvUpuKWiOpeL").embeds.create({ body: { components: ["onboarding", "creator_programs", "wallet"] }, }) res.json({ clientSecret: embed.clientSecret }) ``` The `clientSecret` works once, from an allowed origin, for about 10 minutes. The hosted `url` lasts 24 hours and needs no allowed origin. End an embed early with `PATCH /v1/embeds/:id` and `status: "expired"`. ## Mount it in React Wrap your components in `LucraProvider` once. `fetchClientSecret` calls your server route above, once per mounted component: ```tsx import { loadLucra, LucraProvider, LucraOnboarding } from "@lucra/sdk/react" const lucra = loadLucra({ fetchClientSecret: () => fetch("/api/lucra-embed", { method: "POST" }).then((r) => r.json()).then((r) => r.clientSecret), }) export const Setup = () => ( window.location.assign("/dashboard")} /> ) ``` Each component has its own export: `LucraOnboarding`, `LucraCreatorPrograms`, `LucraWallet`, `LucraPrograms`, `LucraPayments`, `LucraAds` and `LucraMessages`. ## Mount it without React `@lucra/sdk/embed` mounts a component into any element, and reports what happens in it: ```ts import { loadLucra } from "@lucra/sdk/embed" const lucra = loadLucra({ fetchClientSecret }) lucra.mount("wallet", document.querySelector("#wallet")!) lucra.on("complete", ({ component }) => console.log(`${component} finished`)) ``` `lucra.defineElements()` registers custom elements instead, like ``. ## Components | Component | For | Does | | ------------------ | -------- | -------------------------------------------- | | `onboarding` | Both | Profile, payment setup and connected accounts | | `creator_programs` | Creators | Find programs, apply and submit work | | `wallet` | Creators | Balance, earnings and withdrawals | | `programs` | Brands | Create programs and review work | | `payments` | Brands | Pay creators and offer retainers | | `ads` | Brands | Create and manage campaigns | | `messages` | Both | Conversations | Open `onboarding` whenever an account still has a setup step, like a brand with no payment method or a creator who hasn't verified their identity. ## Appearance Components look like Lucra's app. Pass `appearance` to `loadLucra` (or to one component) with `theme` (`light`, `dark` or `auto`), `colors.primary` (a `#rrggbb` accent) and `radius` (0–24 px). ## Events Components emit `ready`, `change`, `complete`, `error` and `close`. In React, use the matching `onReady`, `onChange`, `onComplete`, `onError` and `onClose` props. When a component creates something, the event's `event` and `id` say what, like `submission.created` with the new `sub_…` ID. Your Content Security Policy needs `frame-src https://api.onlucra.com`. ## Endpoints - `POST /v1/embeds` (write, SDK `lucra.embeds.create`): Open Lucra UI for a brand or creator, hosted or embedded - `GET /v1/embeds` (read, SDK `lucra.embeds.list`): List the embeds you opened for a brand or creator - `GET /v1/embeds/:id` (read, SDK `lucra.embeds.retrieve`): Get an embed - `PATCH /v1/embeds/:id` (write, SDK `lucra.embeds.update`): End an embed early --- # All endpoints > Every endpoint and field. Source: https://www.onlucra.com/docs/api-reference ## Account - `GET /v1/account` (read, SDK `lucra.account.retrieve`): Get the key's own account - `PATCH /v1/account` (write, SDK `lucra.account.update`): Update the key's own account ## Analytics - `GET /v1/analytics` (read, SDK `lucra.analytics.retrieve`): Get daily ad results, or creators' organic results, optionally filtered and split by campaign, submission, creator, ad or platform; as a creator, your own work's ad results across brands ## Applications - `GET /v1/applications` (read, SDK `lucra.applications.list`): List applications - `GET /v1/applications/:id` (read, SDK `lucra.applications.retrieve`): Get an application - `POST /v1/applications` (write, SDK `lucra.applications.create`): Invite a creator to a program, or apply as one - `PATCH /v1/applications/:id` (write, SDK `lucra.applications.update`): Approve, reject or reopen an application, or set the creator's pay; as the creator, withdraw it or accept an invitation (approved) ## Balance - `GET /v1/balance` (read, SDK `lucra.balance.retrieve`): Get the balance: the account's commissions, or a creator's earnings - `GET /v1/balance/tax` (read, SDK `lucra.balance.tax.retrieve`): Get US tax reporting: whether a taxpayer ID is on file, and what Lucra paid each year - `GET /v1/balance/transactions` (read, SDK `lucra.balance.transactions.list`): List every ledger line on the account ## Brands - `GET /v1/brands` (read, SDK `lucra.brands.list`): List the brands this key reaches, and a partner's brands whose relationship ended - `GET /v1/brands/:id` (read, SDK `lucra.brands.retrieve`): Get a brand - `POST /v1/brands` (write, SDK `lucra.brands.create`): Create a brand a partner manages - `PATCH /v1/brands/:id` (write, SDK `lucra.brands.update`): Update a brand a partner manages ## Campaigns - `POST /v1/campaigns` (write, SDK `lucra.campaigns.create`): Create and launch a campaign - `POST /v1/campaigns/:id/copies` (write, SDK `lucra.campaigns.copies.create`): Copy a campaign with its ad sets and ads, as a new campaign - `GET /v1/campaigns` (read, SDK `lucra.campaigns.list`): List campaigns - `GET /v1/campaigns/:id` (read, SDK `lucra.campaigns.retrieve`): Get a campaign - `PATCH /v1/campaigns/:id` (write, SDK `lucra.campaigns.update`): Change a campaign, its ad sets' budgets, or its status: launch, pause, resume, retry or archive ## Ad fields - `GET /v1/ad-fields` (read, SDK `lucra.adFields.list`): List the settings each ad platform takes for campaigns, ad sets and ads ## Ad defaults - `GET /v1/ad-defaults` (read, SDK `lucra.adDefaults.retrieve`): Get what the account's new campaigns start with - `PATCH /v1/ad-defaults` (write, SDK `lucra.adDefaults.update`): Change what the account's new campaigns start with ## Ad templates - `GET /v1/ad-templates` (read, SDK `lucra.adTemplates.list`): List the account's ad templates, oldest first - `POST /v1/ad-templates` (write, SDK `lucra.adTemplates.create`): Save an ad template: copy, link, call to action and tracking for each ad platform - `GET /v1/ad-templates/:id` (read, SDK `lucra.adTemplates.retrieve`): Get an ad template - `PATCH /v1/ad-templates/:id` (write, SDK `lucra.adTemplates.update`): Change an ad template, make it the default, or archive it; campaigns made from it keep what they copied ## Ad audiences - `GET /v1/ad-audiences` (read, SDK `lucra.adAudiences.list`): List an ad account's saved audiences (Meta custom and lookalike audiences, TikTok audiences) ## Ad interests - `GET /v1/ad-interests` (read, SDK `lucra.adInterests.list`): Search the interests an ad set can target on Meta or TikTok ## Ad sets - `GET /v1/ad-sets` (read, SDK `lucra.adSets.list`): List ad sets (Meta ad sets, TikTok and Google ad groups, Snapchat ad squads) - `GET /v1/ad-sets/:id` (read, SDK `lucra.adSets.retrieve`): Get an ad set - `PATCH /v1/ad-sets/:id` (write, SDK `lucra.adSets.update`): Rename, pause, resume, archive or re-budget an ad set on its platform - `POST /v1/ad-sets/:id/copies` (write, SDK `lucra.adSets.copies.create`): Copy an ad set (and run it paused) in its campaign or another one ## Ads - `GET /v1/ads` (read, SDK `lucra.ads.list`): List ads: the ones Lucra launched, or with `source=external` the other ads in your connected ad accounts - `GET /v1/ads/:id` (read, SDK `lucra.ads.retrieve`): Get an ad - `PATCH /v1/ads/:id` (write, SDK `lucra.ads.update`): Rename, pause, resume or archive an ad on its platform - `POST /v1/ads/:id/copies` (write, SDK `lucra.ads.copies.create`): Copy an ad (and run it paused) in its ad set or another one ## Connections - `GET /v1/connections/:id` (read, SDK `lucra.connections.retrieve`): Get a connection - `GET /v1/connections/:id/setup` (read, SDK `lucra.connections.setup.retrieve`): List an ad platform's accounts, pages and identities to pick from, and the ones picked - `GET /v1/connections` (read, SDK `lucra.connections.list`): List connected ad platforms, Shopify and creator social accounts - `POST /v1/connections/:id/syncs` (write, SDK `lucra.connections.syncs.create`): Sync a connected shop's catalog, or, as a creator, mirror a social account's posts now - `POST /v1/connections` (write, SDK `lucra.connections.create`): Connect a platform, payouts or a card: returns the hosted page to send the person to - `PATCH /v1/connections/:id` (write, SDK `lucra.connections.update`): Pick a brand's ad account, page and identity after signing in, or disconnect a connection - `GET /v1/connections/:id/posts` (read, SDK `lucra.connections.posts.list`): List the posts Lucra mirrors from a creator's connected account, newest first - `POST /v1/connections/:id/logos` (write, SDK `lucra.connections.logos.create`): Upload a square logo (PNG or JPEG, at least 128 × 128, up to 5 MB) to a Google Ads connection ## Creators - `GET /v1/creators` (read, SDK `lucra.creators.list`): List the account's creators and, for a partner, its roster - `GET /v1/creators/:id` (read, SDK `lucra.creators.retrieve`): Get one of the account's creators, or, as a creator, yourself - `POST /v1/creators` (write, SDK `lucra.creators.create`): Add a creator to a partner's roster; one already on it is returned with 200, and one with a Lucra account is invited (status invited) until they accept - `PATCH /v1/creators/:id` (write, SDK `lucra.creators.update`): Update a creator: a brand's labels and status, a partner's roster creator, or, as a creator, your own profile ## Files - `POST /v1/files` (write, SDK `lucra.files.create`): Upload a file - `GET /v1/files` (read, SDK `lucra.files.list`): List files - `GET /v1/files/:id` (read, SDK `lucra.files.retrieve`): Get a file - `PATCH /v1/files/:id` (write, SDK `lucra.files.update`): Finish or delete an upload ## Messages - `GET /v1/messages` (read, SDK `lucra.messages.list`): List messages - `POST /v1/messages` (write, SDK `lucra.messages.create`): Send a message - `PATCH /v1/messages/:id` (write, SDK `lucra.messages.update`): Edit or delete a message the caller sent (at its version), react to a message, or report it; one change per request ## Payments - `POST /v1/payments` (write, SDK `lucra.payments.create`): Pay a creator - `GET /v1/payments` (read, SDK `lucra.payments.list`): List payments to creators - `GET /v1/payments/:id` (read, SDK `lucra.payments.retrieve`): Get a payment to a creator - `PATCH /v1/payments/:id` (write, SDK `lucra.payments.update`): Approve or dispute a payment to a creator ## Payouts - `GET /v1/payouts/countries` (read, SDK `lucra.payouts.countries.retrieve`): The payout countries that can be chosen now: a creator's (Stripe Global Payouts' too while it's on), or this brand's or partner's - `GET /v1/payouts` (read, SDK `lucra.payouts.list`): List payouts: to creators from this brand's earnings, received by this account, or (as a creator) the creator's own - `GET /v1/payouts/:id` (read, SDK `lucra.payouts.retrieve`): Get a payout ## Earnings - `GET /v1/earnings` (read, SDK `lucra.earnings.list`): List a creator's earnings - `GET /v1/earnings/:id` (read, SDK `lucra.earnings.retrieve`): Get one of a creator's earnings ## Withdrawals - `POST /v1/withdrawals` (write, SDK `lucra.withdrawals.create`): Withdraw from the balance to the bank account or debit card (not for Global Payouts creators, paid every Monday) - `GET /v1/withdrawals` (read, SDK `lucra.withdrawals.list`): List withdrawals from the balance, or a Global Payouts creator's weekly payouts - `GET /v1/withdrawals/:id` (read, SDK `lucra.withdrawals.retrieve`): Get a withdrawal ## Cities - `GET /v1/cities` (read, SDK `lucra.cities.list`): Search cities by name, exact matches first ## Keys - `GET /v1/keys/current` (read, SDK `lucra.keys.current.retrieve`): Get the calling key's webhook URL - `PATCH /v1/keys/current` (write, SDK `lucra.keys.current.update`): Set or clear the calling key's webhook URL; setting one issues a new signing secret, in this response only ## Events - `GET /v1/events` (read, SDK `lucra.events.list`): List the events sent to this key's webhook, newest first, to replay missed ones ## Agreements - `GET /v1/agreements/acceptances` (read, SDK `lucra.agreements.acceptances.list`): List creators' acceptances of your agreements; as a creator, the ones you accepted and the ones waiting on you - `GET /v1/agreements` (read, SDK `lucra.agreements.list`): List agreements - `GET /v1/agreements/:id` (read, SDK `lucra.agreements.retrieve`): Get an agreement - `GET /v1/agreements/:id/versions` (read, SDK `lucra.agreements.versions.list`): List an agreement's versions, newest first - `GET /v1/agreements/:id/versions/:number` (read, SDK `lucra.agreements.versions.retrieve`): Get a version of an agreement by its number - `POST /v1/agreements` (write, SDK `lucra.agreements.create`): Publish an agreement - `POST /v1/agreements/:id/versions` (write, SDK `lucra.agreements.versions.create`): Publish a new version of an agreement - `PATCH /v1/agreements/:id` (write, SDK `lucra.agreements.update`): Change where an agreement is required, or archive it ## Threads - `GET /v1/threads` (read, SDK `lucra.threads.list`): List the conversations the account (or, acting as a creator, the creator) takes part in, most recently active first - `POST /v1/threads` (write, SDK `lucra.threads.create`): Open a direct conversation with an account or creator you work with (an account can also message any creator it finds in the marketplace), a program's group chat or a Lucra Support thread; an existing one is returned - `GET /v1/threads/:id` (read, SDK `lucra.threads.retrieve`): Get a conversation; its messages are GET /v1/messages?thread= ## Posts - `GET /v1/posts` (read, SDK `lucra.posts.list`): List creators' posts in organic programs - `GET /v1/posts/:id` (read, SDK `lucra.posts.retrieve`): Get a post with its claim evidence - `PATCH /v1/posts/:id` (write, SDK `lucra.posts.update`): Tag or archive a post ## Ad authorizations - `GET /v1/ad-authorizations` (read, SDK `lucra.adAuthorizations.list`): List ad authorizations: a brand's requests, or, as a creator, the ones asked of you - `GET /v1/ad-authorizations/:id` (read, SDK `lucra.adAuthorizations.retrieve`): Get an ad authorization - `POST /v1/ad-authorizations` (write, SDK `lucra.adAuthorizations.create`): Ask a submission's creator to authorize it as a Meta partnership ad, or an existing TikTok post's creator for a Spark code - `PATCH /v1/ad-authorizations/:id` (write, SDK `lucra.adAuthorizations.update`): Grant an ad authorization as its creator (on TikTok, with the post's Spark code), or revoke it ## Pixels - `POST /v1/pixels` (write, SDK `lucra.pixels.create`): Create a pixel - `POST /v1/pixels/:id/events` (write, SDK `lucra.pixels.events.create`): Send a conversion from the brand's server; 202 once it's recorded for attribution - `GET /v1/pixels` (read, SDK `lucra.pixels.list`): List pixels - `GET /v1/pixels/:id` (read, SDK `lucra.pixels.retrieve`): Get a pixel and whether it's verified - `PATCH /v1/pixels/:id` (write, SDK `lucra.pixels.update`): Rename a pixel, change its domains or excluded countries, or archive it ## Conversions - `POST /v1/conversions` (write, SDK `lucra.conversions.create`): Create a conversion: the events that count as a sign-up, a purchase or your own goal - `GET /v1/conversions` (read, SDK `lucra.conversions.list`): List conversions, newest first - `GET /v1/conversions/:id` (read, SDK `lucra.conversions.retrieve`): Get a conversion and how many events counted as it - `PATCH /v1/conversions/:id` (write, SDK `lucra.conversions.update`): Change a conversion or archive it; events already recorded keep counting as they did - `GET /v1/conversions/:id/report` (read, SDK `lucra.conversions.report.retrieve`): Break a conversion down by creator, place, in-app browser, source or day, with counts and value ## Pixel events - `GET /v1/pixel-events` (read, SDK `lucra.pixelEvents.list`): List the events your pixels received, newest first - `GET /v1/pixel-events/:id` (read, SDK `lucra.pixelEvents.retrieve`): Get one pixel event, with what ad platforms' conversion APIs take ## Embeds - `POST /v1/embeds` (write, SDK `lucra.embeds.create`): Open Lucra UI for a brand or creator, hosted or embedded - `GET /v1/embeds` (read, SDK `lucra.embeds.list`): List the embeds you opened for a brand or creator - `GET /v1/embeds/:id` (read, SDK `lucra.embeds.retrieve`): Get an embed - `PATCH /v1/embeds/:id` (write, SDK `lucra.embeds.update`): End an embed early ## Products - `GET /v1/products` (read, SDK `lucra.products.list`): List products; as a creator, the ones attached to the programs you're approved in, which you can ask a sample of - `POST /v1/products` (write, SDK `lucra.products.create`): Add a product to the catalog - `GET /v1/products/:id` (read, SDK `lucra.products.retrieve`): Get a product - `PATCH /v1/products/:id` (write, SDK `lucra.products.update`): Edit, archive or set the access code of a product ## Retainers - `GET /v1/retainers` (read, SDK `lucra.retainers.list`): List retainers - `GET /v1/retainers/:id` (read, SDK `lucra.retainers.retrieve`): Get a retainer - `POST /v1/retainers` (write, SDK `lucra.retainers.create`): Offer a creator a retainer - `PATCH /v1/retainers/:id` (write, SDK `lucra.retainers.update`): Cancel a retainer, or, as the creator, accept or reject an offer or cancel ## Programs - `GET /v1/programs` (read, SDK `lucra.programs.list`): List programs: your own (archived ones only with status=archived), or with scope=network the open programs on the Lucra Network - `GET /v1/programs/:id` (read, SDK `lucra.programs.retrieve`): Get a program - `POST /v1/programs` (write, SDK `lucra.programs.create`): Create a program - `PATCH /v1/programs/:id` (write, SDK `lucra.programs.update`): Update a program; every change in one request applies together, or none does ## Samples - `GET /v1/samples` (read, SDK `lucra.samples.list`): List samples - `POST /v1/samples` (write, SDK `lucra.samples.create`): Request a sample as a creator, or send one to a creator as the brand - `GET /v1/samples/:id` (read, SDK `lucra.samples.retrieve`): Get a sample; its creator can reveal an access code with expand=accessCode - `PATCH /v1/samples/:id` (write, SDK `lucra.samples.update`): Approve or reject a sample request ## Submissions - `GET /v1/submissions` (read, SDK `lucra.submissions.list`): List submissions - `GET /v1/submissions/:id` (read, SDK `lucra.submissions.retrieve`): Get a submission - `POST /v1/submissions` (write, SDK `lucra.submissions.create`): Submit work as a creator, or add a brand's own video, or an existing TikTok post to boost, to its library - `PATCH /v1/submissions/:id` (write, SDK `lucra.submissions.update`): Review, tag, file or archive a submission, or approve paying for a post; as its creator, withdraw it --- # Overview > Let AI agents work in Lucra, within limits you set. Source: https://www.onlucra.com/docs/agents AI agents can work in Lucra the way people do: read programs and results, review work, message creators and, if you allow it, move money. They use the same API, with the same access checks. ## Connect | Option | Best for | | --------------------------------------- | ----------------------------------------------- | | [MCP server](https://www.onlucra.com/docs/mcp) | AI assistants; you sign in with OAuth | | [REST API](https://www.onlucra.com/docs/api-introduction) | Agents you run in your own code, with an API key | | [llms.txt](https://www.onlucra.com/docs/llms-txt) | Giving a coding agent these docs | ## Keep agents scoped - Grant `read` first, and `write` only once you trust the agent with every write, including money. See [Access](https://www.onlucra.com/docs/access). - Give each agent its own key or connection, so you can revoke one alone. - Send an `Idempotency-Key` on writes, so a retry never acts twice. --- # MCP server > Connect an AI client with OAuth. Source: https://www.onlucra.com/docs/mcp Lucra's MCP server gives an AI client a tool for every API endpoint, with the same access and checks as REST. Add it to your client and sign in; there's no key to copy. ## Connect Add a custom MCP server in your client with these settings, then sign in when it asks: | Field | Value | | -------------- | --------------------------------- | | URL | `https://api.onlucra.com/api/mcp` | | Transport | Streamable HTTP | | Authentication | OAuth | When you sign in, you pick the account and grant `read`, or `read` and `write`. API keys don't work here. Disconnect anytime in **Settings → Connected apps**. ## Tools Tools are named after SDK methods in snake case: `programs_list`, `submissions_update`, `payments_create`. Each takes: - path IDs, like `id`, - `query` and `body`, as in the API, - `account`, to act for another account (like the `Lucra-Account` header), - `idempotencyKey`, on writes. A `read` grant sees only read tools. File upload has no tool; upload over [REST](https://www.onlucra.com/docs/api-files). ## Troubleshooting | Result | Check | | ----------------------- | ------------------------------------------------ | | `401` | Sign in again; API keys are refused | | A write tool is missing | Reconnect and grant `write` | | `account_not_reachable` | `account` must be one your sign-in can reach | --- # llms.txt > These docs, indexed for coding agents. Source: https://www.onlucra.com/docs/llms-txt `https://www.onlucra.com/llms.txt` is an index of these docs for AI agents. Give it to your coding agent and it opens the pages it needs. Every page is also Markdown at its own URL plus `.md` (like [/docs/webhooks.md](https://www.onlucra.com/docs/webhooks.md)), and [/llms-full.txt](https://www.onlucra.com/llms-full.txt) holds them all in one file. For exact request and response shapes, point it at the [OpenAPI document](https://api.onlucra.com/v1/openapi.json) or [All endpoints](https://www.onlucra.com/docs/api-reference).