> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fourthwall.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Working with Images

> Upload an image and reference it as a resource on other Memberships endpoints

Many Memberships endpoints reference images by an **image resource ID** rather than accepting a raw file. For example, [Create Tier](/api-reference/membership/tiers/create-tier) takes an `imageResourceId`, [Create Video Post](/api-reference/membership/posts/create-video-post) takes an `imageResourceId`, and [Create Post Series](/api-reference/membership/series/create-post-series) takes a `coverImageResourceId` and a `thumbnailResourceId`.

Before you can set one of those fields, you must upload the image and obtain its `resourceId`. Uploading is a three-step flow: **create the image**, **upload the bytes directly to storage**, then **confirm the upload**.

<Note>
  All image endpoints require the `memberships_write` OAuth scope. API keys have full access.
</Note>

## Overview

```mermaid theme={null}
sequenceDiagram
    participant Client
    participant Fourthwall
    participant Storage as Cloud Storage

    Client->>Fourthwall: ① POST /memberships/images<br/>{ byteSize, checksum }
    Fourthwall->>Client: ② { uploadData: { url, headers, signedId }, resourceId }
    Client->>Storage: ③ PUT bytes to uploadData.url<br/>(with uploadData.headers)
    Client->>Fourthwall: ④ PATCH /memberships/images/{resourceId}/confirm<br/>{ signedId }
    Fourthwall->>Client: ⑤ 204 No Content
    Note over Client: Use resourceId as imageResourceId,<br/>thumbnailResourceId, etc.
```

1. Ask Fourthwall to create an image, sending the file's `byteSize` and `checksum`.
2. Fourthwall responds with a signed, pre-authorized `uploadData.url`, the `headers` you must send with the upload, a `signedId`, and the `resourceId` you'll ultimately reference.
3. Upload the raw image bytes **directly to cloud storage** using the returned URL and headers.
4. Confirm the upload with Fourthwall, passing back the `signedId`.
5. Once confirmed, the `resourceId` is ready to be used on any endpoint that accepts an image resource.

## Step 1 — Create the image

Compute the image's byte size and a base64-encoded **MD5** checksum, then call [Create Image](/api-reference/membership/resources/create-image).

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.fourthwall.com/open-api/v1.1/memberships/images" \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "byteSize": 20481,
      "checksum": "7vJP+O8buKShZwMbdl7wJQ=="
    }'
  ```

  ```javascript JavaScript theme={null}
  import { readFile } from "node:fs/promises";
  import { createHash } from "node:crypto";

  const bytes = await readFile("./tier-image.png");
  const checksum = createHash("md5").update(bytes).digest("base64");

  const res = await fetch(
    "https://api.fourthwall.com/open-api/v1.1/memberships/images",
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${accessToken}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ byteSize: bytes.length, checksum }),
    },
  );

  const { uploadData, resourceId } = await res.json();
  ```
</CodeGroup>

The response contains everything you need for the next two steps:

```json theme={null}
{
  "uploadData": {
    "url": "https://storage.googleapis.com/...",
    "headers": {
      "content-MD5": "7vJP+O8buKShZwMbdl7wJQ==",
      "content-Disposition": "inline; filename=\"7c8f5486-7841-4890-841f-53a3ebe2b445\"; filename*=UTF-8''7c8f5486-7841-4890-841f-53a3ebe2b445",
      "cache-Control": "public, max-age=2592000"
    },
    "signedId": "eyJfcmFpbHMiOnsib..."
  },
  "resourceId": 123456
}
```

| Field                 | Description                                                         |
| --------------------- | ------------------------------------------------------------------- |
| `uploadData.url`      | Pre-authorized URL to `PUT` the raw image bytes to.                 |
| `uploadData.headers`  | Headers you **must** send verbatim with the direct upload.          |
| `uploadData.signedId` | Opaque token identifying this upload. Pass it back in Step 3.       |
| `resourceId`          | The ID you'll use as `imageResourceId`, `thumbnailResourceId`, etc. |

<Warning>
  The `checksum` you send here must be the base64-encoded MD5 of the exact bytes
  you upload in Step 2. Storage rejects the upload if the `content-MD5` header
  does not match the uploaded content.
</Warning>

## Step 2 — Upload the bytes directly

`PUT` the raw image bytes to `uploadData.url`, sending every header from
`uploadData.headers`. This request goes to cloud storage directly — **do not**
send your Fourthwall `Authorization` header to it.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PUT "$UPLOAD_URL" \
    -H "Content-MD5: 7vJP+O8buKShZwMbdl7wJQ==" \
    -H "Content-Disposition: inline; filename=\"...\"" \
    -H "Cache-Control: public, max-age=2592000" \
    --data-binary "@tier-image.png"
  ```

  ```javascript JavaScript theme={null}
  await fetch(uploadData.url, {
    method: "PUT",
    headers: uploadData.headers,
    body: bytes,
  });
  ```
</CodeGroup>

A successful upload returns a `200 OK` from storage with no body.

## Step 3 — Confirm the upload

Tell Fourthwall the bytes are in place by calling
[Confirm Image Upload](/api-reference/membership/resources/confirm-post) with the
`resourceId` in the path and the `signedId` from Step 1 in the body.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PATCH "https://api.fourthwall.com/open-api/v1.1/memberships/images/123456/confirm" \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "signedId": "eyJfcmFpbHMiOnsib..."
    }'
  ```

  ```javascript JavaScript theme={null}
  await fetch(
    `https://api.fourthwall.com/open-api/v1.1/memberships/images/${resourceId}/confirm`,
    {
      method: "PATCH",
      headers: {
        Authorization: `Bearer ${accessToken}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ signedId: uploadData.signedId }),
    },
  );
  ```
</CodeGroup>

A successful confirmation returns `204 No Content`. The image `resourceId` is now
ready to use.

## Step 4 — Reference the image

Pass the `resourceId` into whichever field the target endpoint expects. For
example, when creating a paid tier:

```bash cURL theme={null}
curl -X POST "https://api.fourthwall.com/open-api/v1.1/memberships/tiers" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Gold",
    "imageResourceId": 123456
  }'
```

The same `resourceId` can be used for any image field across the Memberships API:

| Field                  | Used by                                                                                                                                                                                                    |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `imageResourceId`      | [Create Tier](/api-reference/membership/tiers/create-tier), [Create Audio Post](/api-reference/membership/posts/create-audio-post), [Create Video Post](/api-reference/membership/posts/create-video-post) |
| `thumbnailResourceId`  | [Create Post Tag](/api-reference/membership/tags/create-post-tag), [Create Post Series](/api-reference/membership/series/create-post-series)                                                               |
| `coverImageResourceId` | [Create Post Series](/api-reference/membership/series/create-post-series)                                                                                                                                  |

<Info>
  Use `https://api.fourthwall.com` for production. For testing against staging,
  swap in `https://api.staging.fourthwall.com`.
</Info>
