TelgeaDevelopers

Transfer Number to Telgea

Transfer existing phone numbers from other carriers to Telgea.

Number Porting

Number porting lets users transfer their existing phone numbers from another carrier to Telgea. The process varies by country — different markets require different authentication methods, personal details, and carrier-specific information.

This guide covers the full porting lifecycle: discovering field requirements, validating data, submitting a port-in, scheduling, and tracking progress through to completion.

Lifecycle

Who does what, in order. The dotted loop is the resubmission after a rejection.

Your employeeYouTelgeaCollect the number and the wished port dateGET /v1/port-ins/fields/{countryCode}Which fields does this country need?Collect the required fieldsPOST /v1/port-ins/validate validValidate before submittingProvision the line first: it must hold no number, or a port-in that failedPOST /v1/port-ins CREATEDSubmit the port-inPortInSubmitted SUBMITTEDSent to the losing carrierPortInAccepted ACCEPTEDCarrier accepted; date confirmedPortInRejected REJECTEDRefused: read failureDetailsFix the data and submit a new port-inPortInSucceeded SUCCEEDEDNumber is on TelgeaTell them the number has moved

Every port-in follows this state progression. The full explanation, with what to do in each state, is on the port-in lifecycle page.

submissionsubmissioncarrieracceptance checkport dateexecutionexecutionCREATEDSUBMITTEDACCEPTEDSUCCEEDEDREJECTEDFAILED

A port-in can be rejected at two points: during the initial submission to the provider, or later during the acceptance check phase. Similarly, failures can occur at multiple stages — during submission, acceptance checking, or the final execution workflow. In all cases, the port-in's failure describes what went wrong.

Step 1: Discover Required Fields

Steps 1 and 2 are safe to try in the playground first — reading the fields and validating your data change nothing. Step 3 does: it starts a real port-in with the customer's current carrier.

Each country has different requirements for porting. Use the fields endpoint to discover what information you need to collect:

GET /v1/port-ins/fields/{countryCode}

The response is keyed by port-in type (e.g. personal and business, or default in a market with one type). Each type contains a human-readable label, an array of field definitions with validation rules, and the transfer date as executionDate. Field types include text, select, date, number, and checkbox. The United Kingdom's one type takes a PAC:

{
  "default": {
    "label": "Default",
    "fields": [
      {
        "name": "authorizationCode",
        "label": "PAC code",
        "type": "text",
        "description": "Porting Authorization Code from the current carrier. The code is valid for 30 days.",
        "errorMessage": "A PAC is 3 letters, then 6 digits (e.g. ABC123456). 6 digits then 3 letters is a STAC, not a PAC.",
        "howToGet": {
          "id": "port-in.gb.authorizationCode.howToGet",
          "en": "If the number is in a person's name, text PAC to 65075 from the phone with this number. If it's in a company's name, the company asks the current carrier for the PAC."
        },
        "example": "ABC123456",
        "regex": "/^\\s*[A-Za-z]{3}\\s*\\d{3}\\s*\\d{3}\\s*$/"
      }
    ],
    "executionDate": {
      "name": "executionDate",
      "label": "Transfer date",
      "type": "date",
      "errorMessage": "That date isn't possible in the UK.",
      "help": {
        "id": "port-in.executionDate.help.a-weekday-from-the-next-working-day-to-30-days-from-today",
        "en": "A weekday, from the next working day to 30 days from today."
      },
      "minDate": "2027-03-01",
      "maxDate": "2027-03-29",
      "dates": [
        "2027-03-01", "2027-03-02", "2027-03-03", "2027-03-04", "2027-03-05",
        "2027-03-08", "2027-03-09", "2027-03-10", "2027-03-11", "2027-03-12",
        "2027-03-15", "2027-03-16", "2027-03-17", "2027-03-18", "2027-03-19",
        "2027-03-22", "2027-03-23", "2027-03-24", "2027-03-25", "2027-03-26",
        "2027-03-29"
      ],
      "validUntil": "2027-02-27T00:00:00.000Z"
    },
    "timing": {
      "submitted": "immediately",
      "finalDateSetBy": "us",
      "dateMayMove": true,
      "cancel": "never",
      "reschedule": "never"
    }
  }
}

