openapi: 3.1.0
info:
  title: VIP Post API
  version: 2.0.0
  summary: API for creating, retrieving, printing and deleting consignments.
  description: |
    VIP Post API V2 enables authenticated clients to create single or bulk consignments,
    retrieve consignment information, generate a Base64-encoded PDF waybill and delete a consignment.

    ## Response convention
    The API currently returns HTTP 200 for both successful and application-level error responses.
    Clients MUST inspect the `error` field in the JSON response:
    - `0` means success.
    - `1` means an application-level error.

    ## Character encoding
    Request data should be sent as UTF-8.

    ## Identifier behavior
    `identifier` is the client-provided order/reference number. Repeating a previously inserted
    identifier for the same active user normally returns the existing `consignment_id` instead
    of creating a new record.

servers:
  - url: https://vipposta.mk:3030/v2
    description: Test environment
  - url: https://vipposta.mk:6060/v2
    description: Production environment

tags:
  - name: Consignments
    description: Create and manage consignments.
  - name: Documents
    description: Generate shipment documents.

security:
  - bearerAuth: []

paths:
  /consignment/create:
    post:
      tags: [Consignments]
      operationId: createConsignment
      summary: Create a consignment
      description: |
        Creates one consignment.

        When the same `identifier` has already been inserted for the active user, the API normally
        returns the existing `consignment_id` with the message
        `The data has already been inserted.`

        The endpoint accepts `multipart/form-data`.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/ConsignmentCreateRequest'
            encoding:
              type:
                style: form
              price:
                style: form
            examples:
              standard:
                summary: Standard parcel
                value:
                  name: Name/Company
                  phone: "071123456"
                  city: Skopje
                  street: "Улица 123 бр.23"
                  note: "забелешка"
                  payer: "И-Ф"
                  type: 7
                  price: "0"
                  identifier: VIP3000350000012392MK
                  identifier2: ""
                  user_id: "183"
      responses:
        '200':
          description: Success or application-level error. Inspect the `error` field.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/CreateSuccessResponse'
                  - $ref: '#/components/schemas/CreateDuplicateResponse'
                  - $ref: '#/components/schemas/ErrorResponse'
              examples:
                created:
                  value:
                    error: 0
                    message: Data Inserted Successfully.
                    consignment_id: "103079"
                duplicate:
                  value:
                    error: 0
                    message: The data has already been inserted.
                    consignment_id: "103079"
                invalidName:
                  value:
                    error: 1
                    message: Invalid Name!!
                invalidPhone:
                  value:
                    error: 1
                    message: Invalid Phone!!
                invalidPayer:
                  value:
                    error: 1
                    message: Invalid Payer!!
                invalidType:
                  value:
                    error: 1
                    message: Invalid Type!!
                invalidPrice:
                  value:
                    error: 1
                    message: Invalid Price!!
                invalidUser:
                  value:
                    error: 1
                    message: Invalid user_id!!
        default:
          $ref: '#/components/responses/UnexpectedResponse'

  /consignment/bulkcreate:
    post:
      tags: [Consignments]
      operationId: bulkCreateConsignments
      summary: Create multiple consignments
      description: |
        Creates multiple consignments in one JSON request.

        The response contains one result object per submitted item. New and duplicate identifiers
        may be mixed in the same request and are processed independently.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              minItems: 1
              items:
                $ref: '#/components/schemas/ConsignmentCreateRequest'
            examples:
              standard:
                value:
                  - name: Velice Kumanovka
                    phone: "075924384"
                    city: Kumanovo
                    street: Braniteli 12
                    note: "-"
                    payer: "И-Ф"
                    type: 7
                    price: "1500"
                    identifier: VIP3000350000012393MK
                    user_id: "183"
                  - name: Velice Ohrid
                    phone: "075924384"
                    city: Ohrid
                    street: Makedonski 12
                    note: "-"
                    payer: "И-Ф"
                    type: 7
                    price: "1300"
                    identifier: VIP3000350000012394MK
                    user_id: "183"
      responses:
        '200':
          description: |
            Per-item results, or a general error object when the request body cannot be parsed.
          content:
            application/json:
              schema:
                oneOf:
                  - type: array
                    items:
                      $ref: '#/components/schemas/BulkItemResponse'
                  - $ref: '#/components/schemas/ErrorResponse'
              examples:
                created:
                  value:
                    - error: 0
                      message: Data Inserted Successfully.
                      consignment_id: "103080"
                      identifier: VIP3000350000012393MK
                    - error: 0
                      message: Data Inserted Successfully.
                      consignment_id: "103081"
                      identifier: VIP3000350000012394MK
                invalidJson:
                  value:
                    error: 1
                    message: Syntax error
        default:
          $ref: '#/components/responses/UnexpectedResponse'

  /consignment/id/{user_id}/{consignment_id}:
    get:
      tags: [Consignments]
      operationId: getConsignmentById
      summary: Get a consignment by ID
      parameters:
        - $ref: '#/components/parameters/UserId'
        - $ref: '#/components/parameters/ConsignmentId'
      responses:
        '200':
          description: Matching consignment or application-level error.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/ConsignmentResponse'
                  - $ref: '#/components/schemas/ErrorResponse'
              examples:
                found:
                  value:
                    error: 0
                    consignment:
                      name: API Test Recipient
                      phone: "071123456"
                      city: "Скопје"
                      street: API Test Address 1
                      note: Automated integration test.
                      type: "7"
                      payer: "И-Ф"
                      price: "0"
                      delivery_price: "90"
                      status: not picked up
                      created_at: "2026-07-21 00:09:21"
                      consignment_id: "103079"
                      identifier: API-SINGLE-EXAMPLE
                notFound:
                  value:
                    error: 1
                    message: Consignment does not exist!
        default:
          $ref: '#/components/responses/UnexpectedResponse'

  /consignment/identifier/{user_id}/{identifier}:
    get:
      tags: [Consignments]
      operationId: getConsignmentByIdentifier
      summary: Get a consignment by client identifier
      parameters:
        - $ref: '#/components/parameters/UserId'
        - name: identifier
          in: path
          required: true
          description: Client-provided order/reference identifier.
          schema:
            type: string
          example: VIP3000350000012393MK
      responses:
        '200':
          description: Matching consignment or application-level error.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/ConsignmentResponse'
                  - $ref: '#/components/schemas/ErrorResponse'
        default:
          $ref: '#/components/responses/UnexpectedResponse'

  /consignment/waybill/{user_id}/{consignment_id}:
    get:
      tags: [Documents]
      operationId: getConsignmentWaybill
      summary: Get the waybill as a Base64 PDF
      description: |
        Returns a PDF waybill encoded as Base64 in the `waybill` property.
        Decode the Base64 value and save the resulting bytes as a `.pdf` file.
      parameters:
        - $ref: '#/components/parameters/UserId'
        - $ref: '#/components/parameters/ConsignmentId'
      responses:
        '200':
          description: Base64 PDF waybill or application-level error.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/WaybillResponse'
                  - $ref: '#/components/schemas/ErrorResponse'
              examples:
                success:
                  value:
                    error: 0
                    waybill: JVBERi0xLjQK...
        default:
          $ref: '#/components/responses/UnexpectedResponse'

  /consignment/delete/{user_id}/{consignment_id}:
    get:
      tags: [Consignments]
      operationId: deleteConsignment
      summary: Delete a consignment
      description: |
        Deletes the selected consignment.

        This API currently exposes deletion as an HTTP GET operation. Clients should avoid
        prefetching or automatically following this URL because it changes server state.
      parameters:
        - $ref: '#/components/parameters/UserId'
        - $ref: '#/components/parameters/ConsignmentId'
      responses:
        '200':
          description: Deletion result or application-level error.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/DeleteSuccessResponse'
                  - $ref: '#/components/schemas/ErrorResponse'
              examples:
                success:
                  value:
                    error: 0
                    message: Consignment Deleted Successfully
        default:
          $ref: '#/components/responses/UnexpectedResponse'

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: Bearer token supplied by VIP Post.

  parameters:
    UserId:
      name: user_id
      in: path
      required: true
      description: VIP Post user identifier.
      schema:
        type: string
      example: "183"
    ConsignmentId:
      name: consignment_id
      in: path
      required: true
      description: VIP Post consignment identifier returned after creation.
      schema:
        type: string
      example: "103079"

  responses:
    UnexpectedResponse:
      description: Unexpected transport-level or server response.

  schemas:
    ConsignmentCreateRequest:
      type: object
      additionalProperties: false
      required:
        - name
        - phone
        - city
        - street
        - payer
        - type
        - price
        - identifier
        - user_id
      properties:
        name:
          type: string
          minLength: 1
          description: Recipient name or company name.
          example: Name/Company
        phone:
          type: string
          minLength: 1
          description: Recipient phone number.
          example: "071123456"
        city:
          type: string
          minLength: 1
          description: |
            Recipient city. This field is required, but no universal city-value validation
            is documented. Processing may depend on the configuration of the authenticated user.
          example: Skopje
        street:
          type: string
          minLength: 1
          description: Street, house number and other address details.
          example: "Улица 123 бр.23"
        note:
          type: string
          description: Optional delivery note.
          default: ""
          example: "забелешка"
        payer:
          type: string
          enum: ["И-Ф", "И-Г", "П-Г", "П-Ф"]
          description: |
            Payment arrangement. Confirmed examples include:
            - `П-Г`: payment on delivery.
            - `И-Ф`: payment by invoice/card arrangement; invoicing is handled periodically.
          example: "И-Ф"
        type:
          oneOf:
            - type: integer
            - type: string
              pattern: '^[0-9]+$'
          description: |
            Shipment type:
            1. Обично писмо
            2. Препорачано писмо
            3. Писмо лично на рака
            4. Писмо Експрес
            5. Писмено Нотар лично на рака
            6. Извршител лично на рака
            7. 0–5 kg
            8. 5–10 kg
            9. 10–20 kg
            10. 20–30 kg
            11. Пакет Експрес
          example: 7
        price:
          oneOf:
            - type: string
              pattern: '^[0-9]+(?:\.[0-9]+)?$'
            - type: number
              minimum: 0
          description: Cash-on-delivery / redemption amount in MKD.
          example: "1500"
        identifier:
          type: string
          minLength: 1
          description: |
            Client order/reference identifier (for example NPN, IBR or order number).
            Use a stable unique value per order.
          example: VIP3000350000012392MK
        identifier2:
          type: string
          description: Optional secondary client identifier.
          default: ""
          example: "1001695159"
        user_id:
          type: string
          minLength: 1
          description: VIP Post user identifier assigned to the integration.
          example: "183"

    CreateSuccessResponse:
      type: object
      required: [error, message, consignment_id]
      properties:
        error:
          type: integer
          const: 0
        message:
          type: string
          const: Data Inserted Successfully.
        consignment_id:
          type: string

    CreateDuplicateResponse:
      type: object
      required: [error, message, consignment_id]
      properties:
        error:
          type: integer
          const: 0
        message:
          type: string
          const: The data has already been inserted.
        consignment_id:
          type: string

    BulkItemResponse:
      type: object
      required: [error, message, consignment_id, identifier]
      properties:
        error:
          type: integer
          enum: [0, 1]
        message:
          type: string
        consignment_id:
          type: string
        identifier:
          type: string

    ErrorResponse:
      type: object
      required: [error, message]
      properties:
        error:
          type: integer
          const: 1
        message:
          type: string
          examples:
            - Token is not valid
            - Invalid Name!!
            - Invalid Phone!!
            - Invalid Payer!!
            - Invalid Type!!
            - Invalid Price!!
            - Invalid user_id!!
            - Syntax error
            - Consignment does not exist!

    ConsignmentResponse:
      type: object
      required: [error, consignment]
      properties:
        error:
          type: integer
          const: 0
        consignment:
          $ref: '#/components/schemas/Consignment'

    Consignment:
      type: object
      required:
        - name
        - phone
        - city
        - street
        - note
        - type
        - payer
        - price
        - delivery_price
        - status
        - created_at
        - consignment_id
        - identifier
      properties:
        name:
          type: string
        phone:
          type: string
        city:
          type: string
        street:
          type: string
        note:
          type: string
        type:
          type: string
        payer:
          type: string
        price:
          type: string
        delivery_price:
          type: string
        status:
          type: string
          enum:
            - not picked up
            - in transit
            - delivered
            - canceled
            - deleted
        created_at:
          type: string
          description: Server-local datetime in `YYYY-MM-DD HH:mm:ss` format.
          pattern: '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$'
        consignment_id:
          type: string
        identifier:
          type: string

    WaybillResponse:
      type: object
      required: [error, waybill]
      properties:
        error:
          type: integer
          const: 0
        waybill:
          type: string
          contentEncoding: base64
          contentMediaType: application/pdf
          description: Base64-encoded PDF document.

    DeleteSuccessResponse:
      type: object
      required: [error, message]
      properties:
        error:
          type: integer
          const: 0
        message:
          type: string
          const: Consignment Deleted Successfully
