# Port-ins

Bring a number from another carrier: discover the fields each country needs, validate, submit and track the port.

> Partner API · https://developer.telgea.com/docs/partner/api/port-ins

## The port-in object {#object}

Port phone numbers from other providers into Telgea.

| Field | Type | Description |
| --- | --- | --- |
| `id` | string (required) | UUID of the port-in request. Example: `"c3d4e5f6-a7b8-9012-c345-678901234567"` |
| `countryCode` | string (required) | Market the number is being ported into. Example: `"DE"` |
| `msisdn` | string (required) | The number being ported, in E.164 format. Example: `"+4915112345678"` |
| `executionDate` | string (required) | The porting date, as YYYY-MM-DD. Example: `"2027-03-01"` |
| `type` | string (required) | Port-in type this request was submitted under, or null when the donor needed none. Example: `"personal"` |
| `status` | enum (required) | Current status of the port-in One of: `PENDING`, `CREATED`, `SUBMITTED`, `ACCEPTED`, `REJECTED`, `SUCCEEDED`, `FAILED`. Example: `"CREATED"` |
| `numberId` | string (required) | UUID of the number this port-in creates. Use it to read the line once porting succeeds. Example: `"8238c1b9-c2c0-4455-9b03-4bd787052709"` |
| `failure` | PortInFailure (required) | Why a REJECTED or FAILED port-in ended that way: values you can branch on, and a text you can show your customer. Null in every other status. |
| `failure.reason` | enum (required) | Why the port-in ended this way, or null when we could not classify it. One of: `WRONG_CUSTOMER_TYPE`, `CONTRACT_LOCKED`, `NUMBER_OWNERSHIP_MISMATCH`, `NUMBER_ALREADY_PORTING`, `AUTHORIZATION_CODE_INVALID`, `PORTING_DATE_UNAVAILABLE`, `FIELD_MISSING`, `FIELD_INVALID`, `FIELD_MISMATCH`, `REJECTED_BY_DONOR`, `CANCELLED`. Example: `"AUTHORIZATION_CODE_INVALID"` |
| `failure.field` | string (required) | What the donor objected to, when it identified something. Either a key you sent in `fields` (e.g. `authorizationCode`) or a top-level property of the port-in (`msisdn`, `type`, `executionDate`). Example: `"authorizationCode"` |
| `failure.text` | string (required) | What happened and what to do next, in English, for showing to your customer. The wording may change at any time: branch on `reason`, never on this. Example: `"Your authorization code was refused or has expired."` |
| `createdAt` | string (required) | When the port-in was submitted, ISO 8601. Example: `"2027-02-22T10:00:00Z"` |
| `updatedAt` | string (required) | When the port-in last changed status, ISO 8601. Example: `"2027-02-22T10:00:00Z"` |

## Operations