executionDate.dates lists every date the port-in can be executed on, as of the day you asked: send one of them in Step 2 and Step 3. The list holds until validUntil, the market's next midnight, when its first date moves on. minDate and maxDate are its first and last dates, for a date picker's bounds, and help describes the range in words you can show your customer.

The texts help you guide your customer through the form. Each is an object with an id that never changes, to key a translation off, and the English text in en. A type or field that has nothing to say leaves the text out:

  • typeLabel on a type heads the choice of type, such as "Who is the number registered to?", in a market with more than one type. It is the same in every type.
  • explanation on a type helps your customer pick it, in a market with more than one type.
  • help on a field says what the value is and whose details it is.
  • howToGet on a field says where to find a value your customer has to look up, such as the PAC above.

A field can also carry example, a plain string in the format the field accepts, such as ABC123456 above. It shows the format only and is never a real value.

timing says how a port-in runs in the market: when it is submitted to the carrier, who sets the final transfer date (finalDateSetBy), whether the date can still move once submitted (dateMayMove), and up to when the port-in can be cancelled (cancel) or its date changed (reschedule). It is the same in every type of a market.

Do not hardcode field requirements. They vary by country and provider, and can change. Always fetch them dynamically. See the Get port-in fields API reference for a full example response with multiple field types. A JSON Schema version is also available for client-side validation, with the same dates under $defs.executionDate.

Step 2: Validate Before Submission

Use the validate endpoint to dry-run your port-in data against provider requirements before committing:

POST /v1/port-ins/validate
{
  "countryCode": "GB",
  "type": "default",
  "msisdn": "+447400123456",
  "executionDate": "2027-03-01",
  "fields": {
    "authorizationCode": "ABC123456"
  }
}

This is a read-only operation — no resources are created or modified. The response indicates whether the data would pass:

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

If validation fails, valid is false and errors holds one entry per refused value. Each names its field (a key you sent in fields, or type), a code to branch on and a message in English. A PAC typed the wrong way round is a STAC:

{
  "valid": false,
  "errors": [
    {
      "field": "authorizationCode",
      "code": "FORMAT_INVALID",
      "message": "A PAC is 3 letters, then 6 digits (e.g. ABC123456). 6 digits then 3 letters is a STAC, not a PAC."
    }
  ]
}
CodeThe value
REQUIREDis missing or empty
FORMAT_INVALIDisn't in the field's format, such as a PAC that isn't 3 letters then 6 digits
LENGTH_INVALIDis shorter than the field's minLength or longer than its maxLength
NOT_ALLOWEDis well formed but not one the market accepts: a value outside options or outside min and max, a date of birth in the future, or a French landline RIO
CHECK_DIGIT_INVALIDhas a check digit, check letter or key that doesn't match the rest of it: a typo, or a code that belongs to another number. Checked for Swedish personnummer and organisationsnummer, Spanish DNI and NIE, and French RIO
WRONG_CUSTOMER_TYPEbelongs under another of the market's types, such as a Swedish organisationsnummer sent with personal. Send the other type
DATE_NOT_ALLOWEDis a date the field doesn't accept. A transfer date outside executionDate.dates is refused with a 400 instead; see Constraints

A code is never renamed or removed, but new ones will be added. Show the message for a code you don't recognise. Submitting the port-in without validating it first returns the same errors, as described in Step 3.

The phone number is checked before any of this. An msisdn that isn't a valid phone number, or that belongs to another country than countryCode, is refused with 400 VALIDATION_ERROR and named in its fields (see Validation errors), both here and on submission. It never appears in errors.

Always validate before submitting to catch problems without side effects.

A number that cannot be ported in at all is refused with 409 CONFLICT, exactly as submitting it would be: one Telgea already holds, or one with a port-in already in progress.

See the Validate port-in data API reference.

Step 3: Submit the Port-In

This one is real

Submitting a port-in — from your code or from the playground — asks the customer's current carrier to move the number. There is no sandbox to rehearse it in.

Once validation passes, submit the port-in:

