# List a service in the Marketplace

> List an Ur Next Door Marketplace service with the API: category lookup, service mode, location rules, up to 3 packages priced in rupees, and FAQs.

Source: https://docs.urnextdoor.com/guides/create-service/

A service is a Marketplace listing that buyers can find and book: what you offer, where you offer it, and up to three priced packages. It takes a few lookups first, so work through the steps in order.

**Endpoint:** `POST /api/v1/services` · **Scope:** `content` (and `read` for the lookups) · **Quota:** `service`

## Step 1: Pick a category

Get the category tree from `GET /api/v1/service-categories`. Top level groups contain `children`; use a child's `id` as `category_id`.

**cURL**

```bash
curl "https://urnextdoor.com/api/v1/service-categories" \
  -H "Authorization: Bearer $UND_API_KEY"
```

**JavaScript**

```js
const res = await fetch("https://urnextdoor.com/api/v1/service-categories", {
  headers: {
    Authorization: `Bearer ${process.env.UND_API_KEY}`,
  },
});
console.log(res.status, await res.json());
```

**Python**

```python
import os

import requests

api_key = os.environ["UND_API_KEY"]

response = requests.get(
    "https://urnextdoor.com/api/v1/service-categories",
    headers={
        "Authorization": f"Bearer {api_key}",
    },
    timeout=30,
)
print(response.status_code, response.json())
```

**PHP**

```php
<?php
$apiKey = getenv('UND_API_KEY');

$ch = curl_init('https://urnextdoor.com/api/v1/service-categories');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . $apiKey,
    ],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

echo $status, PHP_EOL, $body, PHP_EOL;
```

Sample response: `200 OK`

```json
{
  "ok": true,
  "categories": [
    {
      "id": 3,
      "name": "Development",
      "slug": "development",
      "icon": null,
      "description": "Websites, apps and software.",
      "count": 42,
      "children": [
        {
          "id": 12,
          "name": "Web development",
          "slug": "web-development",
          "count": 17
        }
      ]
    }
  ],
  "request_id": "9f8e7d6c5b4a3921"
}
```

## Step 2: Choose how you deliver

`service_mode` is one of:

| Value | Meaning | Location needed? |
| --- | --- | --- |
| `online` | Delivered remotely, anywhere | No |
| `in_person` | At a place, in your service area | Yes |
| `both` | Either | Yes |

If you leave `service_mode` out, it is `both`, so a location is required.

## Step 3: Set the location (unless online)

For `in_person` and `both`, send:

- `country_code`: an ISO country code such as `IN`.
- `postal_code`: a postal code that is valid for that country.
- At least a `state_id`. Add `district_id` and `city_id` to narrow it. If you send a `district_id`, also send a `city_id`.
- Optionally `area` (free text, up to 120 characters), for example a neighbourhood.

