> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://americancloud.com/docs/api/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://americancloud.com/docs/api/_mcp/server.

# Create a port forwarding rule for a public IP address

POST https://api.americancloud.com/api/v1/networks/firewall/port-forwarding/ip/{ipId}
Content-Type: application/json

Reference: https://americancloud.com/docs/api/api-reference/port-forwarding/create-port-forwarding

## Authentication

- `X-API-Client-ID` header (required) — API Client ID
- `X-API-Client-Secret` header (required) — API Client Secret

## Request

### Path parameters

- `ipId` (string, required) — ID of the public IP address

### Body (application/json)

This endpoint expects a CreatePortForwardingRuleDto.

- `privatePort` (string, required) — Port on the VM that traffic is forwarded to. Must be 1-65535.
- `publicPort` (string, required) — Port on the public IP that receives the inbound traffic. Must be 1-65535.
- `protocol` (enum, required) — Protocol the forwarding rule applies to.
  - Allowed values: `TCP`, `UDP`
- `vmId` (string, required) — Identifier of the VM the forwarded traffic is delivered to.
- `openFirewall` (string, optional) — When `true`, automatically creates a matching firewall rule alongside the port forwarding rule.
- `tierId` (string, optional) — For a public IP reserved in a VPC, the VPC tier the rule applies to. A VPC IP is not bound to any single tier, so the rule must target one. You only need to set this when the VM has interfaces in more than one tier of the VPC — otherwise the tier is determined automatically from the VM. Ignored for IPs reserved in an isolated network.

## Response

### 201

The port forwarding rule has been created

- `id` (string, required) — Unique identifier for the port forwarding rule.
- `privatePort` (integer, required) — Port on the VM that traffic is forwarded to.
- `publicPort` (integer, required) — Port on the public IP that receives the inbound traffic.
- `protocol` (string, required) — Protocol the forwarding rule applies to. `TCP` or `UDP`.
- `vmId` (string, required) — Identifier of the VM the forwarded traffic is delivered to.
- `vmName` (string, required) — Name of the VM the forwarded traffic is delivered to.
- `ipAddressId` (string, required) — Identifier of the public IP the rule is attached to.
- `ipAddress` (string, required) — Public IP address the rule is attached to.
- `state` (string, required) — Current lifecycle state of the rule.
- `createdAt` (datetime, optional) — When the rule was created.

## Errors

### 400 Bad Request Error

The request was malformed or failed validation.

- `statusCode` (double, required) — HTTP status code.
- `message` (ApiErrorDtoMessage, required) — Human-readable error message. A single string for most errors, or an array of strings when the request failed multiple validation checks.
- `error` (string, optional) — Short HTTP error label.
- `code` (string, optional) — Machine-readable reason for the error. Match on this value rather than on `message`, which can be reworded at any time. Present on the errors that have more than one cause for the same status code; omitted otherwise.
- `errors` (list of ValidationErrorItemDto, optional) — Per-field validation details. Present on 400 responses produced by request-body validation; omitted otherwise.
- `snapshots` (list of BlockingSnapshotDto, optional) — The snapshots that block a delete. Present on a 409 whose `code` is `volume_has_snapshots`; omitted otherwise.

### 401 Unauthorized Error

Authentication credentials were missing or invalid.

- `statusCode` (double, required) — HTTP status code.
- `message` (ApiErrorDtoMessage, required) — Human-readable error message. A single string for most errors, or an array of strings when the request failed multiple validation checks.
- `error` (string, optional) — Short HTTP error label.
- `code` (string, optional) — Machine-readable reason for the error. Match on this value rather than on `message`, which can be reworded at any time. Present on the errors that have more than one cause for the same status code; omitted otherwise.
- `errors` (list of ValidationErrorItemDto, optional) — Per-field validation details. Present on 400 responses produced by request-body validation; omitted otherwise.
- `snapshots` (list of BlockingSnapshotDto, optional) — The snapshots that block a delete. Present on a 409 whose `code` is `volume_has_snapshots`; omitted otherwise.

### 403 Forbidden Error

The authenticated principal is not permitted to perform this action.

- `statusCode` (double, required) — HTTP status code.
- `message` (ApiErrorDtoMessage, required) — Human-readable error message. A single string for most errors, or an array of strings when the request failed multiple validation checks.
- `error` (string, optional) — Short HTTP error label.
- `code` (string, optional) — Machine-readable reason for the error. Match on this value rather than on `message`, which can be reworded at any time. Present on the errors that have more than one cause for the same status code; omitted otherwise.
- `errors` (list of ValidationErrorItemDto, optional) — Per-field validation details. Present on 400 responses produced by request-body validation; omitted otherwise.
- `snapshots` (list of BlockingSnapshotDto, optional) — The snapshots that block a delete. Present on a 409 whose `code` is `volume_has_snapshots`; omitted otherwise.

### 404 Not Found Error

Public IP address not found

- `statusCode` (double, required) — HTTP status code.
- `message` (ApiErrorDtoMessage, required) — Human-readable error message. A single string for most errors, or an array of strings when the request failed multiple validation checks.
- `error` (string, optional) — Short HTTP error label.
- `code` (string, optional) — Machine-readable reason for the error. Match on this value rather than on `message`, which can be reworded at any time. Present on the errors that have more than one cause for the same status code; omitted otherwise.
- `errors` (list of ValidationErrorItemDto, optional) — Per-field validation details. Present on 400 responses produced by request-body validation; omitted otherwise.
- `snapshots` (list of BlockingSnapshotDto, optional) — The snapshots that block a delete. Present on a 409 whose `code` is `volume_has_snapshots`; omitted otherwise.

