Changelog
API changelog and release history.
Unreleased
2026-10-19
- Analytics:
GET /v1/analyticsanswers400 BAD_REQUESTto an emptymsisdn. It used to ignore the parameter and report usage for every number in scope. - Users:
GET /v1/usersanswers400 BAD_REQUESTto an empty or blankemployeeId. It used to ignore the parameter and list every user. If you build either query from a value that can be empty, leave the parameter out instead.
- User plans:
GET /v1/user-plans/searchanswers400 BAD_REQUESTto an emptyiccid. - User plans:
POST /v1/user-plans/{userId}/terminationnow ends a plan whose number is suspended. The number stays suspended until the end date and is deactivated 14 days later, as any ended number is. Before, the request was accepted but the number was never ended. The same holds for a number suspended after its end was booked. Until the end date such a plan readsTERMINATINGwithterminationScheduledAtset, where it used to readINACTIVE. - User plans: a plan whose number is suspended now reads
ACTIVE, where it used to readTERMINATING. Suspending a number doesn't end the plan, and the plan is still billed while the number is suspended. Such a plan readsTERMINATINGonly once its end is booked. If you filter user plans bystatus, suspended numbers now come back underACTIVE.
2026-10-16
2026-10-15
- Data pools: the docs for
GET /v1/data-pools/{poolId}/membersnow say to countusageBytesonce peruserId. A member with several lines is listed once per line, and each row carries the member's usage across all their lines, so summing the rows counts that member more than once. The response itself has not changed. If you add upusageBytesacross rows, deduplicate byuserIdfirst.
- Webhooks:
UserPlanCreatedandUserPlanActivefire once for each local number a user takes, not only for their first. Both payloads addlocalNumbers, every local number the user holds, oldest first.UserPlanActivekeepslocalNumber, which still describes the user's first local number. For a user with one number nothing else changes:localNumbersholds that one number. Each item'ssimisnullwhile the number has no eSIM yet.
2026-10-12
- All endpoints: every name you send or read is camelCase. This renames the snake_case
names that were left. On
GET /v1/usage, the query isstartDate,endDate,pageSizeanduserId. Each record hastimeStarted,timeEnded,servingCountryCode,usageClass,counterpartyMsisdn,callDirection,counterpartyCountryCode,networkType,userId,totalPrice,unitPriceandrateId, and a page hasnextCursorandhasMore. OnGET /v1/analytics, the query isstartDate,endDate,userId,countryIsoCodeandorderBy. OnGET /v1/rates, it isservingCountryandasOfDate, and each rate hasratePerUnitandvalidFrom. Every error carriesrequestId, and its extension members are camelCase too:missingPermission,revokedAt,countryCode,planEndedAt,currentState,requiredStates,executionDate,firstDateandlastDate. The old names are no longer accepted or returned. - All endpoints: since 2026-10-06, every error is an
RFC 9457 Problem Details body served as
application/problem+json. It's a flat object withtype,title,status,detail,codeandrequest_id, where it used to be wrapped inerror.messageis nowdetail, andhintis gone:typeis a URI that opens the error's own page, which says what to do about it.codekeeps its values, andfieldsandmissing_permissionkeep their place, now at the top level. Many refusals that used to answer with a generic code have their owntype, such ashub/PORT_IN_ALREADY_IN_PROGRESS. Branch oncodeortype. See Errors. - User plans and port-ins: a market can require your organization to pass business verification
before it provisions a user plan or ports a number in there. Until it has,
POST /v1/user-plansandPOST /v1/port-insanswer403 COUNTRY_NOT_SUPPORTEDwith the typeORGANIZATION_VERIFICATION_REQUIRED, and nothing is created. Send the request again once your organization has passed. No market requires verification yet. - Port-ins: since 2026-10-05,
POST /v1/port-ins/validateneeds theport-ins:validatepermission instead ofport-ins:create. Every key that heldport-ins:createwas given it, so no existing key needs a change. Grant it in Console to a new key that validates port-ins. - Port-ins: the execution date must be one of the dates the market offers, and the 7-day minimum
is gone. Each type on
GET /v1/port-ins/fields/{countryCode}lists the dates inexecutionDate.dates, which hold until the market's midnight,executionDate.validUntil.GET /v1/port-ins/fields/{countryCode}/schemacarries the same list as$defs.executionDate. How soon the first date is and how far ahead the last differ by market, and weekends are refused in every market. See the port-in guide. - Port-ins:
POST /v1/port-ins/validateandPOST /v1/port-insrefuse a date outside the list with400 BAD_REQUESTand the typeprovider-platform/EXECUTION_DATE_NOT_OFFERED, which names the market's first and last dates. The date is checked once every other field passes. Until 2026-10-12 only some markets checked the date, and a refused one answered200withvalid: falseorstatus: "REJECTED". A malformedexecutionDateanswersVALIDATION_ERROR, andearliest_execution_dateis gone with the 7-day minimum. - Port-ins:
GET /v1/port-ins/fields/{countryCode}has carried texts for guiding your customer through the form since 2026-10-07. They were not announced until now. Each type can carryexplanation, to help pick it where a market has more than one, andtiming: when the port-in is submitted to the carrier, who sets the final date, whether it can still move, and up to when it can be cancelled or rescheduled. Each field can carryhelp(what the value is and whose details it is),howToGet(where to find a value such as a PAC) andexample(a value in the accepted format). Each text has anidthat never changes, to key a translation off, and the English inen. See the port-in guide. - Port-ins: each type on
GET /v1/port-ins/fields/{countryCode}carriestypeLabelin a market with more than one type: the label over the choice of type, such as "Who is the number registered to?", to head the choice beside each type'sexplanation. - Port-ins: since 2026-10-07, each entry in
errorsfromPOST /v1/port-ins/validate, and from aREJECTEDanswer toPOST /v1/port-ins, names itsfieldand carries acodeto branch on:REQUIRED,FORMAT_INVALID,LENGTH_INVALID,NOT_ALLOWEDorDATE_NOT_ALLOWED. Since 2026-10-08 it can also beCHECK_DIGIT_INVALIDorWRONG_CUSTOMER_TYPE. New codes will be added; show themessagefor one you don't recognise. The port-in guide says what each means. - Port-ins: since 2026-10-08, validating or creating a port-in catches typos the carrier would
otherwise refuse days later. A wrong check digit in a Swedish personnummer or
organisationsnummer, a Spanish DNI or NIE, or a French RIO answers
CHECK_DIGIT_INVALID. A Swedish organisationsnummer sent withpersonalanswersWRONG_CUSTOMER_TYPE. A French landline RIO and a German date of birth in the future answerNOT_ALLOWED. Values are also accepted as people type them: spaces, separators and lower case in a UK PAC, a French RIO, Spanish and Swedish ID numbers and SIM numbers, and a US ZIP+4. A SIM number in Denmark and Belgium no longer needs at least 18 digits. - Port-ins: since 2026-10-05,
POST /v1/port-ins/validateandPOST /v1/port-insanswer409 CONFLICTfor a number Telgea already holds: one on a Telgea line, or one whose plan ended in the last 14 days and can still be restored. Validating a number with a port-in already in progress answers409too, as creating one always has. Neither creates anything.
2026-10-09
- Port-ins:
POST /v1/port-ins/{portInId}/discarddiscards a port-in that ended inREJECTEDorFAILED. The number leaves the plan, which readsINACTIVEunless the user has another number, so you can give the user a new number or port in a different one. A port-in that can't be discarded answers400 BAD_REQUESTwith the typePORT_IN_NOT_DISCARDABLE. It needs the newport-ins:discardpermission, which no existing key holds; add it to your key in Console.
2026-10-08
- Port-ins:
failureonGET /v1/port-ins/{portInId}andGET /v1/port-ins/number/{numberId}carriestext: what happened and what to do next, in English, for showing to your customer. It includes the market's advice where there is some, such as where to find a SIM number, and is set even whenreasonisnull. Its wording may change at any time; keep branching onreasonandfield.
- Webhooks:
failureDetailsonPortInRejectedandPortInFailedcarriestexttoo, written when the event is sent. It can leave out market advice thatGET /v1/port-ins/{portInId}includes.
2026-10-01
- Usage:
GET /v1/usagereturns records in the order we stored or last corrected them, so a partner that resends the cursor of its last page receives late records and corrections it has not seen. Expect usage from the last 24–48 hours to keep changing as late records arrive and are rated. - All endpoints: the per-key request quota now resets 24 hours after the window opens, as
documented. It previously only reset after a key had been completely idle for 24 hours, so a key
called more often than once a day spent its 1000 requests over its lifetime and was then refused
with
RATE_LIMIT_EXCEEDEDuntil it went quiet for a full day. A key already in that state starts a fresh window on its next request; no change is needed on your side.
2026-09-23
- User plans: the provisioning guide notes that in a market whose operator publishes no API a
change can take a couple of hours rather than minutes. No endpoint behaves differently — poll, and
do not read a long
PROVISIONINGas a failure.
2026-09-21
- Usage: the usage guide gives the typical delay before each market's usage reaches the feed, and says to re-read from the previous day on every poll. The feed was described as near real time; in Belgium and Spain a record typically arrives 13 to 16 hours after the usage.
- User plans: the provisioning guide gives the average time to provision a line in each market, from under a minute to 35 minutes in the United States.
- Port-ins: an execution date in Spain, Sweden or the United Kingdom must be a weekday. This was already enforced, but not documented.
2026-09-18
- Documentation: the reference is split into the Enterprise API and the Partner API, with one page per resource and each operation naming the permission its key needs. Links to the old one-page-per-operation reference redirect to that operation on its resource page.
- All endpoints: the
Idempotency-Keyheader is now declared on every endpoint that changes data, so it appears in the reference, the playground and the OpenAPI description. Its behaviour is unchanged since 2026-09-16. - User plans:
DELETE /v1/user-plans/{userId}/data-restrictionlists each restored number once. A customer whose line draws on several data pools used to see the number repeated once per pool. - Errors: stopping and restoring data now say what went wrong instead of answering with a
generic message: data already stopped (
409), nothing to restore (409), no line in one of your data pools (422), and a number that cannot be stopped yet (400). Each hint names the call that fixes it. The usage feed says the same of a cursor it did not issue (400). - Usage: a window over 31 days, or ending before it starts, is refused as a
VALIDATION_ERRORnamingend_date, instead of a generic400. - Errors: replacing a SIM inside the cooldown now says the number's current SIM is too new to replace. The cooldown counts from when that SIM was issued, so it also applies to a line provisioned moments ago.
- Reference: every endpoint documents the
429returned once an API key has used its request quota. The quota always applied to every endpoint; only three documented it.
2026-09-17
- User plans:
POST /v1/user-plans/{userId}/terminationends a customer's line on a date you name. The line is suspended on that date and deactivated at the network 14 days later, so a202means the termination is booked rather than already carried out. Read it back onGET /v1/user-plans/{userId}, which now carriesterminationScheduledAt— the date the plan is scheduled to end, ornullwhen none is. Billing is not prorated: the customer keeps the period they already bought, andactiveUntilin the response says when it ends. SenddeleteUser: trueto delete the end-user with the line; otherwiseDELETE /v1/users/{userId}succeeds once the line is suspended, without waiting out the 14 days. The endpoint requires theorders:cancelpermission on your key.
2026-09-16
- Port-ins: a port-in that ended in
REJECTEDorFAILEDnow carries afailureobject onGET /v1/port-ins/{portInId}andGET /v1/port-ins/number/{numberId}, giving the reason it ended that way and the field the donor objected to, where one was identified.failure.reasonisnullwhen the cause could not be classified; treat an unrecognised or absent reason as an unexplained failure rather than an error. New reasons will be added over time. - Port-ins: submitting a port-in for a number that already has one in flight now answers
409 CONFLICTwith a message naming the conflict, instead of a generic error. This was always the behaviour; it is now a documented response. Retry once the previous attempt has reached a terminal state: wherefailure.reasonandfailure.fieldidentify a correctable input, fix it before resubmitting; where the reason isnullor one you do not recognise, resubmit as it stands. - User plans:
POST /v1/user-plans/{userId}/data-restrictionstops a customer's mobile data while leaving calls and texts working — for someone who has used up what they paid for — andDELETEon the same path restores it. Both take an optionalreason, kept with the change and visible to our support staff. They requirenumbers:suspendandnumbers:unsuspendon your key. - User plans:
POST /v1/user-plans/{userId}/suspensionstops all traffic on a number, domestic and international — for a phone that has been lost or stolen — andDELETErestores it. Name the number withnumberId, which you get fromGET /v1/user-plans/{userId}. The response reports the numbers the change actually reached at the network. Unlike the data pair these carry noreason: a suspension is a state of the line at the provider with nowhere to record one. Same permissions as above. - All endpoints: requests that change state (
POST,PATCH,DELETE) now accept an optionalIdempotency-Keyheader. Retrying a request with the same key returns the original response instead of creating a duplicate or failing with a conflict, so a dropped connection no longer needs special handling. A key identifies one request to one endpoint and is remembered for 24 hours, replaying the original outcome including a failure — use a new key once you change the request. Keys are private to your API key and organisation. The header is ignored onGET, and a value outside 1–255 printable ASCII characters is rejected with400 BAD_REQUEST. Requests that send no key are unaffected.
2026-09-15
- Data pools:
GET /v1/data-poolslists every active pool in your organization with how much of each is left — bought, consumed and remaining, in GB — and whether any number drawing on it is currently restricted.GET /v1/data-pools/{poolId}/memberslists the users drawing on one pool with the bytes each has taken, so you can see which customer to throttle before the pool runs out. A pool belonging to another organization answers404, not403. Both require thepools:readpermission on your key. - Rates: New
GET /v1/ratesreturns the pay-as-you-go retail price list for a market — international calling, SMS and MMS per destination, or roaming data per visited country. Pass the home-planmarket(ISO 3166-1 alpha-2) and a ratetype; each rate carries the country, the per-unit price as a number to six decimal places, its unit, the currency and when it took effect. Prices are your organization's, with any negotiated rate in place of the list price. Calling and messaging are priced from the market by default — addserving_countryto price that plan while roaming (market=DE&serving_country=DK), and the response echoes theserving_countryit used.ROAMING_DATAprices every visited country at once, so it ignores the parameter and answers withserving_country: null. Calls and messages are priced outbound by default; adddirection=inboundto price being reached instead — chargeable while roaming — and the response echoes thedirectionit used (nullforROAMING_DATA). Prices are those in effect today; addas_of_date(YYYY-MM-DD) to read another day's list, echoed asas_of_date. Every country is listed: one that costs nothing carriesrate_per_unit: 0rather than being left out — whether it is priced free (calls inside the EU/EEA) or has no published pay-as-you-go price, such as a roaming-data country included in the plan. A market no carrier serves answers403 COUNTRY_NOT_SUPPORTED. Requires therates:readscope.
2026-09-14
- Usage:
GET /v1/usagereturns raw voice, SMS and data records as one chronological feed, so you do not have to know that we store calls and data separately. Page withcursorandnext_cursorrather than page numbers — a cursor is stable while new records arrive behind it.page_sizeis at most 100, thestart_date/end_datewindow at most 31 days, and a key may make 1000 requests per 24 hours. Prices arenulluntil a record is rated and on usage covered by a pool. Requires theconsumption:readpermission on your key. - Analytics:
GET /v1/analyticsreturns the same consumption already totalled perinterval(dayormonth), with the highest, lowest and average interval across the window, so a dashboard does not have to add up the raw feed. Narrow it withuser_id,msisdnorcountry_iso_code. Requires theanalytics:readpermission on your key.
2026-09-11
- Users:
firstNameandlastNamemust now be non-blank when you create a user, and when you include them while updating one. A blank or whitespace-only name is rejected with a400 VALIDATION_ERRORthat names the field; leading and trailing whitespace on names is trimmed before the user is stored. Requests that already send real names are unaffected.
v1.0.0
Initial public API release.
- Users: List, create, update, and delete organization users
- Teams: List and create teams for user grouping
- User Plans: List, get, and create telecommunications plans