> ## Documentation Index
> Fetch the complete documentation index at: https://docs.spenza.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Submit a port-in

> **The only port-in submission endpoint** — there is no `POST /api/v1.1/port-ins`.
Move an existing number from another carrier into Spenza. Always
asynchronous.

**Two contract exceptions worth calling out:**
1. **Requires an account role**, not just a valid bearer token —
   `Admin`, `Super Admin`, or `Standard Admin`.
2. **Response is not on the standard envelope.** Success is a flat
   `{ success, transactionId, statusEndpoint, message, timestamp }` —
   no `data` wrapper — and `statusEndpoint` points at the **singular**
   legacy path `/api/v3/transaction/{id}/status` (not the plural
   `/api/v3/transactions/{id}` used everywhere else — both resolve, but
   this is the literal value returned). A synchronous validation error
   is `{ success: false, error: "<plain string>", timestamp }` — note
   `error` is a bare string here, not an `{code, message}` object.

Identify the target line via `plan`, or via `Operator` + `network`
directly. Idempotency is supported via an `Idempotency-Key` header
(handled inline, not the standard interceptor) — a replay returns the
same shape with `message: "Port-in already initiated (idempotent replay)"`.




## OpenAPI

````yaml /openapi.yaml post /api/v3/port-in
openapi: 3.1.0
info:
  title: Spenza Partner API
  version: 3.0.0
  summary: >-
    Partner API for SIMs, eSIMs, plans, billing, users, groups and numbers —
    v1.1 surface.
  description: >
    The **Spenza Partner API** lets you manage SIMs, eSIMs, plans,
    subscriptions,

    billing, users, phone numbers, teams and device/SIM/user groups
    programmatically.


    All endpoints follow one consistent standard:


    - **Auth** — exchange your API `key` + `secret` for a bearer token, then
    send
      `Authorization: Bearer <token>` on every request.
    - **Envelopes** — success responses are `{ success, data, meta? }`; errors
    are
      `{ success: false, error: { code, message, details? } }`.
    - **Pagination** — list endpoints accept `page` (1-indexed) and `pageSize`
      (max 100); totals are returned in `meta`.
    - **Idempotency** — many write endpoints honor an optional `Idempotency-Key`
      request header so a retried call replays the original result instead of
      repeating the side effect.
    - **Async operations** — a handful of endpoints (eSIM provisioning, port-in,
      the `/api/v3/...` purchase variants, top-up) return `202` (`201` for
      top-up) with a `transactionId` + `statusEndpoint` instead of the finished
      resource. Poll
      `GET /api/v3/transactions/{transactionId}` (no auth required) until the
      transaction reaches a terminal status.

    One resource — **port-in submission** — is a documented exception: it lives
    at

    `POST /api/v3/port-in`, requires an Admin-tier account role rather than just
    a

    valid bearer token, and is **not** on the standard envelope (see that
    operation's

    description for its exact response shape).
  contact:
    name: Spenza API Support
    email: support@spenza.com
    url: https://spenza.com
  license:
    name: Proprietary
    url: https://spenza.com/terms
  x-logo:
    url: https://spenza.com/logo.png
    altText: Spenza
servers:
  - url: https://api.spenza.com
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Authentication
    description: Exchange API credentials for a bearer token.
  - name: SIMs
    description: List, inspect, assign and manage SIMs.
  - name: Subscriptions
    description: Plan subscriptions attached to SIMs.
  - name: Plans & Catalog
    description: Purchasable plans and SIM products.
  - name: eSIM
    description: Provision eSIMs and fetch activation QR / install status.
  - name: Billing
    description: Invoices, credit balance and top-ups.
  - name: Users
    description: End users (employees) on your account that SIMs/devices are assigned to.
  - name: Orders & Transactions
    description: Order history and financial transactions.
  - name: Numbers & Port-in
    description: Check port-in eligibility and port existing numbers into Spenza.
  - name: Messaging
    description: Send outbound SMS from a provisioned number.
  - name: Webhooks
    description: Register endpoints for operator SMS/voice events and inspect deliveries.
  - name: Notifications
    description: In-account notifications and delivery preferences.
  - name: Device Groups
    description: Organize devices into named groups with spend/data limits.
  - name: SIM Groups
    description: Organize SIMs into named groups with spend/data limits.
  - name: User Groups
    description: Organize users (departments) into named groups with spend/data limits.
  - name: Devices
    description: The device catalog (phones/tablets) and their assignment to users.
  - name: Team Members
    description: Admins who manage your Spenza account (distinct from Users/end-users).
  - name: Async Status
    description: Universal status poll for any asynchronous (v3) operation.