Get the ids from `GET /api/v1/geo`: `level=states&country=IN`, then `level=districts&state=19`, then `level=cities&state=19&district=329`. See [Look up ids](https://docs.urnextdoor.com/guides/lookups.md).

## Step 4: Add packages and FAQs

Up to 3 `packages`. Each package needs a positive `price` in **Indian rupees** (send `4999` for 4,999 rupees, not paise). A package without a positive price is dropped.

| Package field | Notes |
| --- | --- |
| `name` | Up to 120 characters. Defaults to Basic, Standard and Premium by position. |
| `description` | Up to 500 characters. |
| `price` | Rupees, greater than 0. |
| `delivery_days` | 1 or more. Defaults to 1. |
| `revisions` | Revisions included. `-1` means unlimited. |
| `features` | Up to 20 short lines, up to 200 characters each. |

Up to 20 `faqs`, each `{ "q": "...", "a": "..." }` (question up to 200 characters, answer up to 1000).

## Step 5: Create the service

An online service with two packages:

**cURL**

```bash
curl -X POST "https://urnextdoor.com/api/v1/services" \
  -H "Authorization: Bearer $UND_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "title": "Business website in 7 days",
  "body": "<p>I build fast, mobile friendly websites for small shops and clinics.</p><ul><li>Design and build</li><li>Hosting setup</li></ul>",
  "category_id": 12,
  "summary": "A fast, mobile friendly website for your shop or clinic.",
  "service_mode": "online",
  "packages": [
    {
      "name": "Starter",
      "description": "One page site",
      "price": 4999,
      "delivery_days": 5,
      "revisions": 2,
      "features": [
        "1 page",
        "Contact form"
      ]
    },
    {
      "name": "Business",
      "description": "Up to 5 pages",
      "price": 14999,
      "delivery_days": 7,
      "revisions": -1,
      "features": [
        "5 pages",
        "Blog",
        "Basic SEO"
      ]
    }
  ],
  "faqs": [
    {
      "q": "Do you provide hosting?",
      "a": "I set it up on your account, so you own it."
    }
  ],
  "tags": [
    "web-design"
  ]
}'
```

**JavaScript**

```js
const res = await fetch("https://urnextdoor.com/api/v1/services", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.UND_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    title: "Business website in 7 days",
    body: "<p>I build fast, mobile friendly websites for small shops and clinics.</p><ul><li>Design and build</li><li>Hosting setup</li></ul>",
    category_id: 12,
    summary: "A fast, mobile friendly website for your shop or clinic.",
    service_mode: "online",
    packages: [
      {
        name: "Starter",
        description: "One page site",
        price: 4999,
        delivery_days: 5,
        revisions: 2,
        features: ["1 page", "Contact form"],
      },
      {
        name: "Business",
        description: "Up to 5 pages",
        price: 14999,
        delivery_days: 7,
        revisions: -1,
        features: ["5 pages", "Blog", "Basic SEO"],
      },
    ],
    faqs: [
      {
        q: "Do you provide hosting?",
        a: "I set it up on your account, so you own it.",
      },
    ],
    tags: ["web-design"],
  }),
});
console.log(res.status, await res.json());
```

**Python**

```python
import os
import uuid

import requests

api_key = os.environ["UND_API_KEY"]

response = requests.post(
    "https://urnextdoor.com/api/v1/services",
    headers={
        "Authorization": f"Bearer {api_key}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "title": "Business website in 7 days",
        "body": "<p>I build fast, mobile friendly websites for small shops and clinics.</p><ul><li>Design and build</li><li>Hosting setup</li></ul>",
        "category_id": 12,
        "summary": "A fast, mobile friendly website for your shop or clinic.",
        "service_mode": "online",
        "packages": [
            {
                "name": "Starter",
                "description": "One page site",
                "price": 4999,
                "delivery_days": 5,
                "revisions": 2,
                "features": ["1 page", "Contact form"],
            },
            {
                "name": "Business",
                "description": "Up to 5 pages",
                "price": 14999,
                "delivery_days": 7,
                "revisions": -1,
                "features": ["5 pages", "Blog", "Basic SEO"],
            },
        ],
        "faqs": [
            {
                "q": "Do you provide hosting?",
                "a": "I set it up on your account, so you own it.",
            },
        ],
        "tags": ["web-design"],
    },
    timeout=30,
)
print(response.status_code, response.json())
```

**PHP**

```php
<?php
$apiKey = getenv('UND_API_KEY');

$payload = [
    'title' => 'Business website in 7 days',
    'body' => '<p>I build fast, mobile friendly websites for small shops and clinics.</p><ul><li>Design and build</li><li>Hosting setup</li></ul>',
    'category_id' => 12,
    'summary' => 'A fast, mobile friendly website for your shop or clinic.',
    'service_mode' => 'online',
    'packages' => [
        [
            'name' => 'Starter',
            'description' => 'One page site',
            'price' => 4999,
            'delivery_days' => 5,
            'revisions' => 2,
            'features' => ['1 page', 'Contact form'],
        ],
        [
            'name' => 'Business',
            'description' => 'Up to 5 pages',
            'price' => 14999,
            'delivery_days' => 7,
            'revisions' => -1,
            'features' => ['5 pages', 'Blog', 'Basic SEO'],
        ],
    ],
    'faqs' => [
        [
            'q' => 'Do you provide hosting?',
            'a' => 'I set it up on your account, so you own it.',
        ],
    ],
    'tags' => ['web-design'],
];

$ch = curl_init('https://urnextdoor.com/api/v1/services');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
        'Idempotency-Key: ' . bin2hex(random_bytes(16)),
    ],
    CURLOPT_POSTFIELDS => json_encode($payload),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

echo $status, PHP_EOL, $body, PHP_EOL;
```

Sample response: `201 Created`

```json
{
  "ok": true,
  "id": 9,
  "slug": "business-website-in-7-days",
  "url": "/marketplace/business-website-in-7-days",
  "web_url": "https://urnextdoor.com/marketplace/business-website-in-7-days",
  "item": {
    "id": 9,
    "type": "service",
    "slug": "business-website-in-7-days"
  },
  "request_id": "9f8e7d6c5b4a3921"
}
```

An in person service, scheduled for later:

**cURL**

```bash
curl -X POST "https://urnextdoor.com/api/v1/services" \
  -H "Authorization: Bearer $UND_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "title": "Home tutoring for class 10 maths",
  "body": "<p>One to one sessions at your home, weekday evenings.</p>",
  "category_id": 31,
  "service_mode": "in_person",
  "country_code": "IN",
  "postal_code": "411001",
  "state_id": 19,
  "area": "Kothrud",
  "packages": [
    {
      "name": "Monthly",
      "price": 6000,
      "delivery_days": 30,
      "features": [
        "12 sessions"
      ]
    }
  ],
  "scheduled_at": "2026-10-20T09:30:00Z"
}'
```

**JavaScript**

```js
const res = await fetch("https://urnextdoor.com/api/v1/services", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.UND_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    title: "Home tutoring for class 10 maths",
    body: "<p>One to one sessions at your home, weekday evenings.</p>",
    category_id: 31,
    service_mode: "in_person",
    country_code: "IN",
    postal_code: "411001",
    state_id: 19,
    area: "Kothrud",
    packages: [
      {
        name: "Monthly",
        price: 6000,
        delivery_days: 30,
        features: ["12 sessions"],
      },
    ],
    scheduled_at: "2026-10-20T09:30:00Z",
  }),
});
console.log(res.status, await res.json());
```

**Python**

```python
import os
import uuid

import requests

api_key = os.environ["UND_API_KEY"]

response = requests.post(
    "https://urnextdoor.com/api/v1/services",
    headers={
        "Authorization": f"Bearer {api_key}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "title": "Home tutoring for class 10 maths",
        "body": "<p>One to one sessions at your home, weekday evenings.</p>",
        "category_id": 31,
        "service_mode": "in_person",
        "country_code": "IN",
        "postal_code": "411001",
        "state_id": 19,
        "area": "Kothrud",
        "packages": [
            {
                "name": "Monthly",
                "price": 6000,
                "delivery_days": 30,
                "features": ["12 sessions"],
            },
        ],
        "scheduled_at": "2026-10-20T09:30:00Z",
    },
    timeout=30,
)
print(response.status_code, response.json())
```

**PHP**

```php
<?php
$apiKey = getenv('UND_API_KEY');

$payload = [
    'title' => 'Home tutoring for class 10 maths',
    'body' => '<p>One to one sessions at your home, weekday evenings.</p>',
    'category_id' => 31,
    'service_mode' => 'in_person',
    'country_code' => 'IN',
    'postal_code' => '411001',
    'state_id' => 19,
    'area' => 'Kothrud',
    'packages' => [
        [
            'name' => 'Monthly',
            'price' => 6000,
            'delivery_days' => 30,
            'features' => ['12 sessions'],
        ],
    ],
    'scheduled_at' => '2026-10-20T09:30:00Z',
];

$ch = curl_init('https://urnextdoor.com/api/v1/services');
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
        'Idempotency-Key: ' . bin2hex(random_bytes(16)),
    ],
    CURLOPT_POSTFIELDS => json_encode($payload),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

echo $status, PHP_EOL, $body, PHP_EOL;
```

Sample response: `201 Created`

```json
{
  "ok": true,
  "id": 9,
  "slug": "business-website-in-7-days",
  "url": "/marketplace/business-website-in-7-days",
  "web_url": "https://urnextdoor.com/marketplace/business-website-in-7-days",
  "status": "scheduled",
  "scheduled_at": "2026-10-20T09:30:00Z",
  "item": {
    "id": 9,
    "type": "service",
    "slug": "business-website-in-7-days"
  },
  "request_id": "9f8e7d6c5b4a3921"
}
```

## All fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `title` | string | Yes | The service title (plain text). |
| `body` | string | Yes | The full description as HTML. |
| `category_id` | integer | Yes | A category id from GET /service-categories. |
| `summary` | string | No | A short plain text summary shown on cards. Up to 300 characters. |
| `service_mode` | string | No | How you deliver. Unless it is online, a location is required. One of: `online`, `in_person`, `both`. Default: `both`. |
| `country_code` | string | No | ISO 3166-1 alpha-2 country, for example IN. Required unless service_mode is online. |
| `postal_code` | string | No | Postal code, valid for the country. Required unless service_mode is online. |
| `state_id` | integer | No | From GET /geo?level=states. At least a state (or a district or city) is required unless service_mode is online. |
| `district_id` | integer | No | From GET /geo?level=districts. If you send a district, also send a city. |
| `city_id` | integer | No | From GET /geo?level=cities. |
| `area` | string | No | Neighbourhood or area, free text. Up to 120 characters. |
| `packages` | array of object | No | Up to 3 priced packages. Up to 3 items. |
| `packages[].name` | string | No | Package name. Defaults to Basic, Standard or Premium by position. Up to 120 characters. |
| `packages[].description` | string | No | What the package includes. Up to 500 characters. |
| `packages[].price` | number | Yes | Price in Indian rupees, for example 4999. A package without a positive price is skipped. |
| `packages[].delivery_days` | integer | No | Days to deliver. Defaults to 1. |
| `packages[].revisions` | integer | No | Revisions included. -1 means unlimited. |
| `packages[].features` | array of string | No | Short feature lines, up to 20. Up to 20 items. |
| `faqs` | array of object | No | Up to 20 questions and answers. Up to 20 items. |
| `faqs[].q` | string | Yes | The question. Up to 200 characters. |
| `faqs[].a` | string | Yes | The answer. Up to 1000 characters. |
| `tags` | array of string | No | Topic tags, without the leading #. Example: ["water", "home"]. |
| `community_id` | integer | No | Post inside this community. Use an id from GET /communities/postable. You must be an editor (or above) of it. |
| `community` | string | No | Alternative to community_id: the community slug. It is resolved to its id. |
| `featured_image` | string | No | A /storage/... path returned by POST /media (data.url), or any https:// image URL (linked, not copied). |
| `gallery` | array of string | No | Extra images. Each is a /storage/... path from POST /media or an https:// image URL. Up to 12 items. |
| `scheduled_at` | string (date-time) | No | Publish later instead of now. ISO 8601 with an offset or Z, at least 5 minutes and at most 180 days ahead. Stored in UTC. |

## Common errors

| Status and code | Cause | Fix |
| --- | --- | --- |
| `422 validation_failed` on `category_id` | Missing or unknown category | Use an id from the lookup |
| `422 validation_failed` on the location | No country, an invalid postal code, no state, or a district without a city | Follow step 3, or use `service_mode: "online"` |
| `403 forbidden` | You are not an editor of that community | List it without a community |
| `429 quota_exceeded` | Today's service quota is used up | Wait until 00:00 UTC |

## Next steps

- [Upload images](https://docs.urnextdoor.com/guides/upload-images.md) for `featured_image` and `gallery`.
- [Post a job](https://docs.urnextdoor.com/guides/create-job.md).
- [Look up ids](https://docs.urnextdoor.com/guides/lookups.md).
