HTTP

Status Code

201 Created

Resource successfully created. Learn when to use 201 Created, proper response format, and best practices for creation endpoints.

5 min read beginner Try in Playground

TL;DR: 201 Created means a new resource was successfully created. Check the Location header for the new resource URL.

What is 201 Created?

A 201 Created status code indicates that a request has succeeded and a new resource has been created as a result. The server confirms that the resource was successfully created and typically provides information about the newly created resource in the response body.

This status code is specifically for creation operations where something new now exists that didn’t exist before the request.

When to Use 201 Created

Primary Use Cases

Resource Creation via POST:

POST /api/users
Content-Type: application/json

{
  "name": "John Doe",
  "email": "john@example.com"
}
```text

**File Upload:**

```http
POST /api/files
Content-Type: multipart/form-data

[file data]

Database Record Creation:

POST /api/orders
Content-Type: application/json

{
  "product_id": 123,
  "quantity": 2
}
```text

### Common Scenarios

| Scenario           | Example Request            | Expected Response    |
| ------------------ | -------------------------- | -------------------- |
| User Registration  | `POST /users`              | 201 + user object    |
| Blog Post Creation | `POST /posts`              | 201 + post object    |
| Order Placement    | `POST /orders`             | 201 + order details  |
| File Upload        | `POST /files`              | 201 + file metadata  |
| Comment Addition   | `POST /posts/123/comments` | 201 + comment object |

## Proper 201 Response Format

### Essential Headers

**Location Header (Recommended):**

```http
HTTP/1.1 201 Created
Location: /api/users/456
Content-Type: application/json

Complete Example:

HTTP/1.1 201 Created
Location: /api/users/456
Content-Type: application/json
Content-Length: 156

{
  "id": 456,
  "name": "John Doe",
  "email": "john@example.com",
  "created_at": "2026-01-18T12:00:00Z",
  "profile_url": "/users/456"
}
```text

### Response Body Best Practices

**Include Resource Identifier:**

```json
{
  "id": 789,
  "title": "My New Post",
  "slug": "my-new-post",
  "status": "published"
}

Provide Useful Metadata:

{
  "order_id": "ORD-2026-001",
  "status": "pending",
  "total": 99.99,
  "estimated_delivery": "2026-01-25",
  "tracking_url": "/orders/ORD-2026-001/track"
}
```javascript

## 201 vs Other Success Codes

| Code    | Use Case                | Response Body     | Location Header      |
| ------- | ----------------------- | ----------------- | -------------------- |
| **201** | Resource created        | New resource data | Required/Recommended |
| **200** | General success         | Any data          | Not applicable       |
| **202** | Accepted for processing | Status info       | Optional             |
| **204** | Success, no content     | Empty             | Not applicable       |

## Implementation Examples

### Express.js

```javascript
app.post('/api/users', async (req, res) => {
  try {
    const user = await User.create(req.body)

    res.status(201).location(`/api/users/${user.id}`).json({
      id: user.id,
      name: user.name,
      email: user.email,
      created_at: user.created_at
    })
  } catch (error) {
    res.status(400).json({ error: error.message })
  }
})

Python Flask

@app.route('/api/posts', methods=['POST'])
def create_post():
    try:
        data = request.get_json()
        post = Post.create(data)

        response = jsonify({
            'id': post.id,
            'title': post.title,
            'slug': post.slug,
            'created_at': post.created_at.isoformat()
        })

        response.status_code = 201
        response.headers['Location'] = f'/api/posts/{post.id}'
        return response

    except ValidationError as e:
        return jsonify({'error': str(e)}), 400
```javascript

### PHP Laravel

```php
public function store(Request $request)
{
    $validated = $request->validate([
        'name' => 'required|string|max:255',
        'email' => 'required|email|unique:users'
    ]);

    $user = User::create($validated);

    return response()->json([
        'id' => $user->id,
        'name' => $user->name,
        'email' => $user->email,
        'created_at' => $user->created_at
    ], 201)->header('Location', "/api/users/{$user->id}");
}

Common Mistakes to Avoid

❌ Wrong Status Code Usage

Using 201 for Updates:

PUT /api/users/123  ← Updating existing user
HTTP/1.1 201 Created  ← Should be 200 OK
```text

**Using 200 for Creation:**

```http
POST /api/posts  ← Creating new post
HTTP/1.1 200 OK  ← Should be 201 Created

❌ Missing Location Header

HTTP/1.1 201 Created
Content-Type: application/json
← Missing Location header

{"id": 123, "name": "New User"}
```text

### ❌ Inconsistent Response Format

```http
HTTP/1.1 201 Created
Location: /users/123  ← Relative URL
Content-Type: text/plain  ← Wrong content type

