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.
Every port-in follows this state progression. The full explanation, with what to do in each state, is on the port-in lifecycle page.
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:
typeLabelon 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.explanationon a type helps your customer pick it, in a market with more than one type.helpon a field says what the value is and whose details it is.howToGeton 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."
}
]
}| Code | The value |
|---|---|
REQUIRED | is missing or empty |
FORMAT_INVALID | isn't in the field's format, such as a PAC that isn't 3 letters then 6 digits |
LENGTH_INVALID | is shorter than the field's minLength or longer than its maxLength |
NOT_ALLOWED | is 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_INVALID | has 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_TYPE | belongs under another of the market's types, such as a Swedish organisationsnummer sent with personal. Send the other type |
DATE_NOT_ALLOWED | is 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.datesfrom Step 1. A date outside the list is refused with400 BAD_REQUESTand the typeprovider-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, asvalid: falseorstatus: "REJECTED". A date that isn'tYYYY-MM-DDis refused with400 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=50See 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.
| Event | Fired when |
|---|---|
PortInSubmitted | Request sent to the losing carrier |
PortInAccepted | Losing carrier accepts; execution date confirmed |
PortInRejected | Rejected by the losing carrier or by provider validation, possibly before any PortInSubmitted |
PortInSucceeded | Number ported successfully |
PortInFailed | Failed 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 reachesACCEPTED - 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 (
REJECTEDorFAILED)
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 benull: some failures cannot yet be classified, and we would rather say so than publish a reason that is wrong. Treatnull, or a value you do not recognise, as "this port-in failed and we cannot tell you why" and fall back onstatus. 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
textto 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 whenreasonisnull. Its wording may change at any time, so never branch on it.
Failure Reasons
| Reason | Typically seen for |
|---|---|
WRONG_CUSTOMER_TYPE | Customer type (private/business) doesn't match donor records |
CONTRACT_LOCKED | A binding/contract at the donor blocks porting |
NUMBER_OWNERSHIP_MISMATCH | Donor reports the number is not owned by the stated subscriber |
NUMBER_ALREADY_PORTING | Another active port-in already exists for this MSISDN/SIM |
AUTHORIZATION_CODE_INVALID | Authorization code (PAC/STAC/RIO/DOB/etc.) is invalid or expired |
PORTING_DATE_UNAVAILABLE | Requested porting date isn't available at the donor |
FIELD_MISSING | A required field was not supplied or was empty |
FIELD_INVALID | A field value was present but malformed or not accepted |
FIELD_MISMATCH | A 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_DONOR | Donor rejected the port-in without a more specific reason |
CANCELLED | The 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
| Code | Description | Suggested Action |
|---|---|---|
INCORRECT_DETAILS | Submitted information doesn't match the losing carrier's records | Ask the user to verify and correct their details, then resubmit |
INTERNAL_ERROR | Unexpected error during processing | Retry the port-in; contact support if it persists |
CANCELLED | Port-in was cancelled | Resubmit if still needed |
FORBIDDEN | Number is not eligible for porting | Number 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}/discardIt 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.