POST /v1/port-ins
{
  "userId": "123e4567-e89b-12d3-a456-426614174000",
  "countryCode": "GB",
  "msisdn": "+447400123456",
  "executionDate": "2027-03-01",
  "type": "default",
  "fields": {
    "authorizationCode": "ABC123456"
  }
}

Constraints

  • Execution date: Must be one of the type's executionDate.dates from Step 1. A date outside the list is refused with 400 BAD_REQUEST and the type provider-platform/EXECUTION_DATE_NOT_OFFERED, which names the first and last dates the market offers, both here and on the validate endpoint. The date is checked once every other field passes, so field errors still come back first, as valid: false or status: "REJECTED". A date that isn't YYYY-MM-DD is refused with 400 VALIDATION_ERROR
  • One active port-in per number: If a port-in is already in progress (not completed, failed, or rejected) for the same MSISDN, the request will be rejected
  • Not already with Telgea: A number on a Telgea line, or one whose line ended within the last 14 days and can still be restored, is refused with 409 CONFLICT. To get back a number whose line you ended, contact support within those 14 days
  • Plan state: The user must hold no current phone number, or have a previously unsuccessful port-in

Response

The response confirms the port-in was created with status: "CREATED". The actual submission to the losing carrier happens asynchronously — track its progress as described in Step 4.

{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "status": "CREATED",
  "msisdn": "+447400123456",
  "numberId": "123e4567-e89b-12d3-a456-426614174000",
  "executionDate": "2027-03-01"
}

If you skip the validate endpoint and the data fails pre-flight validation, the response will have status: "REJECTED" with an errors array. Using the validate endpoint first avoids this.

See the Submit a port-in request API reference.

Step 4: Track Progress

Polling

Retrieve the current status of a port-in at any time:

GET /v1/port-ins/{portInId}
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "countryCode": "GB",
  "msisdn": "+447400123456",
  "executionDate": "2027-03-01",
  "type": "default",
  "status": "SUBMITTED",
  "numberId": "123e4567-e89b-12d3-a456-426614174000",
  "createdAt": "2027-02-26T10:00:00Z",
  "updatedAt": "2027-02-26T10:05:00Z",
  "failure": null
}

To see the full porting history for a number (including past port-ins):

GET /v1/port-ins/number/{numberId}?page=1&pageSize=50

See the Get port-in status and List port-ins for a number API references.

Webhooks

Subscribe to webhook events for real-time notifications as the port-in progresses. See the Webhooks guide for setup and security details.

EventFired when
PortInSubmittedRequest sent to the losing carrier
PortInAcceptedLosing carrier accepts; execution date confirmed
PortInRejectedRejected by the losing carrier or by provider validation, possibly before any PortInSubmitted
PortInSucceededNumber ported successfully
PortInFailedFailed at any stage, or cancelled

All port-in webhook payloads share this structure in the data field:

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "eventType": "PortInAccepted",
  "timestamp": "2026-03-25T14:30:00.000Z",
  "organizationId": "b5e2d7f3-a1c4-4e89-9f6b-3d8a7c5e1024",
  "data": {
    "portInId": "d4e5f6a7-b8c9-0123-d456-789012345678",
    "numberId": "f7a8b9c0-d1e2-3456-f789-012345678901",
    "phoneNumber": "+4512345678",
    "countryCode": "DK",
    "state": "ACCEPTED",
    "executionDate": "2026-04-01",
    "failureDetails": null
  }
}

Scheduling Considerations

The execution date is when the number will actually transfer to Telgea. Keep in mind:

  • The execution date must be one of the dates the fields endpoint lists for the type. They are weekdays, and how soon the first one is and how far ahead the last one is differ by market
  • The list changes at the market's midnight, executionDate.validUntil. If your customer leaves a form open past it, fetch the fields again before you submit, or the date they picked may have left the list
  • The losing carrier confirms or adjusts the execution date during the acceptance phase — the confirmed date is on the port-in once it reaches ACCEPTED. Please note that the execution date submitted in the initial port-in request is the desired execution date — the actual execution date is determined by the network, and can change. Always note down the execution date once the port-in reaches ACCEPTED
  • Build buffer time into your scheduling to account for potential rejections and resubmissions
  • In a market whose operator publishes no API, each step is confirmed in hours rather than the moment the losing carrier answers
  • If a port-in is rejected, you can correct the information and submit a new port-in for the same number once the previous one is in a terminal state (REJECTED or FAILED)

