> **Reading as an AI agent?** This is the Markdown version of https://docs.meshy.ai/api/relief.
>
> - Full docs index: https://docs.meshy.ai/llms.txt
> - Single-fetch full content: https://docs.meshy.ai/llms-full.txt
> - Tool-calling access via MCP server: https://docs.meshy.ai/api/ai

---
# Relief API

Turn one photo into a bas-relief 3D model and a 16-bit depth map, for 3D printing, laser engraving, and CNC carving.

---

## POST /openapi/v1/relief -- Create a Relief task

Creates a Relief task. The task turns a photo into a bas-relief model. When it succeeds, the task produces the model as a GLB file and an STL file, a 16-bit grayscale depth map, and a preview image. To get only the depth map, set `should_generate_model` to `false`.

The task runs in the background.

A Relief task can't be the input of another endpoint, such as Remesh, Convert, or Auto Split. Those requests return `403`.

### Parameters

  - `image_url` · *string* · **required**

  The photo to turn into a relief, as a public URL or a data URI (`data:image/png;base64,...`).

  The image must be a JPG, JPEG, PNG, or WebP file of at most 20 MB, and at least 32 × 32 pixels. Its longer side can be at most about 16 times its shorter side. Any other image returns `400`. Meshy fills transparent areas with black before it builds the relief.

  - `variant` · *string* · default: `low`

  The relief style. It shapes the depth map and sets the default `carving_depth_mm`.

  Available values:
  * `high`: deeper carving, for 3D-printed pieces.
  * `low`: shallow carving, for laser engraving, CNC carving, and thin plaques.

  Sending `null` or `""` is the same as leaving it out. Any other value returns `400`.

  - `should_generate_model` · *boolean* · default: `true`

  Whether to build the relief model. Set it to `false` for a depth-map-only task, with no model and no preview image. Meshy still checks the size, carving, base, bend, and resolution parameters, but doesn't use them. It doesn't change the credit cost.

  - `size_mm` · *number* · default: `60`

  The length of the photo's longer side on the model, measured flat before any bend, from `20` to `250` millimeters. With `remove_base`, the model can be smaller.

  - `carving_mode` · *string* · default: `emboss`

  Whether the subject stands out from the plate or is cut into it.

  Available values:
  * `emboss`: the nearer parts of the photo stand out.
  * `engrave`: the depth is inverted, so the nearer parts are cut in.

  The depth map is the same in both modes.

  - `carving_depth_mm` · *number* · default: `6 (high), 3 (low)`

  How deep the relief is carved, from `0` to `60` millimeters. With `remove_base` set to `true`, `0` leaves an empty model. The task still succeeds and costs 20 credits, and its GLB and STL files have no geometry.

  - `remove_base` · *boolean* · default: `false`

  Whether to cut away the background and build no base plate. When it's `true`, `removal_threshold` sets what counts as background. Meshy checks `removal_threshold` either way.

**Only when `remove_base` is set:**
  - `removal_threshold` · *number* · default: `0.01`

  The depth below which Meshy cuts the background away, from `0.01` to `1`, where the depth map runs from `0` to `1`. A high value can cut away the whole relief and leave an empty model, which still succeeds and is charged.

  - `base_thickness_mm` · *number* · default: `1, or 0 with remove_base`

  The thickness of the base plate, from `0.1` to `10` millimeters. When `remove_base` is `true`, leave it out or send `0`.

  - `bend_mode` · *string* · default: `off`

  Whether to bend the relief, and which way. When it isn't `off`, `bend_axis` and `bend_angle` shape the bend. Meshy checks them either way.

  Available values:
  * `off`: a flat relief.
  * `convex`: the front bulges outward.
  * `concave`: the front curves inward.