### 500 Internal Server Error

An unexpected error occurred on the server.

- `statusCode` (double, required) — HTTP status code.
- `message` (ApiErrorDtoMessage, required) — Human-readable error message. A single string for most errors, or an array of strings when the request failed multiple validation checks.
- `error` (string, optional) — Short HTTP error label.
- `code` (string, optional) — Machine-readable reason for the error. Match on this value rather than on `message`, which can be reworded at any time. Present on the errors that have more than one cause for the same status code; omitted otherwise.
- `errors` (list of ValidationErrorItemDto, optional) — Per-field validation details. Present on 400 responses produced by request-body validation; omitted otherwise.
- `snapshots` (list of BlockingSnapshotDto, optional) — The snapshots that block a delete. Present on a 409 whose `code` is `volume_has_snapshots`; omitted otherwise.

## Types

### ApiErrorDtoMessage

Human-readable error message. A single string for most errors, or an array of strings when the request failed multiple validation checks.

### ValidationErrorItemDto

- `field` (string, required) — Dotted path to the field that failed validation.
- `message` (string, required) — Human-readable validation message for this field.

### BlockingSnapshotDto

- `id` (string, required) — Snapshot ID.
- `name` (string, required) — Snapshot name.

## Examples

**Request**

```json
{
  "privatePort": "80",
  "publicPort": "8080",
  "protocol": "TCP",
  "vmId": "123e4567-e89b-12d3-a456-426614174000"
}
```

**Response**

```json
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "privatePort": 80,
  "publicPort": 8080,
  "protocol": "TCP",
  "vmId": "123e4567-e89b-12d3-a456-426614174000",
  "vmName": "my-vm",
  "ipAddressId": "123e4567-e89b-12d3-a456-426614174000",
  "ipAddress": "203.0.113.5",
  "state": "Active",
  "createdAt": "2026-05-29T12:34:56Z"
}
```

**SDK Code**

```python
import requests

url = "https://api.americancloud.com/api/v1/networks/firewall/port-forwarding/ip/123e4567-e89b-12d3-a456-426614174000"

payload = {
    "privatePort": "80",
    "publicPort": "8080",
    "protocol": "TCP",
    "vmId": "123e4567-e89b-12d3-a456-426614174000"
}
headers = {
    "X-API-Client-ID": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.americancloud.com/api/v1/networks/firewall/port-forwarding/ip/123e4567-e89b-12d3-a456-426614174000';
const options = {
  method: 'POST',
  headers: {'X-API-Client-ID': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"privatePort":"80","publicPort":"8080","protocol":"TCP","vmId":"123e4567-e89b-12d3-a456-426614174000"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.americancloud.com/api/v1/networks/firewall/port-forwarding/ip/123e4567-e89b-12d3-a456-426614174000"

	payload := strings.NewReader("{\n  \"privatePort\": \"80\",\n  \"publicPort\": \"8080\",\n  \"protocol\": \"TCP\",\n  \"vmId\": \"123e4567-e89b-12d3-a456-426614174000\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("X-API-Client-ID", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.americancloud.com/api/v1/networks/firewall/port-forwarding/ip/123e4567-e89b-12d3-a456-426614174000")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["X-API-Client-ID"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"privatePort\": \"80\",\n  \"publicPort\": \"8080\",\n  \"protocol\": \"TCP\",\n  \"vmId\": \"123e4567-e89b-12d3-a456-426614174000\"\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.americancloud.com/api/v1/networks/firewall/port-forwarding/ip/123e4567-e89b-12d3-a456-426614174000")
  .header("X-API-Client-ID", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"privatePort\": \"80\",\n  \"publicPort\": \"8080\",\n  \"protocol\": \"TCP\",\n  \"vmId\": \"123e4567-e89b-12d3-a456-426614174000\"\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.americancloud.com/api/v1/networks/firewall/port-forwarding/ip/123e4567-e89b-12d3-a456-426614174000', [
  'body' => '{
  "privatePort": "80",
  "publicPort": "8080",
  "protocol": "TCP",
  "vmId": "123e4567-e89b-12d3-a456-426614174000"
}',
  'headers' => [
    'Content-Type' => 'application/json',
    'X-API-Client-ID' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.americancloud.com/api/v1/networks/firewall/port-forwarding/ip/123e4567-e89b-12d3-a456-426614174000");
var request = new RestRequest(Method.POST);
request.AddHeader("X-API-Client-ID", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"privatePort\": \"80\",\n  \"publicPort\": \"8080\",\n  \"protocol\": \"TCP\",\n  \"vmId\": \"123e4567-e89b-12d3-a456-426614174000\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "X-API-Client-ID": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = [
  "privatePort": "80",
  "publicPort": "8080",
  "protocol": "TCP",
  "vmId": "123e4567-e89b-12d3-a456-426614174000"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.americancloud.com/api/v1/networks/firewall/port-forwarding/ip/123e4567-e89b-12d3-a456-426614174000")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```