- `GET` [Get port-in fields for a country](#get-port-in-fields-for-a-country)
- `GET` [Get port-in fields JSON Schema](#get-port-in-fields-json-schema)
- `POST` [Validate port-in data](#validate-port-in-data)
- `POST` [Submit a port-in request](#submit-a-port-in-request)
- `GET` [Get port-in status](#get-port-in-status)
- `POST` [Discard a failed port-in](#discard-a-failed-port-in)
- `GET` [List port-ins for a number](#list-port-ins-for-a-number)

### Get port-in fields for a country {#get-port-in-fields-for-a-country}

`GET /v1/port-ins/fields/{countryCode}` · Enterprise API + Partner API

Retrieves the dynamic form fields required to submit a number port-in for a given country. Different countries and providers require different information (e.g., authorization codes, account numbers, personal details).

The response is keyed by port-in type (e.g., "personal", "business"). Each type contains a human-readable label and an array of input field definitions with validation rules.

Each type also carries the market's transfer date as `executionDate` and how a port-in into the market runs as `timing`. Both hold for the whole market, and repeat in every type because the response is keyed by type.

Use this endpoint to build dynamic forms that collect the right data before submitting a port-in.

Requires the permission `port-ins:read` on the key.

[Try it in the playground](https://developer.telgea.com/playground#tag/port-ins/GET/v1/port-ins/fields/{countryCode}). Calls run against your live organization; there is no sandbox.

**Parameters**

| Parameter | In | Type | Description |
| --- | --- | --- | --- |
| `countryCode` | path | string (required) | ISO 3166-1 alpha-2 country code (e.g., "DK", "US", "GB") Example: `DE` |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` | Successfully retrieved port-in field definitions for the specified country. The response is keyed by port-in type (e.g., `personal`, `business`). Each type contains a human-readable label and an array of field definitions describing the inputs required for that port-in type. Field types are `text`, `select`, `date`, `number` and `checkbox`. Each carries only the constraints that apply to it: `minLength`, `maxLength` and `regex` on text; `options` on select; `minDate`, `maxDate`, `dates` and `validUntil` on date; `min`, `max` and `decimalPlaces` on number; `requiredValue` on checkbox. Any field can carry `help`, `howToGet` and an `example` of its format. `executionDate` is a date field whose `dates` are every date a port-in can happen on, as of today in the market's time zone. They hold until `validUntil`, the market's next midnight; fetch the fields again after it. | `GetPortInFieldsResponse` |
| `400` | The countryCode is not a valid ISO 3166-1 alpha-2 code. Valid codes look like DK, US, GB or FR. | none |
| `401` | Missing or invalid API key in the X-API-Key header | `UnauthorizedError` |
| `403` | Insufficient permissions to read port-in data | `ForbiddenError` |
| `429` | The API key has used its request quota for the current window. `Retry-After` says how many seconds until it resets. | `TooManyRequestsError` |
| `500` | Internal server error. Contact support if this persists. | `InternalError` |

**Example request**

```bash
curl -X GET "https://api.telgea.com/v1/port-ins/fields/DE" \
  -H "x-api-key: $TELGEA_API_KEY"
```

**Response 200**

```json
{
  "personal": {
    "label": "Personal",
    "typeLabel": {
      "id": "port-in.de.typeLabel",
      "en": "Who is the number registered to?"
    },
    "explanation": {
      "id": "port-in.de.personal.explanation",
      "en": "The contract is in a person's name."
    },
    "fields": [
      {
        "name": "currentCarrier",
        "label": "Current carrier",
        "description": "Your current mobile carrier",
        "type": "select",
        "help": {
          "id": "port-in.de.currentCarrier.help",
          "en": "The brand on your bill or SIM."
        },
        "options": [
          {
            "label": "Congstar",
            "value": "CONG"
          },
          {
            "label": "O2 Deutschland GmbH",
            "value": "O2DE"
          },
          {
            "label": "Vodafone GmbH",
            "value": "VODA"
          }
        ]
      },
      {
        "name": "authorizationCode",
        "label": "Date of birth",
        "description": "Owner date of birth (YYYY-MM-DD)",
        "type": "date",
        "help": {
          "id": "port-in.de.personal.authorizationCode.help",
          "en": "Date of birth of the contract holder."
        }
      },
      {
        "name": "ownerFirstName",
        "label": "First name",
        "description": "Account owner first name",
        "type": "text",
        "minLength": 1,
        "help": {
          "id": "port-in.de.asOnTheContract",
          "en": "As on the contract."
        }
      },
      {
        "name": "ownerLastName",
        "label": "Last name",
        "description": "Account owner last name",
        "type": "text",
        "minLength": 1,
        "help": {
          "id": "port-in.de.asOnTheContract",
          "en": "As on the contract."
        }
      },
      {
        "name": "isUnlocked",
        "label": "Number released for porting",
        "description": "Please confirm that the current carrier has been contacted and unlocked the number for port-out",
        "type": "checkbox",
        "help": {
          "id": "port-in.de.isUnlocked.help",
          "en": "Ask your current carrier to release the number before you start."
        },
        "requiredValue": true
      }
    ],
    "executionDate": {
      "name": "executionDate",
      "label": "Transfer date",
      "type": "date",
      "help": {
        "id": "port-in.executionDate.help.a-weekday-from-7-days-to-14-days-from-today",
        "en": "A weekday, from 7 days to 14 days from today."
      },
      "errorMessage": "That date isn't possible. Choose a weekday from 7 days to 14 days from today.",
      "minDate": "2027-03-01",
      "maxDate": "2027-03-08",
      "dates": [
        "2027-03-01",
        "2027-03-02",
        "2027-03-03",
        "2027-03-04",
        "2027-03-05",
        "2027-03-08"
      ],
      "validUntil": "2027-02-22T23:00:00.000Z"
    },
    "timing": {
      "submitted": "beforeTheDate",
      "finalDateSetBy": "oldCarrier",
      "dateMayMove": true,
      "cancel": "beforeSubmission",
      "reschedule": "beforeSubmission"
    }
  },
  "business": {
    "label": "Business",
    "typeLabel": {
      "id": "port-in.de.typeLabel",
      "en": "Who is the number registered to?"
    },
    "explanation": {
      "id": "port-in.de.business.explanation",
      "en": "The contract is in a company's name."
    },
    "fields": [
      {
        "name": "currentCarrier",
        "label": "Current carrier",
        "description": "Your current mobile carrier",
        "type": "select",
        "help": {
          "id": "port-in.de.currentCarrier.help",
          "en": "The brand on your bill or SIM."
        },
        "options": [
          {
            "label": "Congstar",
            "value": "CONG"
          },
          {
            "label": "O2 Deutschland GmbH",
            "value": "O2DE"
          },
          {
            "label": "Vodafone GmbH",
            "value": "VODA"
          }
        ]
      },
      {
        "name": "authorizationCode",
        "label": "Customer number",
        "description": "Your customer number with the current carrier. Can be found on a recent bill from them or by contacting the carrier directly.",
        "type": "text",
        "minLength": 1,
        "help": {
          "id": "port-in.de.business.authorizationCode.help",
          "en": "On the current carrier's invoice."
        },
        "howToGet": {
          "id": "port-in.de.business.authorizationCode.howToGet",
          "en": "On the current carrier's invoice."
        }
      },
      {
        "name": "companyName",
        "label": "Company name",
        "description": "Registered company name of company that owns the number",
        "type": "text",
        "minLength": 1,
        "help": {
          "id": "port-in.de.asOnTheContract",
          "en": "As on the contract."
        }
      },
      {
        "name": "isUnlocked",
        "label": "Number released for porting",
        "description": "Please confirm that the current carrier has been contacted and unlocked the number for port-out",
        "type": "checkbox",
        "help": {
          "id": "port-in.de.isUnlocked.help",
          "en": "Ask your current carrier to release the number before you start."
        },
        "requiredValue": true
      }
    ],
    "executionDate": {
      "name": "executionDate",
      "label": "Transfer date",
      "type": "date",
      "help": {
        "id": "port-in.executionDate.help.a-weekday-from-7-days-to-14-days-from-today",
        "en": "A weekday, from 7 days to 14 days from today."
      },
      "errorMessage": "That date isn't possible. Choose a weekday from 7 days to 14 days from today.",
      "minDate": "2027-03-01",
      "maxDate": "2027-03-08",
      "dates": [
        "2027-03-01",
        "2027-03-02",
        "2027-03-03",
        "2027-03-04",
        "2027-03-05",
        "2027-03-08"
      ],
      "validUntil": "2027-02-22T23:00:00.000Z"
    },
    "timing": {
      "submitted": "beforeTheDate",
      "finalDateSetBy": "oldCarrier",
      "dateMayMove": true,
      "cancel": "beforeSubmission",
      "reschedule": "beforeSubmission"
    }
  }
}
```

### Get port-in fields JSON Schema {#get-port-in-fields-json-schema}

`GET /v1/port-ins/fields/{countryCode}/schema` · Enterprise API + Partner API

Retrieves JSON Schema definitions for port-in fields, keyed by port-in type. Use these schemas for client-side validation of port-in form data before submission.

Each key in the response corresponds to a port-in type (e.g., "personal", "business"), and the value is a standard JSON Schema object that can be used with any JSON Schema validator.

A type whose market declares transfer dates also carries `$defs.executionDate`: check the port-in's `executionDate` against it. Its dates hold until the `executionDate.validUntil` the fields endpoint returns; fetch the schema again after it. Nothing in the schema's `properties` refers to it, so the schema validates `fields` alone.

Requires the permission `port-ins:read` on the key.

[Try it in the playground](https://developer.telgea.com/playground#tag/port-ins/GET/v1/port-ins/fields/{countryCode}/schema). Calls run against your live organization; there is no sandbox.

**Parameters**

| Parameter | In | Type | Description |
| --- | --- | --- | --- |
| `countryCode` | path | string (required) | ISO 3166-1 alpha-2 country code (e.g., "DK", "US", "GB") Example: `DE` |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` | Successfully retrieved JSON Schema definitions for port-in fields. | `GetPortInFieldsSchemaResponse` |
| `400` | The countryCode is not a valid ISO 3166-1 alpha-2 code. Valid codes look like DK, US, GB or FR. | none |
| `401` | Missing or invalid API key in the X-API-Key header | `UnauthorizedError` |
| `403` | Insufficient permissions to read port-in data | `ForbiddenError` |
| `429` | The API key has used its request quota for the current window. `Retry-After` says how many seconds until it resets. | `TooManyRequestsError` |
| `500` | Internal server error. Contact support if this persists. | `InternalError` |

**Example request**

```bash
curl -X GET "https://api.telgea.com/v1/port-ins/fields/DE/schema" \
  -H "x-api-key: $TELGEA_API_KEY"
```

**Response 200**

```json
{
  "personal": {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "properties": {
      "currentCarrier": {
        "type": "string",
        "enum": [
          "CONG",
          "O2DE",
          "VODA"
        ],
        "errorMessage": "Value must be one of the allowed options"
      },
      "authorizationCode": {
        "type": "string",
        "format": "date",
        "errorMessage": "Value must be a valid date"
      },
      "ownerFirstName": {
        "type": "string",
        "minLength": 1
      },
      "ownerLastName": {
        "type": "string",
        "minLength": 1
      },
      "isUnlocked": {
        "type": "boolean",
        "const": true,
        "errorMessage": "Value must be true"
      }
    },
    "required": [
      "currentCarrier",
      "authorizationCode",
      "ownerFirstName",
      "ownerLastName",
      "isUnlocked"
    ],
    "additionalProperties": false,
    "$defs": {
      "executionDate": {
        "type": "string",
        "enum": [
          "2027-03-01",
          "2027-03-02",
          "2027-03-03",
          "2027-03-04",
          "2027-03-05",
          "2027-03-08"
        ],
        "format": "date",
        "errorMessage": "That date isn't possible. Choose a weekday from 7 days to 14 days from today."
      }
    }
  }
}
```

### Validate port-in data {#validate-port-in-data}

`POST /v1/port-ins/validate` · Enterprise API + Partner API

Validates port-in data against provider requirements without creating a port-in. Use this endpoint to check whether the submitted fields, phone number, and execution date would pass provider validation before committing to a port-in submission.

The `executionDate` must be one of the type's `executionDate.dates` from `GET /v1/port-ins/fields/{countryCode}`, which hold until its `validUntil`. The date is checked once everything else passes: a date outside them is then refused with 400 BAD_REQUEST, not `valid: false`.

This is a dry-run operation — no resources are created or modified.

Requires the permission `port-ins:validate` on the key.

[Try it in the playground](https://developer.telgea.com/playground#tag/port-ins/POST/v1/port-ins/validate). Calls run against your live organization; there is no sandbox.

**Parameters**

| Parameter | In | Type | Description |
| --- | --- | --- | --- |
| `idempotency-key` | header | string | A value unique to this request, of 1 to 255 printable ASCII characters — a UUID is the usual choice. Retry with the same key within 24 hours and you get the original response back, even if it was a failure, and nothing is done twice. Send a new key whenever you change the request. Keys are private to your API key and organisation. Example: `0b7e4f1c-6a2d-4c9e-8f35-d1a7b2c93e60` |

**Request body**

| Field | Type | Description |
| --- | --- | --- |
| `countryCode` | string (required) | ISO 3166-1 alpha-2 country code Example: `"DE"` |
| `type` | string (required) | Port-in type as returned by the fields endpoint (e.g., "personal", "business") Example: `"personal"` |
| `msisdn` | string (required) | Phone number to port in, in E.164 format Example: `"+4915112345678"` |
| `executionDate` | string (date) (required) | Requested porting date in YYYY-MM-DD format. Must be one of the type's `executionDate.dates` from `GET /v1/port-ins/fields/{countryCode}`, which hold until its `validUntil`. Example: `"2027-03-01"` |
| `fields` | map of unknown (required) | Port-in fields as defined by the fields endpoint. Must include authorizationCode. Example: `{"currentCarrier":"CONG","authorizationCode":"1985-04-12","ownerFirstName":"Erika","ownerLastName":"Mustermann","isUnlocked":true}` |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` | Validation completed. Check the `valid` field to determine if the data passed. If `valid` is false, the `errors` array contains details: each error names its `field` and carries a stable `code` to branch on. | `ValidatePortInResponse` |
| `400` | Request body failed schema validation, or everything else passed and the `executionDate` is not one the market offers | none |
| `401` | Missing or invalid API key in the X-API-Key header | `UnauthorizedError` |
| `403` | Insufficient permissions to validate port-ins | `ForbiddenError` |
| `409` | The number cannot be ported in: Telgea already holds it, or a port-in for it is already in flight. Creating the port-in would be refused the same way. | `ConflictError` |
| `429` | The API key has used its request quota for the current window. `Retry-After` says how many seconds until it resets. | `TooManyRequestsError` |
| `500` | Internal server error. Contact support if this persists. | `InternalError` |

**Response fields (200, ValidatePortInResponse)**

| Field | Type | Description |
| --- | --- | --- |
| `valid` | boolean (required) | Whether the port-in data passed validation Example: `true` |
| `errors` | array of PortInValidationError (required) | List of validation errors, empty if valid |
| `errors.message` | string (required) | Validation error description Example: `"A PAC is 3 letters, then 6 digits (e.g. ABC123456). 6 digits then 3 letters is a STAC, not a PAC."` |
| `errors.field` | string | The field that caused the validation error Example: `"authorizationCode"` |
| `errors.code` | enum (required) | Why the value was refused, as a stable code to branch on. A code is never renamed or removed, but new ones can be added: show the `message` for a code you do not recognise. One of: `REQUIRED`, `FORMAT_INVALID`, `LENGTH_INVALID`, `NOT_ALLOWED`, `DATE_NOT_ALLOWED`, `CHECK_DIGIT_INVALID`, `WRONG_CUSTOMER_TYPE`. Example: `"FORMAT_INVALID"` |

**Example request**

```bash
curl -X POST "https://api.telgea.com/v1/port-ins/validate" \
  -H "x-api-key: $TELGEA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "countryCode": "DE",
  "type": "personal",
  "msisdn": "+4915112345678",
  "executionDate": "2027-03-01",
  "fields": {
    "currentCarrier": "CONG",
    "authorizationCode": "1985-04-12",
    "ownerFirstName": "Erika",
    "ownerLastName": "Mustermann",
    "isUnlocked": true
  }
}'
```

**Response 200**

```json
{
  "valid": true,
  "errors": []
}
```

### Submit a port-in request {#submit-a-port-in-request}

`POST /v1/port-ins` · Enterprise API + Partner API

Submits a number porting request to bring a phone number from another carrier into Telgea. The user must have an existing plan in INACTIVE or PORTING status.

**Important constraints:**
- The `executionDate` must be one of the type's `executionDate.dates` from `GET /v1/port-ins/fields/{countryCode}`, which hold until its `validUntil`. The date is checked once everything else passes: a date outside them is then refused with 400 BAD_REQUEST, and no port-in is created.
- Only **one port-in can be in progress** per phone number at a time. Submitting a port-in for an MSISDN that already has an active (non-completed, non-failed, non-rejected) port-in will be rejected.
- A number **already with Telgea** cannot be ported in: one on a Telgea line, or one whose plan ended within the last 14 days and can still be restored. It is refused with 409 CONFLICT.

**Recommended flow:**
1. Call `GET /v1/port-ins/fields/{countryCode}` to discover required fields for the country
2. Call `POST /v1/port-ins/validate` to verify the data before submission
3. Call this endpoint to submit the port-in

If provider-side validation fails, the response will have `status: "REJECTED"` with an `errors` array describing the issues. A successful submission returns `status: "CREATED"`.

The port-in will progress through states: CREATED → SUBMITTED → ACCEPTED → SUCCEEDED. Check the status with `GET /v1/port-ins/{portInId}`.

**Retrying a port-in that ended in REJECTED or FAILED:** read `failure.reason` and `failure.field` on the port-in, correct what they point at (`failure.text` explains it in words you can show your customer), then call this endpoint again with the same `msisdn`. The failed attempt is kept as history and stays readable at its own id — `GET /v1/port-ins/number/{numberId}` lists every attempt for the number, newest first. A retry sent while a port-in for that number is still in flight is refused with 409 CONFLICT — a number can only be ported once at a time anywhere. Wait for it to finish, then read `failure.reason` and submit again.

Requires the permission `port-ins:create` on the key.

[Try it in the playground](https://developer.telgea.com/playground#tag/port-ins/POST/v1/port-ins). Calls run against your live organization; there is no sandbox.

**Parameters**

| Parameter | In | Type | Description |
| --- | --- | --- | --- |
| `idempotency-key` | header | string | A value unique to this request, of 1 to 255 printable ASCII characters — a UUID is the usual choice. Retry with the same key within 24 hours and you get the original response back, even if it was a failure, and nothing is done twice. Send a new key whenever you change the request. Keys are private to your API key and organisation. Example: `0b7e4f1c-6a2d-4c9e-8f35-d1a7b2c93e60` |

**Request body**

| Field | Type | Description |
| --- | --- | --- |
| `userId` | string (uuid) (required) | UUID of the user to port a number for. The user must have an INACTIVE or PORTING plan. Example: `"9c1f2a6e-3b7d-4e0a-8f21-5d6c7b8a9e01"` |
| `countryCode` | string (required) | ISO 3166-1 alpha-2 country code Example: `"DE"` |
| `msisdn` | string (required) | Phone number to port in, in E.164 format Example: `"+4915112345678"` |
| `executionDate` | string (date) (required) | Requested porting date in YYYY-MM-DD format. Must be one of the type's `executionDate.dates` from `GET /v1/port-ins/fields/{countryCode}`, which hold until its `validUntil`. Example: `"2027-03-01"` |
| `type` | string (required) | Port-in type as returned by the fields endpoint (e.g., "personal", "business") Example: `"personal"` |
| `fields` | map of unknown (required) | Port-in fields as defined by the fields endpoint. Must include authorizationCode. Example: `{"currentCarrier":"CONG","authorizationCode":"1985-04-12","ownerFirstName":"Erika","ownerLastName":"Mustermann","isUnlocked":true}` |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` | Port-in request processed. Check the `status` field: "CREATED" means the port-in was accepted for processing; "REJECTED" means validation failed — see the `errors` array. | `CreatePortInResponse` |
| `400` | Request body failed schema validation, or business rule violation (e.g., user plan not in correct state, or, once everything else passes, an `executionDate` the market does not offer) | none |
| `401` | Missing or invalid API key in the X-API-Key header | `UnauthorizedError` |
| `403` | Insufficient permissions to create port-ins, or the country is not available for your organization: it is not enabled, or it requires business verification your organization has not passed. | `ForbiddenError` |
| `404` | No user with the given `userId` exists in your organization, so no port-in was created. | `NotFoundError` |
| `409` | No port-in was created: Telgea already holds this number, or a port-in for it is already in flight. | `ConflictError` |
| `429` | The API key has used its request quota for the current window. `Retry-After` says how many seconds until it resets. | `TooManyRequestsError` |
| `500` | Internal server error. Contact support if this persists. | `InternalError` |

**Response fields (200, CreatePortInResponse)**

| Field | Type | Description |
| --- | --- | --- |
| `id` | string (required) | UUID of the created port-in Example: `"c3d4e5f6-a7b8-9012-c345-678901234567"` |
| `status` | enum (required) | Status of the port-in. CREATED means accepted for processing; REJECTED means validation failed. One of: `CREATED`, `REJECTED`. Example: `"CREATED"` |
| `msisdn` | string | The phone number being ported Example: `"+4915112345678"` |
| `numberId` | string | UUID of the associated number resource Example: `"8238c1b9-c2c0-4455-9b03-4bd787052709"` |
| `executionDate` | string | Confirmed execution date Example: `"2027-03-01"` |
| `errors` | array of PortInValidationError | Validation errors when status is REJECTED |
| `errors.message` | string (required) | Validation error description Example: `"A PAC is 3 letters, then 6 digits (e.g. ABC123456). 6 digits then 3 letters is a STAC, not a PAC."` |
| `errors.field` | string | The field that caused the validation error Example: `"authorizationCode"` |
| `errors.code` | enum (required) | Why the value was refused, as a stable code to branch on. A code is never renamed or removed, but new ones can be added: show the `message` for a code you do not recognise. One of: `REQUIRED`, `FORMAT_INVALID`, `LENGTH_INVALID`, `NOT_ALLOWED`, `DATE_NOT_ALLOWED`, `CHECK_DIGIT_INVALID`, `WRONG_CUSTOMER_TYPE`. Example: `"FORMAT_INVALID"` |

**Example request**

```bash
curl -X POST "https://api.telgea.com/v1/port-ins" \
  -H "x-api-key: $TELGEA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "userId": "9c1f2a6e-3b7d-4e0a-8f21-5d6c7b8a9e01",
  "countryCode": "DE",
  "type": "personal",
  "msisdn": "+4915112345678",
  "executionDate": "2027-03-01",
  "fields": {
    "currentCarrier": "CONG",
    "authorizationCode": "1985-04-12",
    "ownerFirstName": "Erika",
    "ownerLastName": "Mustermann",
    "isUnlocked": true
  }
}'
```

**Response 200**

```json
{
  "id": "c3d4e5f6-a7b8-9012-c345-678901234567",
  "status": "CREATED",
  "msisdn": "+4915112345678",
  "numberId": "8238c1b9-c2c0-4455-9b03-4bd787052709",
  "executionDate": "2027-03-01",
  "errors": []
}
```

### Get port-in status {#get-port-in-status}

`GET /v1/port-ins/{portInId}` · Enterprise API + Partner API

Retrieves the current status and details of a port-in request. Use this endpoint to track the progress of a submitted port-in through its lifecycle.

Requires the permission `port-ins:read` on the key.

[Try it in the playground](https://developer.telgea.com/playground#tag/port-ins/GET/v1/port-ins/{portInId}). Calls run against your live organization; there is no sandbox.

**Parameters**

| Parameter | In | Type | Description |
| --- | --- | --- | --- |
| `portInId` | path | string (uuid) (required) | UUID of the port-in request. Example: `c3d4e5f6-a7b8-9012-c345-678901234567` |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` | Successfully retrieved port-in details. | `GetPortInResponse` |
| `400` | Request failed schema validation, or was refused by a business rule | none |
| `401` | Missing or invalid API key in the X-API-Key header | `UnauthorizedError` |
| `403` | Insufficient permissions to read port-in data | `ForbiddenError` |
| `404` | Port-in not found. The specified ID does not match any existing port-in. | `NotFoundError` |
| `429` | The API key has used its request quota for the current window. `Retry-After` says how many seconds until it resets. | `TooManyRequestsError` |
| `500` | Internal server error. Contact support if this persists. | `InternalError` |

**Response fields (200, GetPortInResponse)**

| Field | Type | Description |
| --- | --- | --- |
| `id` | string (required) | UUID of the port-in request. Example: `"c3d4e5f6-a7b8-9012-c345-678901234567"` |
| `countryCode` | string (required) | Market the number is being ported into. Example: `"DE"` |
| `msisdn` | string (required) | The number being ported, in E.164 format. Example: `"+4915112345678"` |
| `executionDate` | string (required) | The porting date, as YYYY-MM-DD. Example: `"2027-03-01"` |
| `type` | string (required) | Port-in type this request was submitted under, or null when the donor needed none. Example: `"personal"` |
| `status` | enum (required) | Current status of the port-in One of: `PENDING`, `CREATED`, `SUBMITTED`, `ACCEPTED`, `REJECTED`, `SUCCEEDED`, `FAILED`. Example: `"CREATED"` |
| `numberId` | string (required) | UUID of the number this port-in creates. Use it to read the line once porting succeeds. Example: `"8238c1b9-c2c0-4455-9b03-4bd787052709"` |
| `failure` | PortInFailure (required) | Why a REJECTED or FAILED port-in ended that way: values you can branch on, and a text you can show your customer. Null in every other status. |
| `failure.reason` | enum (required) | Why the port-in ended this way, or null when we could not classify it. One of: `WRONG_CUSTOMER_TYPE`, `CONTRACT_LOCKED`, `NUMBER_OWNERSHIP_MISMATCH`, `NUMBER_ALREADY_PORTING`, `AUTHORIZATION_CODE_INVALID`, `PORTING_DATE_UNAVAILABLE`, `FIELD_MISSING`, `FIELD_INVALID`, `FIELD_MISMATCH`, `REJECTED_BY_DONOR`, `CANCELLED`. Example: `"AUTHORIZATION_CODE_INVALID"` |
| `failure.field` | string (required) | What the donor objected to, when it identified something. Either a key you sent in `fields` (e.g. `authorizationCode`) or a top-level property of the port-in (`msisdn`, `type`, `executionDate`). Example: `"authorizationCode"` |
| `failure.text` | string (required) | What happened and what to do next, in English, for showing to your customer. The wording may change at any time: branch on `reason`, never on this. Example: `"Your authorization code was refused or has expired."` |
| `createdAt` | string (required) | When the port-in was submitted, ISO 8601. Example: `"2027-02-22T10:00:00Z"` |
| `updatedAt` | string (required) | When the port-in last changed status, ISO 8601. Example: `"2027-02-22T10:00:00Z"` |

**Example request**

```bash
curl -X GET "https://api.telgea.com/v1/port-ins/c3d4e5f6-a7b8-9012-c345-678901234567" \
  -H "x-api-key: $TELGEA_API_KEY"
```

**Response 200**

```json
{
  "id": "c3d4e5f6-a7b8-9012-c345-678901234567",
  "countryCode": "DE",
  "msisdn": "+4915112345678",
  "executionDate": "2027-03-01",
  "type": "personal",
  "status": "SUBMITTED",
  "numberId": "8238c1b9-c2c0-4455-9b03-4bd787052709",
  "failure": null,
  "createdAt": "2027-02-22T10:00:00Z",
  "updatedAt": "2027-02-22T10:00:00Z"
}
```

### Discard a failed port-in {#discard-a-failed-port-in}

`POST /v1/port-ins/{portInId}/discard` · Enterprise API + Partner API

Gives up on a port-in that ended in REJECTED or FAILED, when you will not submit it again. The number leaves the plan and stays with the losing carrier. Unless the user has another number, the plan reads `INACTIVE`, so you can create a user plan with a new number or port in a different one.

The port-in stays readable at its own id and keeps its `REJECTED` or `FAILED` status. A discard shows as the number leaving the plan it was being ported onto, and the number's port-ins also leave `GET /v1/port-ins/number/{numberId}`.

Only a port-in that ended in REJECTED or FAILED can be discarded, and not while a newer port-in for the same number is in flight or once one has succeeded. Either is refused with 400 BAD_REQUEST. Discarding a port-in that is already discarded succeeds and changes nothing.

Requires the permission `port-ins:discard` on the key.

[Try it in the playground](https://developer.telgea.com/playground#tag/port-ins/POST/v1/port-ins/{portInId}/discard). Calls run against your live organization; there is no sandbox.

**Parameters**

| Parameter | In | Type | Description |
| --- | --- | --- | --- |
| `portInId` | path | string (uuid) (required) | UUID of the port-in request. Example: `c3d4e5f6-a7b8-9012-c345-678901234567` |
| `idempotency-key` | header | string | A value unique to this request, of 1 to 255 printable ASCII characters — a UUID is the usual choice. Retry with the same key within 24 hours and you get the original response back, even if it was a failure, and nothing is done twice. Send a new key whenever you change the request. Keys are private to your API key and organisation. Example: `0b7e4f1c-6a2d-4c9e-8f35-d1a7b2c93e60` |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `204` | Port-in discarded. No content returned. | none |
| `400` | Request failed schema validation, or the port-in cannot be discarded: it has not failed, or a newer port-in has taken over its number | none |
| `401` | Missing or invalid API key in the X-API-Key header | `UnauthorizedError` |
| `403` | Insufficient permissions to discard port-ins | `ForbiddenError` |
| `404` | Port-in not found. The specified ID does not match any existing port-in. | `NotFoundError` |
| `429` | The API key has used its request quota for the current window. `Retry-After` says how many seconds until it resets. | `TooManyRequestsError` |
| `500` | Internal server error. Contact support if this persists. | `InternalError` |

**Example request**

```bash
curl -X POST "https://api.telgea.com/v1/port-ins/c3d4e5f6-a7b8-9012-c345-678901234567/discard" \
  -H "x-api-key: $TELGEA_API_KEY"
```

### List port-ins for a number {#list-port-ins-for-a-number}

`GET /v1/port-ins/number/{numberId}` · Enterprise API + Partner API

Retrieves a paginated list of all port-in requests associated with a specific number. Use this to see the full porting history for a phone number, including completed, failed, and in-progress port-ins.

Requires the permission `port-ins:read` on the key.

[Try it in the playground](https://developer.telgea.com/playground#tag/port-ins/GET/v1/port-ins/number/{numberId}). Calls run against your live organization; there is no sandbox.

**Parameters**

| Parameter | In | Type | Description |
| --- | --- | --- | --- |
| `numberId` | path | string (uuid) (required) | UUID of the number resource Example: `8238c1b9-c2c0-4455-9b03-4bd787052709` |
| `page` | query | integer | Page to return, starting at 1. Example: `1` |
| `pageSize` | query | integer | Items per page, at most 100. Defaults to 50. Example: `50` |

**Responses**

| Status | Meaning | Body |
| --- | --- | --- |
| `200` | Successfully retrieved port-ins for the specified number. | `ListPortInsResponse` |
| `400` | Request failed schema validation, or was refused by a business rule | none |
| `401` | Missing or invalid API key in the X-API-Key header | `UnauthorizedError` |
| `403` | Insufficient permissions to read port-in data | `ForbiddenError` |
| `429` | The API key has used its request quota for the current window. `Retry-After` says how many seconds until it resets. | `TooManyRequestsError` |
| `500` | Internal server error. Contact support if this persists. | `InternalError` |

**Response fields (200, ListPortInsResponse)**

| Field | Type | Description |
| --- | --- | --- |
| `page` | integer (required) | The page these items came from. Example: `1` |
| `pageSize` | integer (required) | Items per page, as requested. Example: `50` |
| `itemsCount` | integer (required) | Total items across all pages. Divide by pageSize to get the number of pages. Example: `142` |
| `items` | array of GetPortInResponse (required) | Port-ins on this page, newest first. |
| `items.id` | string (required) | UUID of the port-in request. Example: `"c3d4e5f6-a7b8-9012-c345-678901234567"` |
| `items.countryCode` | string (required) | Market the number is being ported into. Example: `"DE"` |
| `items.msisdn` | string (required) | The number being ported, in E.164 format. Example: `"+4915112345678"` |
| `items.executionDate` | string (required) | The porting date, as YYYY-MM-DD. Example: `"2027-03-01"` |
| `items.type` | string (required) | Port-in type this request was submitted under, or null when the donor needed none. Example: `"personal"` |
| `items.status` | enum (required) | Current status of the port-in One of: `PENDING`, `CREATED`, `SUBMITTED`, `ACCEPTED`, `REJECTED`, `SUCCEEDED`, `FAILED`. Example: `"CREATED"` |
| `items.numberId` | string (required) | UUID of the number this port-in creates. Use it to read the line once porting succeeds. Example: `"8238c1b9-c2c0-4455-9b03-4bd787052709"` |
| `items.failure` | PortInFailure (required) | Why a REJECTED or FAILED port-in ended that way: values you can branch on, and a text you can show your customer. Null in every other status. |
| `items.failure.reason` | enum (required) | Why the port-in ended this way, or null when we could not classify it. One of: `WRONG_CUSTOMER_TYPE`, `CONTRACT_LOCKED`, `NUMBER_OWNERSHIP_MISMATCH`, `NUMBER_ALREADY_PORTING`, `AUTHORIZATION_CODE_INVALID`, `PORTING_DATE_UNAVAILABLE`, `FIELD_MISSING`, `FIELD_INVALID`, `FIELD_MISMATCH`, `REJECTED_BY_DONOR`, `CANCELLED`. Example: `"AUTHORIZATION_CODE_INVALID"` |
| `items.failure.field` | string (required) | What the donor objected to, when it identified something. Either a key you sent in `fields` (e.g. `authorizationCode`) or a top-level property of the port-in (`msisdn`, `type`, `executionDate`). Example: `"authorizationCode"` |
| `items.failure.text` | string (required) | What happened and what to do next, in English, for showing to your customer. The wording may change at any time: branch on `reason`, never on this. Example: `"Your authorization code was refused or has expired."` |
| `items.createdAt` | string (required) | When the port-in was submitted, ISO 8601. Example: `"2027-02-22T10:00:00Z"` |
| `items.updatedAt` | string (required) | When the port-in last changed status, ISO 8601. Example: `"2027-02-22T10:00:00Z"` |

**Example request**

```bash
curl -X GET "https://api.telgea.com/v1/port-ins/number/8238c1b9-c2c0-4455-9b03-4bd787052709" \
  -H "x-api-key: $TELGEA_API_KEY"
```

**Response 200**

```json
{
  "page": 1,
  "pageSize": 50,
  "itemsCount": 2,
  "items": [
    {
      "id": "c3d4e5f6-a7b8-9012-c345-678901234567",
      "countryCode": "DE",
      "msisdn": "+4915112345678",
      "executionDate": "2027-03-01",
      "type": "personal",
      "status": "SUBMITTED",
      "numberId": "8238c1b9-c2c0-4455-9b03-4bd787052709",
      "failure": null,
      "createdAt": "2027-02-22T10:00:00Z",
      "updatedAt": "2027-02-22T10:00:00Z"
    },
    {
      "id": "b1d2c3e4-f5a6-7890-b123-456789012345",
      "countryCode": "DE",
      "msisdn": "+4915112345678",
      "executionDate": "2027-03-01",
      "type": "personal",
      "status": "FAILED",
      "numberId": "8238c1b9-c2c0-4455-9b03-4bd787052709",
      "failure": {
        "reason": "CONTRACT_LOCKED",
        "field": null,
        "text": "The current contract doesn't allow the transfer yet. Ask your current carrier to release the number for porting."
      },
      "createdAt": "2027-02-22T10:00:00Z",
      "updatedAt": "2027-02-22T10:00:00Z"
    }
  ]
}
```
