9 operations. Every schema and example on this page is generated from the platform contract.
Internal-admin. Previews made in the range (newest first, 200 at most) with their status, photos as 1-hour signed links, and the funnel (created > sent > opened > Make it mine > claimed > live) split by channel and preview type. Test previews are left out unless include_test. Expired previews are purged on the way in.
Previews made in the last N days. Default 30d.
7d30d90d365dcold_dmcomment_replycategory_pagein_personadasked_firstInclude test previews. Default false.
cold_dmcomment_replycategory_pagein_personadasked_firstdraftsentopenedclaimedliveexpiredname_onlywith_photoslogoproductcreatedmarked_sentopenedmake_it_mine_tappedclaimedlivecold_dmcomment_replycategory_pagein_personadasked_firstcreatedmarked_sentopenedmake_it_mine_tappedclaimedlivename_onlywith_photoscreatedmarked_sentopenedmake_it_mine_tappedclaimedlivecurl -G "https://www.membber.com/api/v1/admin-previews" \
-H "Authorization: Bearer $MEMBBER_TOKEN"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/admin-previews");
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.adminPreviewsList().ok.body.json
print(response){
"previews": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"url": "https://example.com/image.jpg",
"ig_handle": "<ig_handle>",
"shop_name": "<shop_name>",
"kind": "<kind>",
"channel": "cold_dm",
"items": [
{
"name": "Example name",
"price_pence": 1500,
"price_from_caption": true,
"photo_id": "b40c2e48-0000-4000-8000-d0c5000000b4"
}
],
"is_test": true,
"status": "draft",
"preview_type": "name_only",
"expires_at": "<expires_at>",
"expires_in_days": 1,
"created_at": "<created_at>",
"updated_at": "<updated_at>",
"sent_at": "<sent_at>",
"followed_up_at": "<followed_up_at>",
"first_opened_at": "<first_opened_at>",
"last_opened_at": "<last_opened_at>",
"open_count": 1,
"claimed_at": "<claimed_at>",
"claimed_store_id": "6a0dc31d-0000-4000-8000-d0c50000006a",
"ownership_verified_at": "<ownership_verified_at>",
"claim_undone_at": "<claim_undone_at>",
"photos": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"role": "logo",
"sort": 1,
"width": 1,
"height": 1,
"url": "https://example.com/image.jpg",
"created_at": "<created_at>"
}
]
}
],
"truncated": true,
"funnel": {
"from": "<from>",
"to": "<to>",
"include_test": true,
"stages": [
{
"stage": "created",
"previews": 1,
"of_previous": 1,
"of_created": 1
}
],
"by_channel": [
{
"channel": "cold_dm",
"stages": [
{
"stage": "created",
"previews": 1,
"of_previous": 1,
"of_created": 1
}
]
}
],
"by_preview_type": [
{
"preview_type": "name_only",
"stages": [
{
"stage": "created",
"previews": 1,
"of_previous": 1,
"of_created": 1
}
]
}
],
"outcomes": {
"not_for_me": 1,
"not_for_me_reasons": {
"not_selling_now": 1,
"happy_with_dms": 1,
"use_something_else": 1,
"none": 1
},
"expired": 1,
"deleted": 1,
"do_not_contact": 1
}
}
}Internal-admin. Refuses a handle on the do-not-contact list (409 DO_NOT_CONTACT, with the date) and a second unclaimed preview for the same handle (409 PREVIEW_EXISTS, with its id). Send an Idempotency-Key (UUID) header: a retry with the same key returns the same preview.
Her Instagram handle, with or without @, or her profile link.
cookiescakescinnamon-bunspizzatakeawaymeal-prepfish-and-chipscoffeestreetwearbrowniescheesecakesflowerschristmascake-shedsmarket-stallsbutchersfood-vansothercold_dmcomment_replycategory_pagein_personadasked_firstUp to 3. No photo_id yet: photos are added after the preview exists.
A test preview (a fake handle), left out of the funnel.
cold_dmcomment_replycategory_pagein_personadasked_firstdraftsentopenedclaimedliveexpiredname_onlywith_photoslogoproductcurl -X POST "https://www.membber.com/api/v1/admin-previews" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"ig_handle": "<ig_handle>",
"shop_name": "<shop_name>",
"kind": "cookies",
"channel": "cold_dm"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/admin-previews", {
body: {
ig_handle: "<ig_handle>",
shop_name: "<shop_name>",
kind: "cookies",
channel: "cold_dm"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.adminPreviewCreate(
body: .json(.init(
igHandle: "<ig_handle>",
shopName: "<shop_name>",
kind: .cookies,
channel: .coldDm
))
).ok.body.json
print(response){
"preview": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"url": "https://example.com/image.jpg",
"ig_handle": "<ig_handle>",
"shop_name": "<shop_name>",
"kind": "<kind>",
"channel": "cold_dm",
"items": [
{
"name": "Example name",
"price_pence": 1500,
"price_from_caption": true,
"photo_id": "b40c2e48-0000-4000-8000-d0c5000000b4"
}
],
"is_test": true,
"status": "draft",
"preview_type": "name_only",
"expires_at": "<expires_at>",
"expires_in_days": 1,
"created_at": "<created_at>",
"updated_at": "<updated_at>",
"sent_at": "<sent_at>",
"followed_up_at": "<followed_up_at>",
"first_opened_at": "<first_opened_at>",
"last_opened_at": "<last_opened_at>",
"open_count": 1,
"claimed_at": "<claimed_at>",
"claimed_store_id": "6a0dc31d-0000-4000-8000-d0c50000006a",
"ownership_verified_at": "<ownership_verified_at>",
"claim_undone_at": "<claim_undone_at>",
"photos": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"role": "logo",
"sort": 1,
"width": 1,
"height": 1,
"url": "https://example.com/image.jpg",
"created_at": "<created_at>"
}
]
}
}Internal-admin. Deletes an unclaimed preview, its photo rows and its stored photos. The funnel keeps an anonymous "deleted" event. A claimed preview is refused (409 PREVIEW_CLAIMED).
curl -X DELETE "https://www.membber.com/api/v1/admin-previews/ce5f1b03-0000-4000-8000-d0c5000000ce" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.DELETE("/api/v1/admin-previews/{previewId}", {
params: { path: { previewId: "ce5f1b03-0000-4000-8000-d0c5000000ce" } },
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.adminPreviewDelete(
path: .init(previewId: "ce5f1b03-0000-4000-8000-d0c5000000ce")
).ok.body.json
print(response){
"deleted": true,
"id": "00000d1b-0000-4000-8000-d0c500000000"
}Internal-admin. Edits are refused once she has claimed it (409 PREVIEW_CLAIMED). A follow-up needs a send first (409 NOT_SENT), and there is only ever one.
cookiescakescinnamon-bunspizzatakeawaymeal-prepfish-and-chipscoffeestreetwearbrowniescheesecakesflowerschristmascake-shedsmarket-stallsbutchersfood-vansothercold_dmcomment_replycategory_pagein_personadasked_firstRecord that the founder sent the link, or sent the one follow-up. Repeats are no-ops.
sentfollowed_upcold_dmcomment_replycategory_pagein_personadasked_firstdraftsentopenedclaimedliveexpiredname_onlywith_photoslogoproductcurl -X PATCH "https://www.membber.com/api/v1/admin-previews/ce5f1b03-0000-4000-8000-d0c5000000ce" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"shop_name": "<shop_name>",
"kind": "cookies",
"channel": "cold_dm",
"items": [
{
"name": "Example name",
"price_pence": 1500,
"price_from_caption": true,
"photo_id": "b40c2e48-0000-4000-8000-d0c5000000b4"
}
],
"mark": "sent"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.PATCH("/api/v1/admin-previews/{previewId}", {
params: { path: { previewId: "ce5f1b03-0000-4000-8000-d0c5000000ce" } },
body: {
shop_name: "<shop_name>",
kind: "cookies",
channel: "cold_dm",
items: [
{
name: "Example name",
price_pence: 1500,
price_from_caption: true,
photo_id: "b40c2e48-0000-4000-8000-d0c5000000b4"
}
],
mark: "sent"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.adminPreviewUpdate(
path: .init(previewId: "ce5f1b03-0000-4000-8000-d0c5000000ce"),
body: .json(.init(
shopName: "<shop_name>",
kind: .cookies,
channel: .coldDm,
items: [.init(
name: "Example name",
pricePence: 1500,
priceFromCaption: true,
photoId: "b40c2e48-0000-4000-8000-d0c5000000b4"
)],
mark: .sent
))
).ok.body.json
print(response){
"preview": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"url": "https://example.com/image.jpg",
"ig_handle": "<ig_handle>",
"shop_name": "<shop_name>",
"kind": "<kind>",
"channel": "cold_dm",
"items": [
{
"name": "Example name",
"price_pence": 1500,
"price_from_caption": true,
"photo_id": "b40c2e48-0000-4000-8000-d0c5000000b4"
}
],
"is_test": true,
"status": "draft",
"preview_type": "name_only",
"expires_at": "<expires_at>",
"expires_in_days": 1,
"created_at": "<created_at>",
"updated_at": "<updated_at>",
"sent_at": "<sent_at>",
"followed_up_at": "<followed_up_at>",
"first_opened_at": "<first_opened_at>",
"last_opened_at": "<last_opened_at>",
"open_count": 1,
"claimed_at": "<claimed_at>",
"claimed_store_id": "6a0dc31d-0000-4000-8000-d0c50000006a",
"ownership_verified_at": "<ownership_verified_at>",
"claim_undone_at": "<claim_undone_at>",
"photos": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"role": "logo",
"sort": 1,
"width": 1,
"height": 1,
"url": "https://example.com/image.jpg",
"created_at": "<created_at>"
}
]
}
}Internal-admin. The path is minted by the server under previews/<previewId>/ in the PRIVATE bucket drops-previews. JPEG, PNG or WebP up to 5 MB. Refused when the role is already full (1 logo, 6 products).
logoproductimage/jpegimage/pngimage/webpcurl -X POST "https://www.membber.com/api/v1/admin-previews/ce5f1b03-0000-4000-8000-d0c5000000ce/photos" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"role": "logo",
"content_type": "image/jpeg",
"byte_size": 1
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/admin-previews/{previewId}/photos", {
params: { path: { previewId: "ce5f1b03-0000-4000-8000-d0c5000000ce" } },
body: {
role: "logo",
content_type: "image/jpeg",
byte_size: 1
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.adminPreviewPhotoUpload(
path: .init(previewId: "ce5f1b03-0000-4000-8000-d0c5000000ce"),
body: .json(.init(
role: .logo,
contentType: .image/jpeg,
byteSize: 1
))
).ok.body.json
print(response){
"path": "<path>",
"token": "<token>",
"signed_url": "https://example.com/image.jpg"
}Internal-admin. Removes the photo row and its stored object; an item that used it loses its photo.
cold_dmcomment_replycategory_pagein_personadasked_firstdraftsentopenedclaimedliveexpiredname_onlywith_photoslogoproductcurl -X DELETE "https://www.membber.com/api/v1/admin-previews/ce5f1b03-0000-4000-8000-d0c5000000ce/photos/dc8482ed-0000-4000-8000-d0c5000000dc" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.DELETE("/api/v1/admin-previews/{previewId}/photos/{photoId}", {
params: { path: { previewId: "ce5f1b03-0000-4000-8000-d0c5000000ce", photoId: "dc8482ed-0000-4000-8000-d0c5000000dc" } },
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.adminPreviewPhotoDelete(
path: .init(previewId: "ce5f1b03-0000-4000-8000-d0c5000000ce", photoId: "dc8482ed-0000-4000-8000-d0c5000000dc")
).ok.body.json
print(response){
"preview": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"url": "https://example.com/image.jpg",
"ig_handle": "<ig_handle>",
"shop_name": "<shop_name>",
"kind": "<kind>",
"channel": "cold_dm",
"items": [
{
"name": "Example name",
"price_pence": 1500,
"price_from_caption": true,
"photo_id": "b40c2e48-0000-4000-8000-d0c5000000b4"
}
],
"is_test": true,
"status": "draft",
"preview_type": "name_only",
"expires_at": "<expires_at>",
"expires_in_days": 1,
"created_at": "<created_at>",
"updated_at": "<updated_at>",
"sent_at": "<sent_at>",
"followed_up_at": "<followed_up_at>",
"first_opened_at": "<first_opened_at>",
"last_opened_at": "<last_opened_at>",
"open_count": 1,
"claimed_at": "<claimed_at>",
"claimed_store_id": "6a0dc31d-0000-4000-8000-d0c50000006a",
"ownership_verified_at": "<ownership_verified_at>",
"claim_undone_at": "<claim_undone_at>",
"photos": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"role": "logo",
"sort": 1,
"width": 1,
"height": 1,
"url": "https://example.com/image.jpg",
"created_at": "<created_at>"
}
]
}
}Internal-admin. Checks the stored object exists, is an allowed image type (by its first bytes, not only its declared type) and is within 5 MB, then attaches it (1 logo, 6 products). A bad or surplus upload is removed from storage. A retry with the same path returns the same preview.
logoproductcold_dmcomment_replycategory_pagein_personadasked_firstdraftsentopenedclaimedliveexpiredname_onlywith_photoslogoproductcurl -X POST "https://www.membber.com/api/v1/admin-previews/ce5f1b03-0000-4000-8000-d0c5000000ce/photos/confirm" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"path": "<path>",
"role": "logo"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/admin-previews/{previewId}/photos/confirm", {
params: { path: { previewId: "ce5f1b03-0000-4000-8000-d0c5000000ce" } },
body: {
path: "<path>",
role: "logo"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.adminPreviewPhotoConfirm(
path: .init(previewId: "ce5f1b03-0000-4000-8000-d0c5000000ce"),
body: .json(.init(
path: "<path>",
role: .logo
))
).ok.body.json
print(response){
"preview": {
"id": "00000d1b-0000-4000-8000-d0c500000000",
"url": "https://example.com/image.jpg",
"ig_handle": "<ig_handle>",
"shop_name": "<shop_name>",
"kind": "<kind>",
"channel": "cold_dm",
"items": [
{
"name": "Example name",
"price_pence": 1500,
"price_from_caption": true,
"photo_id": "b40c2e48-0000-4000-8000-d0c5000000b4"
}
],
"is_test": true,
"status": "draft",
"preview_type": "name_only",
"expires_at": "<expires_at>",
"expires_in_days": 1,
"created_at": "<created_at>",
"updated_at": "<updated_at>",
"sent_at": "<sent_at>",
"followed_up_at": "<followed_up_at>",
"first_opened_at": "<first_opened_at>",
"last_opened_at": "<last_opened_at>",
"open_count": 1,
"claimed_at": "<claimed_at>",
"claimed_store_id": "6a0dc31d-0000-4000-8000-d0c50000006a",
"ownership_verified_at": "<ownership_verified_at>",
"claim_undone_at": "<claim_undone_at>",
"photos": [
{
"id": "00000d1b-0000-4000-8000-d0c500000000",
"role": "logo",
"sort": 1,
"width": 1,
"height": 1,
"url": "https://example.com/image.jpg",
"created_at": "<created_at>"
}
]
}
}Internal-admin. Newest first, 200 at most. Handle, source and date only.
Handle search (contains).
not_for_mestop_replyadmincurl -G "https://www.membber.com/api/v1/admin-previews/do-not-contact" \
-H "Authorization: Bearer $MEMBBER_TOKEN"import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.GET("/api/v1/admin-previews/do-not-contact");
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.adminPreviewDncList().ok.body.json
print(response){
"entries": [
{
"ig_handle": "<ig_handle>",
"source": "not_for_me",
"added_at": "<added_at>"
}
],
"truncated": true
}Internal-admin. Adds the handle (she replied STOP, or the founder decided). Any unclaimed preview for the handle is deleted with it, photos included. Idempotent: an existing entry keeps its first date.
stop_replyadminnot_for_mestop_replyadmincurl -X POST "https://www.membber.com/api/v1/admin-previews/do-not-contact" \
-H "Authorization: Bearer $MEMBBER_TOKEN" \
-H "Idempotency-Key: 1f0e2d3c-4b5a-4678-9abc-def012345678" \
-H "Content-Type: application/json" \
-d '{
"ig_handle": "<ig_handle>",
"source": "stop_reply"
}'import { createMembberClient } from "@membber/sdk-ts";
const membber = createMembberClient({
getAccessToken: () => process.env.MEMBBER_TOKEN,
});
const { data, error } = await membber.raw.POST("/api/v1/admin-previews/do-not-contact", {
body: {
ig_handle: "<ig_handle>",
source: "stop_reply"
},
headers: { "Idempotency-Key": crypto.randomUUID() },
});
if (error) {
// Typed error envelope: { error: { code, message, requestId } }
throw new Error(`${error.error.code}: ${error.error.message}`);
}
console.log(data);import MembberSwift
let client = MembberClient(
serverURL: MembberClient.productionServerURL,
tokenProvider: { session.accessToken }
)
let response = try await client.api.adminPreviewDncAdd(
body: .json(.init(
igHandle: "<ig_handle>",
source: .stopReply
))
).ok.body.json
print(response){
"entry": {
"ig_handle": "<ig_handle>",
"source": "not_for_me",
"added_at": "<added_at>"
},
"already": true,
"deleted_preview_id": "eb13e4d8-0000-4000-8000-d0c5000000eb"
}