>_ Analyst Engineering
Labs Mission 01 Structured 60 min

Mission 01: Analyze the Payments API Before Integration Starts

A hands-on lab for technical analysts: read a real OpenAPI contract against requirements, sample responses, and business rules, and find the gaps before a developer writes integration code.

Functional AnalystBusiness AnalystQA Analyst API analysisRequirements gap analysis

Mission card

Your role
You are the technical analyst on a marketplace team about to integrate Northline Pay. The developers start on Monday.
Deliverable
A gap register: every place where the contract, the requirements, the sample responses, and the business rules disagree, with severity and what you would ask the API team.
System
Northline Pay: A fictional payment service provider for marketplaces. Every lab is set inside it.
Self-assessment
8 expected findings in the solution

The situation

Your marketplace is replacing its payment provider with Northline Pay. Product has written the requirements. The API team at Northline has sent the OpenAPI contract, and someone on your team already made a few sandbox calls and saved the responses. The developers start the integration on Monday.

Your job is the one nobody else has time for: read everything, side by side, and find what does not line up before it becomes a defect. Every gap you find now is a question to the API team this week. Every gap you miss is a change request in six weeks.

What you will practice

  • Reading an OpenAPI 3.1 contract for what it says, what it implies, and what it leaves out.
  • Tracing each requirement to the endpoint, field, or error that satisfies it, and naming what is missing.
  • Using sample responses as evidence against the contract, not as decoration.
  • Writing findings a developer and an API team can act on without a meeting.

How the mission works

Four steps. Each step reveals one more artifact and gives you one task. Work through them in order: the point is to notice what changes in your reading when a new artifact arrives. Keep a running list of findings from step one. At the end you compare your list with the solution and mark, honestly, which findings you had.

Keep your findings in a file as you go: the solution at the end is only useful compared with what you actually wrote down.

The steps

  1. 01 Read the contract Inventory what the Merchant API can do from the OpenAPI file alone, and write down what the contract does not tell you.
  2. 02 Trace the requirements Map each of the ten requirements to the contract, and record the ones the contract does not satisfy.
  3. 03 Check the sample responses Compare four real sandbox responses with the schemas in the contract, and treat every difference as evidence.
  4. 04 Apply the business rules and write the gap register Northline's own rules confirm some of your findings and reveal that the contract is missing behavior the provider actually enforces. Write the register.
  5. >_ Solution and self-assessment The practitioner walkthrough and the 8 findings to score yourself against. Requires a free account.
Start step 1

Read first

Platform artifacts

Available from the start. Mission-specific evidence arrives with each step.

Platform artifact openapi.yaml 470 lines download show
openapi: 3.1.0
info:
  title: Northline Pay Merchant API
  version: "1.4.0"
  description: |
    Create payments on behalf of buyers, capture, refund, and list them.
    All amounts are expressed in the payment currency.
    Authenticate with your secret API key as a bearer token.
  contact:
    name: Northline Pay API team
    email: api@northlinepay.example
servers:
  - url: https://api.sandbox.northlinepay.example/v1
    description: Sandbox
  - url: https://api.northlinepay.example/v1
    description: Production

security:
  - apiKey: []

paths:
  /payments:
    post:
      operationId: createPayment
      summary: Create a payment
      description: |
        Creates a payment for a buyer. With `capture_method: automatic` the
        payment is authorized and captured in one call. With `manual`, call
        the capture endpoint later.
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePaymentRequest"
      responses:
        "201":
          description: Payment created
          headers:
            Northline-Request-Id:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Payment"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          description: The payment method was declined
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    get:
      operationId: listPayments
      summary: List payments
      description: Returns the merchant's payments, most recent first.
      parameters:
        - name: limit
          in: query
          description: Number of payments to return.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: status
          in: query
          schema:
            $ref: "#/components/schemas/PaymentStatus"
      responses:
        "200":
          description: A page of payments
          content:
            application/json:
              schema:
                type: object
                required: [object, data, has_more]
                properties:
                  object:
                    type: string
                    const: list
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Payment"
                  has_more:
                    type: boolean
        "401":
          $ref: "#/components/responses/Unauthorized"

  /payments/{payment_id}:
    get:
      operationId: getPayment
      summary: Retrieve a payment
      parameters:
        - $ref: "#/components/parameters/PaymentId"
      responses:
        "200":
          description: The payment
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Payment"
        "404":
          $ref: "#/components/responses/NotFound"

  /payments/{payment_id}/capture:
    post:
      operationId: capturePayment
      summary: Capture an authorized payment
      parameters:
        - $ref: "#/components/parameters/PaymentId"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                amount:
                  type: number
                  description: Amount to capture. Defaults to the authorized amount.
      responses:
        "200":
          description: The captured payment
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Payment"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: The payment is not in a capturable state
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /refunds:
    post:
      operationId: createRefund
      summary: Refund a captured payment
      description: Refunds all or part of a captured payment.
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [payment_id]
              properties:
                payment_id:
                  type: string
                amount:
                  type: number
                  description: Amount to refund. Defaults to the full captured amount.
                reason:
                  type: string
                  enum: [requested_by_customer, duplicate, fraudulent]
      responses:
        "201":
          description: Refund created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Refund"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"

  /refunds/{refund_id}:
    get:
      operationId: getRefund
      summary: Retrieve a refund
      parameters:
        - name: refund_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: The refund
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Refund"
        "404":
          $ref: "#/components/responses/NotFound"