User created successfully  ← Not structured data

✅ Correct Implementation

HTTP/1.1 201 Created
Location: https://api.example.com/users/123
Content-Type: application/json

{
  "id": 123,
  "name": "John Doe",
  "email": "john@example.com",
  "created_at": "2026-01-18T12:00:00Z"
}
```javascript

## Advanced Patterns

### Async Resource Creation

```javascript
app.post('/api/reports', async (req, res) => {
  const job = await ReportJob.create({
    user_id: req.user.id,
    parameters: req.body,
    status: 'pending'
  })

  // Start async processing
  processReportAsync(job.id)

  res
    .status(201)
    .location(`/api/jobs/${job.id}`)
    .json({
      job_id: job.id,
      status: 'pending',
      estimated_completion: '2026-01-18T12:05:00Z',
      status_url: `/api/jobs/${job.id}/status`
    })
})

Bulk Creation

app.post('/api/users/batch', async (req, res) => {
  const users = await User.bulkCreate(req.body.users)

  res.status(201).json({
    created_count: users.length,
    users: users.map((user) => ({
      id: user.id,
      name: user.name,
      email: user.email
    })),
    batch_id: generateBatchId()
  })
})
```javascript

### Nested Resource Creation

```javascript
app.post('/api/posts/:postId/comments', async (req, res) => {
  const comment = await Comment.create({
    post_id: req.params.postId,
    author_id: req.user.id,
    content: req.body.content
  })

  res.status(201).location(`/api/comments/${comment.id}`).json({
    id: comment.id,
    content: comment.content,
    author: comment.author.name,
    post_id: comment.post_id,
    created_at: comment.created_at
  })
})

Error Handling

Validation Errors

app.post('/api/users', async (req, res) => {
  const { error, value } = userSchema.validate(req.body)

  if (error) {
    return res.status(400).json({
      error: 'Validation failed',
      details: error.details.map((d) => ({
        field: d.path.join('.'),
        message: d.message
      }))
    })
  }

  try {
    const user = await User.create(value)
    res.status(201).location(`/api/users/${user.id}`).json(user)
  } catch (dbError) {
    if (dbError.code === 'DUPLICATE_ENTRY') {
      return res.status(409).json({
        error: 'User already exists',
        field: 'email'
      })
    }

    res.status(500).json({
      error: 'Internal server error'
    })
  }
})
```javascript

## Testing 201 Responses

### Unit Test Example

```javascript
describe('POST /api/users', () => {
  it('should create user and return 201', async () => {
    const userData = {
      name: 'John Doe',
      email: 'john@example.com'
    }

    const response = await request(app).post('/api/users').send(userData).expect(201)

    expect(response.headers.location).toMatch(/\/api\/users\/\d+/)
    expect(response.body).toHaveProperty('id')
    expect(response.body.name).toBe(userData.name)
    expect(response.body.email).toBe(userData.email)
  })
})

Frequently Asked Questions

1. Should I always include the created resource in the response body?

Yes, it’s considered best practice. Clients often need the server-generated data (like ID, timestamps, computed fields) immediately after creation.

2. Is the Location header required for 201 responses?

While not strictly required by the HTTP specification, it’s strongly recommended. It provides the canonical URL for the newly created resource.

3. Can I use 201 for bulk creation operations?

Yes, but consider the semantics carefully. If you’re creating multiple independent resources, 201 is appropriate. Include information about all created resources in the response.

4. What’s the difference between 201 and 202 for creation?

Use 201 when the resource is immediately created and available. Use 202 when creation is accepted but happens asynchronously (like queued processing).

5. Should I use absolute or relative URLs in the Location header?

Absolute URLs are preferred as they’re unambiguous and work correctly with proxies, load balancers, and different client configurations.

6. How do I handle partial creation failures in bulk operations?

Return 207 Multi-Status with individual status codes for each item, or use 201 with a detailed response body indicating which items succeeded/failed.

Frequently Asked Questions

What does 201 Created mean?

A 201 response means the request succeeded and a new resource was created. The server should return the created resource in the body and its URL in the Location header.

What is the difference between 200 and 201?

200 means the request succeeded. 201 specifically means a new resource was created. Use 201 for POST requests that create something new, 200 for requests that do not create resources.

Should 201 include a response body?

Yes, typically return the created resource so clients do not need a follow-up GET request. Include the Location header pointing to the new resource URL.

When should I use 201 vs 204?

Use 201 when creating a resource and returning it. Use 204 when the operation succeeds but there is nothing to return, like a DELETE request.

Keep Learning