**Only when `bend_mode` is set:**
  - `bend_axis` · *string* · default: `xz`

  Which edges the bend curls.

  Available values:
  * `xz`: the left and right edges.
  * `yz`: the top and bottom edges.

  - `bend_angle` · *number* · default: `60`

  The arc the bend spans, from `0` to `360` degrees. Below `1`, the relief stays flat.

  - `mesh_resolution` · *integer* · default: `1024`

  The number of mesh points along the photo's longer side, from `512` to `2048`. A higher value keeps finer detail and makes larger files. At the default `1024`, the GLB file is about 40 to 50 MB and the STL file about 80 to 105 MB. A square photo gives the larger sizes. At `2048`, they are about 150 MB and 300 MB. A bend makes them larger: the STL file can reach about 800 MB at `2048`.

### Returns

`202 Accepted`, with the ID of the new Relief task in `result`.

### Failure modes

  - `400 - Bad Request`

  The request can't run. The error message says which problem it found. For example:
  * **Missing parameter**: `image_url` wasn't sent.
  * **Invalid parameter**: A parameter has the wrong type, is outside its range, or isn't one of its available values. For example, `variant` isn't `high` or `low`, or `mesh_resolution` isn't an integer from `512` to `2048`.
  * **Base thickness**: `base_thickness_mm` isn't `0` with `remove_base` set to `true`, or is below `0.1` without it.
  * **Unreachable URL**: Meshy can't download the `image_url` image, for example because the URL returns `404` or times out.
  * **Invalid image**: The image isn't a JPG, JPEG, PNG, or WebP file, is over 20 MB, is smaller than 32 × 32 pixels, has too many pixels, or can't be read.
  * **Extreme aspect ratio**: The image's longer side is more than about 16 times its shorter side.

  Fix the request, then send it again. Meshy doesn't charge for it. See [Errors](/api/errors#request-errors).

  - `401 - Unauthorized`

  The API key is missing or invalid. Check the `Authorization` header.

  - `402 - Payment Required`

  For example:
  * **Insufficient credits**: The account doesn't have enough credits for this task, or the account reached a credit limit. Check your credit balance, then retry.
  * **Free plan**: Creating a task requires a paid plan. Upgrade your plan in your [subscription settings](https://www.meshy.ai/settings/subscription), then retry.

  - `403 - Forbidden`

  Content moderation blocked the image. Meshy doesn't charge for the request. Send a different image.

  - `429 - Too Many Requests`

  One of these limits was reached:
  * You sent too many requests. Wait, then retry. See [Rate limits](/api/rate-limits).
  * Too many of your tasks are queued or running. Wait for some to finish, then retry.
  * On an unlimited plan with no credits left, you reached the number of tasks that can run without credits. Wait for a task to finish, or add credits.

If a safety check declines the image while the task runs, the task fails with `task_error.type` set to `invalid_input` and `task_error.code` set to `provider_content_refused`. Meshy refunds the task's credits. Send a different image. See [Errors](/api/errors#invalid-input).

  **cURL**

  ```bash
  # Simple request: a low relief, 60 mm on its longer side
  curl https://api.meshy.ai/openapi/v1/relief \
  -X POST \
  -H "Authorization: Bearer ${YOUR_API_KEY}" \
  -H 'Content-Type: application/json' \
  -d '{
      "image_url": "<your publicly accessible image url or base64-encoded data URI>"
    }'

  # Advanced request: a deep 120 mm relief with a thicker base, curved from side to side
  curl https://api.meshy.ai/openapi/v1/relief \
  -X POST \
  -H "Authorization: Bearer ${YOUR_API_KEY}" \
  -H 'Content-Type: application/json' \
  -d '{
      "image_url": "<your publicly accessible image url or base64-encoded data URI>",
      "variant": "high",
      "size_mm": 120,
      "carving_depth_mm": 10,
      "base_thickness_mm": 3,
      "bend_mode": "convex",
      "bend_angle": 90
    }'
  ```

  ```javascript
  import axios from 'axios'

  const headers = { Authorization: `Bearer ${YOUR_API_KEY}` };

  // Simple request: a low relief, 60 mm on its longer side
  const payload = {
      image_url: "<your publicly accessible image url or base64-encoded data URI>"
  };

  // Advanced request: a deep 120 mm relief with a thicker base, curved from side to side
  const advancedPayload = {
      image_url: "<your publicly accessible image url or base64-encoded data URI>",
      variant: "high",
      size_mm: 120,
      carving_depth_mm: 10,
      base_thickness_mm: 3,
      bend_mode: "convex",
      bend_angle: 90
  };

  try {
      const response = await axios.post(
      'https://api.meshy.ai/openapi/v1/relief',
      payload, // or advancedPayload
    { headers }
  );
      console.log(response.data);
  } catch (error) {
      console.error(error);
  }
  ```

  ```python
  import requests

  headers = {
      "Authorization": f"Bearer {YOUR_API_KEY}"
  }

  # Simple request: a low relief, 60 mm on its longer side
  payload = {
      "image_url": "<your publicly accessible image url or base64-encoded data URI>"
  }

  # Advanced request: a deep 120 mm relief with a thicker base, curved from side to side
  advanced_payload = {
      "image_url": "<your publicly accessible image url or base64-encoded data URI>",
      "variant": "high",
      "size_mm": 120,
      "carving_depth_mm": 10,
      "base_thickness_mm": 3,
      "bend_mode": "convex",
      "bend_angle": 90
  }

  response = requests.post(
      "https://api.meshy.ai/openapi/v1/relief",
      headers=headers,
      json=payload,  # or advanced_payload
  )
  response.raise_for_status()
  print(response.json())
  ```

**Response**

```json
{
  "result": "0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20"
}
```

---

## GET /openapi/v1/relief/:id -- Get a Relief task

Returns a Relief task by its ID.

### Parameters

  - `id` · *path*

  The task ID from the create response.

### Returns

The [Relief task object](#the-relief-task-object).

  **cURL**

  ```bash
  curl https://api.meshy.ai/openapi/v1/relief/0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20 \
  -H "Authorization: Bearer ${YOUR_API_KEY}"
  ```

  ```javascript
  import axios from 'axios'

  const taskId = '0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20';
  const headers = { Authorization: `Bearer ${YOUR_API_KEY}` };

  try {
      const response = await axios.get(
      `https://api.meshy.ai/openapi/v1/relief/${taskId}`,
    { headers }
  );
      console.log(response.data);
  } catch (error) {
      console.error(error);
  }
  ```

  ```python
  import requests

  task_id = "0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20"
  headers = {
      "Authorization": f"Bearer {YOUR_API_KEY}"
  }

  response = requests.get(
      f"https://api.meshy.ai/openapi/v1/relief/{task_id}",
      headers=headers,
  )
  response.raise_for_status()
  print(response.json())
  ```

**Response**

```json
{
  "id": "0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20",
  "type": "relief",
  "model_urls": {
    "glb": "https://assets.meshy.ai/***/tasks/0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20/output/model.glb?Expires=***",
    "stl": "https://assets.meshy.ai/***/tasks/0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20/output/model.stl?Expires=***"
  },
  "depth_map_url": "https://assets.meshy.ai/***/tasks/0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20/output/depth.png?Expires=***",
  "thumbnail_url": "https://assets.meshy.ai/***/tasks/0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20/output/preview.png?Expires=***",
  "progress": 100,
  "status": "SUCCEEDED",
  "created_at": 1699999999000,
  "started_at": 1700000000000,
  "finished_at": 1700000060000,
  "expires_at": 1700259260000,
  "task_error": null,
  "consumed_credits": 20
}
```

---

## DELETE /openapi/v1/relief/:id -- Delete a Relief task

Permanently deletes a Relief task, including its model, depth map, and data. You can't undo a delete.

### Path parameters

  - `id` · *path*

  The ID of the task to delete.

### Task status

Deleting a `PENDING` task refunds the credits it used when you created it.

An `IN_PROGRESS` task can't be deleted. The request returns `409 Conflict`,
and the task keeps running. Meshy doesn't refund a task that has started,
so deleting it would cost you both the credits and the result. Wait for it
to reach `SUCCEEDED` or `FAILED`, then delete it.

Deleting a `SUCCEEDED` task doesn't refund its credits. A `FAILED` task was already refunded when it failed.

### Returns

`200 OK` when the task is deleted, or `409 Conflict` when the task is
`IN_PROGRESS`.

  **cURL**

  ```bash
  curl --request DELETE \
    --url https://api.meshy.ai/openapi/v1/relief/0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20 \
    -H "Authorization: Bearer ${YOUR_API_KEY}"
  ```

  ```javascript
  import axios from 'axios'

  const taskId = '0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20'
  const headers = { Authorization: `Bearer ${YOUR_API_KEY}` }

  try {
    await axios.delete(
      `https://api.meshy.ai/openapi/v1/relief/${taskId}`,
      { headers }
    )
  } catch (error) {
    console.error(error)
  }
  ```

  ```python
  import requests

  task_id = "0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20"
  headers = {
      "Authorization": f"Bearer {YOUR_API_KEY}"
  }

  response = requests.delete(
      f"https://api.meshy.ai/openapi/v1/relief/{task_id}",
      headers=headers,
  )
  response.raise_for_status()
  ```

**Response**

```json
// 200 OK on success, with an empty body.
//
// 409 Conflict when the task is IN_PROGRESS. The task keeps running:
{
  "message": "Task is IN_PROGRESS and cannot be deleted. Credits for a task the worker has already started are not refundable; wait for it to reach SUCCEEDED, FAILED or CANCELED, then delete it."
}
```

---

## GET /openapi/v1/relief -- List Relief tasks

Returns a list of your Relief tasks.

### Parameters

  - `page_num` · *integer*

  The page number to return. Starts at and defaults to `1`.

  - `page_size` · *integer*

  The number of tasks per page, up to `100`. Defaults to `10`. Meshy treats a value from `101` to `255` as `100`, and a larger value returns `400`.

  - `sort_by` · *string*

  The sort order.

  Available values:
  * `+created_at`: oldest first
  * `-created_at`: newest first

### Returns

A paginated list of [Relief task objects](#the-relief-task-object).

  **cURL**

  ```bash
  curl https://api.meshy.ai/openapi/v1/relief?page_size=10 \
  -H "Authorization: Bearer ${YOUR_API_KEY}"
  ```

  ```javascript
  import axios from 'axios'

  const headers = { Authorization: `Bearer ${YOUR_API_KEY}` };

  try {
   const response = await axios.get(
     `https://api.meshy.ai/openapi/v1/relief?page_size=10`,
  { headers }
    );
   console.log(response.data);
  } catch (error) {
   console.error(error);
  }
  ```

  ```python
  import requests

  headers = {
      "Authorization": f"Bearer {YOUR_API_KEY}"
  }

  response = requests.get(
      "https://api.meshy.ai/openapi/v1/relief",
      headers=headers,
      params={"page_size": 10}
  )
  response.raise_for_status()
  print(response.json())
  ```

**Response**

```json
[
  {
    "id": "0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20",
    "type": "relief",
    "model_urls": {
      "glb": "https://assets.meshy.ai/***/tasks/0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20/output/model.glb?Expires=***",
      "stl": "https://assets.meshy.ai/***/tasks/0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20/output/model.stl?Expires=***"
    },
    "depth_map_url": "https://assets.meshy.ai/***/tasks/0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20/output/depth.png?Expires=***",
    "thumbnail_url": "https://assets.meshy.ai/***/tasks/0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20/output/preview.png?Expires=***",
    "progress": 100,
    "status": "SUCCEEDED",
    "created_at": 1699999999000,
    "started_at": 1700000000000,
    "finished_at": 1700000060000,
    "expires_at": 1700259260000,
    "task_error": null,
    "consumed_credits": 20
  }
]
```

---

## GET /openapi/v1/relief/:id/stream -- Stream a Relief task

Streams a Relief task's updates as Server-Sent Events (SSE).

### Parameters

  - `id` · *path*

  The task ID from the create response.

### Returns

A stream of [Relief task objects](#the-relief-task-object) as Server-Sent Events.

Each `message` event holds the full task object, the same one [Get a Relief task](#retrieve-a-relief-task) returns. While the task is `PENDING` or `IN_PROGRESS`, the fields that change between events are `progress`, `status`, `started_at`, and `preceding_tasks` (present only while tasks are ahead of this one). `model_urls`, `depth_map_url`, and `thumbnail_url` get their values when the task reaches `SUCCEEDED`. A depth-map-only task keeps `model_urls` `{}` and an empty `thumbnail_url`.

An `error` event holds only `status_code` and `message`, so check the event name before you read `status`.

  **cURL**

  ```bash
  curl -N https://api.meshy.ai/openapi/v1/relief/0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20/stream \
  -H "Authorization: Bearer ${YOUR_API_KEY}"
  ```

  ```javascript
  const response = await fetch(
    'https://api.meshy.ai/openapi/v1/relief/0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20/stream',
    {
      headers: { Authorization: `Bearer ${YOUR_API_KEY}` }
    }
  );

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = '';
  let event = 'message';

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });
    const lines = buffer.split('\n');
    buffer = lines.pop();

    for (const line of lines) {
      if (line.startsWith('event:')) {
        event = line.slice(6).trim();
      } else if (line.startsWith('data:')) {
        const data = JSON.parse(line.slice(5));
        console.log(event, data);

        if (event === 'error') {
          reader.cancel(); // {"status_code": ..., "message": ...}
        } else if (['SUCCEEDED', 'FAILED'].includes(data.status)) {
          reader.cancel();
        }
      }
    }
  }
  ```

  ```python
  import requests
  import json

  headers = {
      "Authorization": f"Bearer {YOUR_API_KEY}",
      "Accept": "text/event-stream"
  }

  response = requests.get(
      'https://api.meshy.ai/openapi/v1/relief/0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20/stream',
      headers=headers,
      stream=True
  )

  event = 'message'
  for line in response.iter_lines():
      if not line:
          continue
      if line.startswith(b'event:'):
          event = line.decode('utf-8')[6:].strip()
      elif line.startswith(b'data:'):
          data = json.loads(line.decode('utf-8')[5:])
          print(event, data)

          if event == 'error':
              break  # {"status_code": ..., "message": ...}
          if data['status'] in ['SUCCEEDED', 'FAILED']:
              break

  response.close()
  ```

  **Response Stream**

  ```javascript
  // Error event example
  event: error
  data: {
    "status_code": 404,
    "message": "Task not found"
  }

  // Message event examples illustrate task progress (other task fields omitted here for brevity;
  // each frame is the full task object).
  event: message
  data: {
    "id": "0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20",
    "progress": 0,
    "status": "PENDING"
  }

  event: message
  data: {
    "id": "0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20",
    "type": "relief",
    "model_urls": {
      "glb": "https://assets.meshy.ai/***/tasks/0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20/output/model.glb?Expires=***",
      "stl": "https://assets.meshy.ai/***/tasks/0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20/output/model.stl?Expires=***"
    },
    "depth_map_url": "https://assets.meshy.ai/***/tasks/0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20/output/depth.png?Expires=***",
    "thumbnail_url": "https://assets.meshy.ai/***/tasks/0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20/output/preview.png?Expires=***",
    "progress": 100,
    "status": "SUCCEEDED",
    "created_at": 1699999999000,
    "started_at": 1700000000000,
    "finished_at": 1700000060000,
    "expires_at": 1700259260000,
    "task_error": null,
    "consumed_credits": 20
  }
  ```

---

## The Relief task object

A Relief task object describes one relief made from a photo. `model_urls`, `depth_map_url`, and `thumbnail_url` are always present, and empty until the task succeeds.

  - `id` · *string*

  The task ID. Treat it as an opaque string, and don't rely on its format.

  - `type` · *string*

  The task type, always `relief`.

  - `model_urls` · *object*

  Download URLs for the relief model. The files can be large; [`mesh_resolution`](#create-a-relief-task) lists typical sizes.

- `glb` · *string*

  The GLB file, without texture. Its units are meters. Absent for a depth-map-only task.

- `stl` · *string*

  The same model as a binary STL file. Its units are millimeters, so it opens at the relief's real size, with Z up. Absent for a depth-map-only task.

  - `depth_map_url` · *string*

  Download URL for the 16-bit grayscale depth map the relief is built from. Brighter pixels are higher. The values are relative heights, not millimeters. Laser engraving and CNC software can use it as a height map. The file is about 1 to 2 MB. Empty if there is none.

  - `thumbnail_url` · *string*

  Download URL for a preview image of the relief model. Empty if none was rendered, including for a depth-map-only task.

  - `progress` · *integer*

  How much of the task is done, as a percentage. It's `0` until the task starts and `100` when it succeeds.

  - `status` · *string*

  The task status, one of `PENDING`, `IN_PROGRESS`, `SUCCEEDED`, or `FAILED`.

  - `preceding_tasks` · *integer*

  The number of tasks ahead of this one in the queue. The field is left out when the value is `0`, so it appears only while tasks are ahead of this one.

  - `created_at` · *timestamp*

  Time the task was created, in Unix milliseconds.

  - `started_at` · *timestamp*

  Time the task started, in Unix milliseconds. It's `0` until the task starts.

  - `finished_at` · *timestamp*

  Time the task finished, in Unix milliseconds. It's `0` until the task finishes.

  - `expires_at` · *timestamp*

  Time the task's result expires, in Unix milliseconds. It's `0` until the task finishes.

  - `task_error` · *object*

  The error details when the task fails. See [Errors](/api/errors#task-errors).

  - `consumed_credits` · *integer*

  The number of credits the task used: `20`, or `0` for a `FAILED` task, which Meshy refunds.

**The Relief Task Object**

```json
{
  "id": "0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20",
  "type": "relief",
  "model_urls": {
    "glb": "https://assets.meshy.ai/***/tasks/0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20/output/model.glb?Expires=***",
    "stl": "https://assets.meshy.ai/***/tasks/0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20/output/model.stl?Expires=***"
  },
  "depth_map_url": "https://assets.meshy.ai/***/tasks/0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20/output/depth.png?Expires=***",
  "thumbnail_url": "https://assets.meshy.ai/***/tasks/0199c3d2-5a7e-7b14-9e2f-3c8d1a6b4f20/output/preview.png?Expires=***",
  "progress": 100,
  "status": "SUCCEEDED",
  "created_at": 1699999999000,
  "started_at": 1700000000000,
  "finished_at": 1700000060000,
  "expires_at": 1700259260000,
  "task_error": null,
  "consumed_credits": 20
}
```

**Depth map only**

```json
{
  "id": "0199c3d2-6b8f-7c25-8f3a-4d9e2b7c5a31",
  "type": "relief",
  "model_urls": {},
  "depth_map_url": "https://assets.meshy.ai/***/tasks/0199c3d2-6b8f-7c25-8f3a-4d9e2b7c5a31/output/depth.png?Expires=***",
  "thumbnail_url": "",
  "progress": 100,
  "status": "SUCCEEDED",
  "created_at": 1699999999000,
  "started_at": 1700000000000,
  "finished_at": 1700000030000,
  "expires_at": 1700259230000,
  "task_error": null,
  "consumed_credits": 20
}
```