Handling Rejections and Failures

GET /v1/port-ins/{portInId} and GET /v1/port-ins/number/{numberId} carry a failure object on any port-in that ended in REJECTED or FAILED, and null on every other status:

{
  "status": "REJECTED",
  "failure": {
    "reason": "AUTHORIZATION_CODE_INVALID",
    "field": "authorizationCode",
    "text": "Your authorization code was refused or has expired."
  }
}
  • Branch on reason. It can be null: some failures cannot yet be classified, and we would rather say so than publish a reason that is wrong. Treat null, or a value you do not recognise, as "this port-in failed and we cannot tell you why" and fall back on status. New reasons will be added over time, so handle unknown values rather than rejecting them.
  • field, when present, identifies the port-in input the carrier objected to (e.g. authorizationCode, ownerFirstName, currentCarrier, type, msisdn).
  • Show text to your customer. It says what happened and what to do next, in English, with the market's own advice where there is some — where to find a SIM number, which dates are possible. It is always set, including when reason is null. Its wording may change at any time, so never branch on it.

Failure Reasons

ReasonTypically seen for
WRONG_CUSTOMER_TYPECustomer type (private/business) doesn't match donor records
CONTRACT_LOCKEDA binding/contract at the donor blocks porting
NUMBER_OWNERSHIP_MISMATCHDonor reports the number is not owned by the stated subscriber
NUMBER_ALREADY_PORTINGAnother active port-in already exists for this MSISDN/SIM
AUTHORIZATION_CODE_INVALIDAuthorization code (PAC/STAC/RIO/DOB/etc.) is invalid or expired
PORTING_DATE_UNAVAILABLERequested porting date isn't available at the donor
FIELD_MISSINGA required field was not supplied or was empty
FIELD_INVALIDA field value was present but malformed or not accepted
FIELD_MISMATCHA well-formed field value doesn't match what the donor has on file for this number, e.g. the SIM number (ICCID) of a different SIM
REJECTED_BY_DONORDonor rejected the port-in without a more specific reason
CANCELLEDThe port-in was called off at the donor. There is no API for cancelling one yourself

In webhooks

The port-in webhooks carry the failure as failureDetails: a code, a text and — when we can identify them — a fine-grained reason and the offending field:

{
  "failureDetails": {
    "code": "INCORRECT_DETAILS",
    "reason": "AUTHORIZATION_CODE_INVALID",
    "field": "authorizationCode",
    "text": "Your authorization code was refused or has expired."
  }
}

The webhook's text is written when the event is sent and can leave out market advice that GET /v1/port-ins/{portInId} includes.

Failure Codes

CodeDescriptionSuggested Action
INCORRECT_DETAILSSubmitted information doesn't match the losing carrier's recordsAsk the user to verify and correct their details, then resubmit
INTERNAL_ERRORUnexpected error during processingRetry the port-in; contact support if it persists
CANCELLEDPort-in was cancelledResubmit if still needed
FORBIDDENNumber is not eligible for portingNumber cannot be ported — contact support for details

failure on the port-in carries no code: branch on reason there. The coarse code is kept on the webhook for compatibility, but it is being retired — building on it would leave you with work to do later.

Once a port-in reaches a terminal state — REJECTED or FAILED — a new port-in can be submitted for the same number.

Discarding a failed port-in

If you won't submit the number again, discard the port-in. The number leaves the plan it was arriving on and stays with the losing carrier, so you can obtain a new line for the user or port in a different number:

POST /v1/port-ins/{portInId}/discard

It answers 204 with no body. Only a REJECTED or FAILED port-in 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. Discarding a port-in that is already discarded succeeds and changes nothing.

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 arriving on, and the number's port-ins also leave GET /v1/port-ins/number/{numberId}. Discarding needs the port-ins:discard permission on your key.

See the Discard a failed port-in API reference.

On this page