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.
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
- 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.
- 02 Trace the requirements Map each of the ten requirements to the contract, and record the ones the contract does not satisfy.
- 03 Check the sample responses Compare four real sandbox responses with the schemas in the contract, and treat every difference as evidence.
- 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.
- >_ Solution and self-assessment The practitioner walkthrough and the 8 findings to score yourself against. Requires a free account.
Read first
- How to Analyze an API: The Analyst's Method Before Anyone Writes Integration Code A method for analyzing an API before integration: capability mapping, field-level data mapping, failure behavior, limits, versioning, and a fit-gap worksheet.
- The First Time I Read an OpenAPI Contract Field notes on going from intimidated to fluent with OpenAPI: what confused me, what clicked, and how reading the contract myself changed how I work as an analyst.
- How to Write API Requirements That Developers Can Actually Build Write API requirements the right way: endpoint, method, request and response schema, status codes, error contracts, and testable acceptance criteria. With examples.
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.