paths:
  /api/v3/port-in:
    post:
      tags:
        - Numbers & Port-in
      summary: Submit a port-in
      description: >
        **The only port-in submission endpoint** — there is no `POST
        /api/v1.1/port-ins`.

        Move an existing number from another carrier into Spenza. Always

        asynchronous.


        **Two contract exceptions worth calling out:**

        1. **Requires an account role**, not just a valid bearer token —
           `Admin`, `Super Admin`, or `Standard Admin`.
        2. **Response is not on the standard envelope.** Success is a flat
           `{ success, transactionId, statusEndpoint, message, timestamp }` —
           no `data` wrapper — and `statusEndpoint` points at the **singular**
           legacy path `/api/v3/transaction/{id}/status` (not the plural
           `/api/v3/transactions/{id}` used everywhere else — both resolve, but
           this is the literal value returned). A synchronous validation error
           is `{ success: false, error: "<plain string>", timestamp }` — note
           `error` is a bare string here, not an `{code, message}` object.

        Identify the target line via `plan`, or via `Operator` + `network`

        directly. Idempotency is supported via an `Idempotency-Key` header

        (handled inline, not the standard interceptor) — a replay returns the

        same shape with `message: "Port-in already initiated (idempotent
        replay)"`.
      operationId: createPortIn
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - portInNumber
                - currentCarrierName
                - currentAccountNumber
                - currentAccountPassword
                - currentBillingAddress
                - employeeEmail
                - employeeName
              properties:
                plan:
                  type: string
                  description: >-
                    Plan/product name; operator+network derived from it.
                    Required when Operator/network are absent.
                Operator:
                  type: string
                  description: Required when plan is absent.
                network:
                  type: string
                  description: Required when plan is absent.
                portInNumber:
                  type: string
                  description: 10-15 digits, optional leading +, cannot be all zeros.
                  example: '+15555551234'
                currentCarrierName:
                  type: string
                currentAccountNumber:
                  type: string
                currentAccountPassword:
                  type: string
                  description: >-
                    Losing carrier's account PIN (FCC LNP) — treat like a
                    credential, never log it.
                currentBillingAddress:
                  type: object
                  required:
                    - street1
                    - city
                    - state
                    - zip
                  properties:
                    street1:
                      type: string
                    street2:
                      type: string
                    city:
                      type: string
                    state:
                      type: string
                      description: US state, 2-letter or full name.
                    zip:
                      type: string
                iccid:
                  type: string
                  description: 19-20 digits.
                  example: '8901260853182965429'
                targetPortinClassification:
                  type: string
                  enum:
                    - mvno
                    - iot
                subscriberName:
                  type: string
                  description: Defaults to employeeName if omitted.
                employeeEmail:
                  type: string
                  format: email
                  description: Auto-creates the user on this account if no match exists.
                employeeName:
                  type: string
                  description: Used when auto-creating the user.
                metaData:
                  type: object
                  description: Free-form partner metadata for correlation.
      responses:
        '200':
          description: Port-in initiated (note the flat, non-enveloped shape).
          content:
            application/json:
              example:
                success: true
                transactionId: v3_1715567890123_abc123def456
                statusEndpoint: /api/v3/transaction/v3_1715567890123_abc123def456/status
                message: Port-in initiated
                timestamp: '2026-07-31T09:00:00.000Z'
        '400':
          description: >-
            Plan not found, or plan has no operator/network configured. `error`
            is a plain string, not an object.
          content:
            application/json:
              example:
                success: false
                error: Plan 'AT0001' not found
                timestamp: '2026-07-31T09:00:00.000Z'
        '500':
          description: Transaction could not be initiated.
          content:
            application/json:
              example:
                success: false
                error: Failed to initiate transaction
      security: []
components:
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: >
        Any string unique to this logical request. On a retry with the same key

        and the same request body, the original response is replayed verbatim

        (with an `Idempotency-Replayed: true` response header) instead of the

        action repeating. Reusing the key with a **different** body or on a

        different route returns `409 IDEMPOTENCY_KEY_REUSED`. A replay attempted

        while the original call is still in flight returns `409 CONFLICT`.

        Optional — omit it and the endpoint behaves exactly as it would
        otherwise.
      schema:
        type: string
        example: a1b2c3d4-idem-key-001
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Bearer token obtained from `POST /api/v1.1/auth/token`.

````