webhooks:
  payment.authorized:
    post:
      summary: A payment was authorized
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookEvent"
      responses:
        "200":
          description: Acknowledge with any 2xx status
  payment.captured:
    post:
      summary: A payment was captured
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookEvent"
      responses:
        "200":
          description: Acknowledge with any 2xx status
  payment.failed:
    post:
      summary: A payment failed or was declined
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookEvent"
      responses:
        "200":
          description: Acknowledge with any 2xx status
  refund.succeeded:
    post:
      summary: A refund was completed
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookEvent"
      responses:
        "200":
          description: Acknowledge with any 2xx status

components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: Your secret key, `sk_test_...` in sandbox and `sk_live_...` in production.

  parameters:
    PaymentId:
      name: payment_id
      in: path
      required: true
      schema:
        type: string
        pattern: "^pay_[A-Za-z0-9]{16}$"
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: A unique key you generate to safely retry the request.
      schema:
        type: string
        maxLength: 64

  headers:
    RequestId:
      description: Identifier of this request, for support tickets.
      schema:
        type: string

  responses:
    BadRequest:
      description: The request was invalid
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Unauthorized:
      description: The API key is missing or invalid
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    NotFound:
      description: No such resource for this merchant
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"

  schemas:
    PaymentStatus:
      type: string
      enum: [pending, authorized, captured, failed, canceled]

    CreatePaymentRequest:
      type: object
      required: [amount, currency, payment_method]
      properties:
        amount:
          type: number
          description: The payment amount.
        currency:
          type: string
          description: Three-letter currency code.
        capture_method:
          type: string
          enum: [automatic, manual]
          default: automatic
        payment_method:
          $ref: "#/components/schemas/PaymentMethodInput"
        customer:
          type: object
          properties:
            email:
              type: string
              format: email
        description:
          type: string
          maxLength: 200
        split:
          type: array
          description: How the amount is distributed across seller accounts.
          items:
            type: object
            required: [account_id, amount]
            properties:
              account_id:
                type: string
              amount:
                type: number
        metadata:
          type: object
          additionalProperties:
            type: string

    PaymentMethodInput:
      type: object
      required: [type, token]
      properties:
        type:
          type: string
          enum: [card, sepa_debit, interac]
        token:
          type: string
          description: A payment method token created with Northline.js.

    Payment:
      type: object
      required: [id, object, status, amount, currency, capture_method, payment_method, created_at]
      properties:
        id:
          type: string
        object:
          type: string
          const: payment
        status:
          $ref: "#/components/schemas/PaymentStatus"
        amount:
          type: number
        currency:
          type: string
        capture_method:
          type: string
          enum: [automatic, manual]
        payment_method:
          type: object
          properties:
            type:
              type: string
            card:
              type: object
              properties:
                brand:
                  type: string
                last4:
                  type: string
                exp_month:
                  type: integer
                exp_year:
                  type: integer
        customer:
          type: object
          properties:
            email:
              type: string
        split:
          type: array
          items:
            type: object
            properties:
              account_id:
                type: string
              amount:
                type: number
        failure_code:
          type: string
        failure_message:
          type: string
        metadata:
          type: object
          additionalProperties:
            type: string
        created_at:
          type: string
          format: date-time

    Refund:
      type: object
      required: [id, object, payment_id, amount, currency, status, created_at]
      properties:
        id:
          type: string
        object:
          type: string
          const: refund
        payment_id:
          type: string
        amount:
          type: number
        currency:
          type: string
        status:
          type: string
          enum: [pending, succeeded, failed]
        reason:
          type: string
        created_at:
          type: string
          format: date-time

    WebhookEvent:
      type: object
      required: [id, type, created_at, data]
      properties:
        id:
          type: string
        type:
          type: string
        created_at:
          type: string
          format: date-time
        data:
          type: object
          description: The payment or refund resource, as returned by the API.

    Error:
      type: object
      required: [type, code, message]
      properties:
        type:
          type: string
          enum: [invalid_request_error, authentication_error, card_error, api_error]
        code:
          type: string
          description: |
            One of: invalid_request, authentication_failed, payment_not_found,
            payment_not_capturable, card_declined, rate_limited.
        message:
          type: string
        param:
          type: string
          description: The request field the error refers to, when applicable.

Next mission: Validate an event flow

Free account

Save your progress on the Labs

A free account, no password: an email link signs you in. Your steps and your self-assessment are saved, your missions show on a dashboard, and the solutions unlock.

Your email is used to sign you in. Nothing else, unless you ask. Privacy.