{"templateId":"../@theme/templates/BlogPost","sharedDataIds":{"blog-latest-posts":"blog-latest-posts"},"props":{"metadata":{"markdoc":{"tagList":["admonition","cards","card","tabs","tab"]},"redocly_category":"Blog","type":"markdown","template":"../@theme/templates/BlogPost","title":"API contract testing from OpenAPI using Arazzo","description":"Your OpenAPI spec is a contract. Learn how to enforce it automatically with API contract testing using the Arazzo standard and Redocly's Respect CLI.","seo":{"title":"API contract testing from OpenAPI using Arazzo","description":"Your OpenAPI spec is a contract. Learn how to enforce it automatically with API contract testing using the Arazzo standard and Redocly's Respect CLI.","image":"./images/api-contract-testing-arazzo.png"},"author":{"id":"roman-marshevskyi","name":"Roman Marshevskyi","authorBIO":"Director of Engineering, Redocly","image":"/assets/marshevskyi.642f924e188912716ccc91863cae9d6a4979994d1c5341d88c0bddaeffef614b.978384e4.png"},"publishedDate":"2026-04-22","categories":[{"category":{"id":"api-governance","label":"API governance"},"subcategory":{"id":"compliance-quality","label":"Compliance & quality"}},{"category":{"id":"redocly","label":"Redocly"},"subcategory":{"id":"product-updates","label":"Product updates"}}],"image":"/assets/api-contract-testing-arazzo.edf0e7fb5145914f9b6ff425b13b55e0257b4cdf311737b2f65e9c71b98d054e.978384e4.png","slug":"/blog/api-contract-testing-arazzo"},"seo":{"title":"API contract testing from OpenAPI using Arazzo","description":"Your OpenAPI spec is a contract. Learn how to enforce it automatically with API contract testing using the Arazzo standard and Redocly's Respect CLI.","siteUrl":"https://redocly.com","image":"/assets/api-contract-testing-arazzo.edf0e7fb5145914f9b6ff425b13b55e0257b4cdf311737b2f65e9c71b98d054e.978384e4.png","lang":"en-US","llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A developer ships a small backend change."," ","The API still returns ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["200 OK"]},"."," ","Integration tests pass."," ","Monitoring dashboards stay green."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Two days later, support tickets start pouring in."," ","The mobile app is crashing on a screen that nobody changed."," ","Turns out the API dropped a required field from the response--",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["totalPrice"]},", gone."," ","The response was still valid JSON."," ","Still ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["200"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Nobody's tests checked whether the API kept its promise."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["That's the problem API contract testing solves."," ","And with ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/learn/arazzo/what-is-arazzo"},"children":["Arazzo"]}," and ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/respect-cli"},"children":["Respect"]},", you can set it up in two commands."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"why-your-current-tests-miss-this","__idx":0},"children":["Why your current tests miss this"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["You probably already test your API."," ","Unit tests, integration tests, maybe end-to-end flows."," ","Here's why contract drift still gets through:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Unit tests"]}," verify isolated functions. They don't send HTTP requests to your live API."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Integration tests"]}," check whether components work together. They don't validate the response body against your OpenAPI schema."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["E2E tests"]}," confirm user flows work. They don't notice when a new field leaks internal data or a required field disappears."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Uptime monitoring"]}," confirms the API responds. It doesn't care what's in the response."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["None of these are designed to enforce your API contract."," ","All of them can pass while your API silently breaks it."]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Testing type"},"children":["Testing type"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"What it answers"},"children":["What it answers"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Catches contract drift?"},"children":["Catches contract drift?"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Unit tests"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Does this function work in isolation?"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["No"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Integration tests"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Do components work together?"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Sometimes, indirectly"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["E2E tests"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Does the user flow complete?"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Sometimes, indirectly"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Uptime monitoring"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Is the API responding?"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["No"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Contract testing"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Does the response match the OpenAPI spec?"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Yes, by definition"]}]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Contract testing is the missing layer."," ","It treats your OpenAPI description as the source of truth and validates every live response against it--status codes, content types, schemas, headers."," ","Not \"does it work?\" but \"does it match the contract?\""]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"what-respect-does-about-it","__idx":1},"children":["What Respect does about it"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/respect-cli"},"children":["Respect"]}," is an open-source CLI that removes an entire class of tests you currently maintain."," ","It's part of ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://github.com/Redocly/redocly-cli"},"children":["Redocly CLI"]},"--the same toolchain you may already use for linting and bundling OpenAPI."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["You already wrote these tests."," ","You just didn't realize your spec could replace them."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["No assertions to write."," ","No test scripts to keep in sync."," ","No spec duplicated in a test framework."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Your OpenAPI description already defines what the API should return."," ","Respect reads it, sends real HTTP requests to your live API, and validates every response against the spec."," ","The spec ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["is"]}," the test."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","data-title":"Two commands. Zero test code.","header":{"title":"Two commands. Zero test code.","controls":{"copy":{}}},"source":"# Generate test workflows from your OpenAPI spec\nnpx @redocly/cli generate-arazzo openapi.yaml\n\n# Run contract tests against your live API\nnpx @redocly/cli respect auto-generated.arazzo.yaml --verbose\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["That's it."," ","No test framework to configure."," ","No collection files to export."," ","No assertion libraries."," ","Just your spec, your running API, and the truth about whether they agree."]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info","name":"Works with the API you have"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Respect doesn't require a perfect OpenAPI description."," ","Start with what you have--even a partially documented spec catches real issues."," ","You can iteratively improve your spec as Respect reveals gaps."]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"what-arazzo-adds-multi-step-workflows-without-glue-code","__idx":2},"children":["What Arazzo adds: multi-step workflows without glue code"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["OpenAPI describes individual endpoints."," ","But real API usage is never a single call."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["You authenticate, create a resource, read it back, update it, verify the update."," ","Before Arazzo, testing these flows meant writing custom glue code: scripts that chain requests, extract tokens, pass IDs between steps, and hope nothing breaks when the API changes."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://spec.openapis.org/arazzo/v1.0.1/arazzo-specification-v1.0.1.html"},"children":["Arazzo Specification"]},"--an open standard from the OpenAPI Initiative--replaces all of that with pure data flow."," ","No custom code."," ","Outputs from one step feed directly into the next, declared in YAML:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"yaml","data-title":"cafe-order-flow.arazzo.yaml","header":{"title":"cafe-order-flow.arazzo.yaml","controls":{"copy":{}}},"source":"arazzo: \"1.0.1\"\ninfo:\n  title: Redocly Cafe order flow\n  version: 1.0.0\nsourceDescriptions:\n  - name: cafeApi\n    type: openapi\n    url: openapi.yaml\nworkflows:\n  - workflowId: createMenuItemAndOrder\n    steps:\n      - stepId: createMenuItem\n        operationId: cafeApi.createMenuItem\n        requestBody:\n          payload:\n            name: \"Espresso\"\n            price: 350\n            category: \"beverage\"\n        successCriteria:\n          - condition: $statusCode == 201\n        outputs:\n          menuItemId: $response.body#/id\n\n      - stepId: placeOrder\n        operationId: cafeApi.createOrder\n        requestBody:\n          payload:\n            customerName: \"Mary Ann\"\n            orderItems:\n              - menuItemId: $steps.createMenuItem.outputs.menuItemId\n                quantity: 2\n        successCriteria:\n          - condition: $statusCode == 201\n        outputs:\n          orderId: $response.body#/id\n\n      - stepId: verifyOrder\n        operationId: cafeApi.getOrderById\n        parameters:\n          - name: orderId\n            in: path\n            value: $steps.placeOrder.outputs.orderId\n        successCriteria:\n          - condition: $statusCode == 200\n","lang":"yaml"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["See ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["$steps.createMenuItem.outputs.menuItemId"]},"?"," ","The menu item ID from step one flows into step two automatically, then the order ID flows into step three."," ","This replaces the glue scripts, the variable chaining, the brittle test harnesses."," ","One YAML file. In your repo. Versioned with your API."]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"info","name":"Arazzo is an open standard"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Arazzo is maintained by the OpenAPI Initiative--the same organization behind OpenAPI itself."," ","Your workflow definitions are portable and vendor-neutral."," ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/learn/arazzo/what-is-arazzo"},"children":["Learn more about Arazzo"]},"."]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"what-gets-caught","__idx":3},"children":["What gets caught"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When Respect runs your workflows, it validates every response against the linked OpenAPI description."," ","Here's what that looks like in practice."]},{"$$mdtype":"Tag","name":"Cards","attributes":{"columns":2,"cardMinWidth":240},"children":[{"$$mdtype":"Tag","name":"Card","attributes":{"title":"Status codes","imagePosition":"start","iconPosition":"auto","layout":"vertical","align":"start","variant":"filled"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Your spec says ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["201 Created"]},", but the API returns ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["200 OK"]}," after a refactor."," ","Functionally fine."," ","Contractually broken."," ","Every consumer expecting ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["201"]}," now has a bug."," ","Respect catches it."]}]},{"$$mdtype":"Tag","name":"Card","attributes":{"title":"Content types","imagePosition":"start","iconPosition":"auto","layout":"vertical","align":"start","variant":"filled"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The spec declares ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["application/json"]},", but a new middleware starts returning ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["text/html"]}," on error paths."," ","Client JSON parsers choke silently."," ","Respect catches it."]}]},{"$$mdtype":"Tag","name":"Card","attributes":{"title":"Response schemas","imagePosition":"start","iconPosition":"auto","layout":"vertical","align":"start","variant":"filled"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A developer removes a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["required"]}," field."," ","Or worse: an internal field like ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["internal_user_id"]}," leaks into the public response."," ","Schema validation catches both--missing fields and dangerous extras."," ","Respect catches it."]}]},{"$$mdtype":"Tag","name":"Card","attributes":{"title":"Headers","imagePosition":"start","iconPosition":"auto","layout":"vertical","align":"start","variant":"filled"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["CORS headers disappear after a proxy update."," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Cache-Control"]}," goes missing."," ","A security header gets silently dropped."," ","Respect catches it."]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Here's a concrete example."," ","Say your OpenAPI spec defines this response schema for ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /orders/{orderId}"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"yaml","data-title":"Expected: Order schema from openapi.yaml","header":{"title":"Expected: Order schema from openapi.yaml","controls":{"copy":{}}},"source":"properties:\n  id:\n    type: string\n    pattern: ^ord_[0-9abcdefghjkmnpqrstvwxyz]{26}$\n  customerName:\n    type: string\n  status:\n    type: string\n    enum: [placed, preparing, completed, canceled]\n  totalPrice:\n    type: integer\n    minimum: 0\n  orderItems:\n    type: array\n    minItems: 1\n    items:\n      type: object\n      properties:\n        menuItemId:\n          type: string\n        quantity:\n          type: integer\n          minimum: 1\n      required: [menuItemId, quantity]\n      additionalProperties: false\nrequired: [id, customerName, status, totalPrice, orderItems]\nadditionalProperties: false\n","lang":"yaml"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["But after a deploy, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET /orders/{orderId}"]}," returns:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","data-title":"Actual response -- spot the problems","header":{"title":"Actual response -- spot the problems","controls":{"copy":{}}},"source":"{\n  \"id\": \"ord_01h1s5z6vf2mm1mz3hevnn9va7\",\n  \"customerName\": \"Mary Ann\",\n  \"status\": \"fulfilled\",\n  \"totalPrice\": 200,\n  \"orderItems\": [\n    { \"menuItemId\": \"itm_01h1s5z6vf2mm1mz3hevnn9va7\", \"quantity\": 2 }\n  ],\n  \"internalCostCents\": 45\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Two contract violations:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["status"]}," is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["\"fulfilled\""]}," -- not in the enum. Every client with a switch on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["placed | preparing | completed | canceled"]}," just hit the default case."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["internalCostCents"]}," leaked into the public API. That's internal pricing data exposed to consumers."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Respect flags both automatically."," ","No test code needed--the OpenAPI spec already defines what's allowed."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This is exactly the kind of bug that passes every other test."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"running-it-in-practice","__idx":4},"children":["Running it in practice"]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"div","attributes":{"label":"Quick start","disable":false},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Two commands, from nothing to validated contracts:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","data-title":"Generate and run","header":{"title":"Generate and run","controls":{"copy":{}}},"source":"npx @redocly/cli generate-arazzo openapi.yaml\nnpx @redocly/cli respect auto-generated.arazzo.yaml --verbose\n","lang":"bash"},"children":[]}]},{"$$mdtype":"Tag","name":"div","attributes":{"label":"CI/CD (GitHub Actions)","disable":false},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Gate every pull request on contract compliance:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"yaml","data-title":".github/workflows/contract-tests.yml","header":{"title":".github/workflows/contract-tests.yml","controls":{"copy":{}}},"source":"name: API Contract Tests\non: [pull_request]\njobs:\n  contract-test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 22\n      - run: npm install -g @redocly/cli\n      - run: redocly respect ./tests/api-contracts.arazzo.yaml --verbose\n","lang":"yaml"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Non-zero exit code on contract violations."," ","Broken contracts don't merge."]}]},{"$$mdtype":"Tag","name":"div","attributes":{"label":"Custom workflows","disable":false},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Write targeted Arazzo files for your critical paths."," ","This example shows a real order lifecycle--with reusable atomic workflows for auth and cleanup:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"yaml","data-title":"cafe-orders.arazzo.yaml","header":{"title":"cafe-orders.arazzo.yaml","controls":{"copy":{}}},"source":"arazzo: 1.0.1\ninfo:\n  title: Cafe API - Orders\n  version: 1.0.0\nsourceDescriptions:\n  - name: cafeApi\n    type: openapi\n    url: openapi.yaml\n  - name: authorization\n    type: arazzo\n    url: atomic-operations/authorization.arazzo.yaml\n  - name: create-menu-item\n    type: arazzo\n    url: atomic-operations/create-menu-item.arazzo.yaml\n\nworkflows:\n  - workflowId: crud-orders-workflow\n    summary: CRUD Orders\n    steps:\n      - stepId: authorize\n        workflowId: $sourceDescriptions.authorization.workflows.authorize-with-code\n        outputs:\n          access_token: $outputs.access_token_with_code\n          client_id: $outputs.client_id\n\n      - stepId: create-menu-item\n        workflowId: $sourceDescriptions.create-menu-item.workflows.create-menu-item-workflow\n        parameters:\n          - name: accessToken\n            value: $steps.authorize.outputs.access_token\n          - name: clientId\n            value: $steps.authorize.outputs.client_id\n        outputs:\n          menuItemId: $outputs.menuItemId\n\n      - stepId: create-order\n        operationId: $sourceDescriptions.cafeApi.createOrder\n        requestBody:\n          payload:\n            customerName: John\n            orderItems:\n              - menuItemId: $steps.create-menu-item.outputs.menuItemId\n                quantity: 3\n                comment: \"add more chocolate\"\n        successCriteria:\n          - condition: $statusCode == 201\n          - condition: $response.body#/id != null\n        outputs:\n          orderId: $response.body#/id\n\n      - stepId: update-order-status\n        operationId: $sourceDescriptions.cafeApi.updateOrder\n        parameters:\n          - name: orderId\n            in: path\n            value: $steps.create-order.outputs.orderId\n        requestBody:\n          payload:\n            status: completed\n        successCriteria:\n          - condition: $statusCode == 200\n          - condition: $response.body#/status == 'completed'\n\n      - stepId: get-order-by-id\n        operationId: $sourceDescriptions.cafeApi.getOrderById\n        parameters:\n          - name: orderId\n            in: path\n            value: $steps.create-order.outputs.orderId\n        successCriteria:\n          - condition: $statusCode == 200\n          - condition: $response.body#/customerName == 'John'\n","lang":"yaml"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Notice the composition: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["authorization"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["create-menu-item"]}," are separate Arazzo files referenced as source descriptions."," ","Each is reusable across multiple test workflows."]}]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"the-open-standards-advantage","__idx":5},"children":["The open-standards advantage"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Respect builds on two open standards."," ","This avoids tool lock-in entirely."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["OpenAPI"]}," defines your API contract. The same file you use for docs, code generation, and governance now also powers your tests."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Arazzo"]}," defines your test workflows. An open spec from the OpenAPI Initiative, not a proprietary format tied to a single tool."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Your test definitions are plain YAML files that live in your repo, version with your code, and work with any Arazzo-compatible tool."," ","When your OpenAPI description changes, contract tests validate against the updated spec automatically."," ","No separate test layer to keep in sync."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["One spec. One workflow layer. No duplication."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"from-cli-to-continuous-monitoring","__idx":6},"children":["From CLI to continuous monitoring"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Respect CLI is free and open source--run it locally, in CI, anywhere."," ","When you need always-on validation, ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/respect"},"children":["Respect Monitoring"]}," extends the same foundation into a hosted service."]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Capability"},"children":["Capability"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Respect CLI (free)"},"children":["Respect CLI (free)"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Respect Monitoring (hosted)"},"children":["Respect Monitoring (hosted)"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Contract testing"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Yes"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Yes"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["CI/CD integration"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Yes"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Yes"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Scheduled checks"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Manual / cron"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Built-in (minute / hour / day)"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Alerts (Slack, email)"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["No"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Yes"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Dashboards and trend tracking"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["No"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Yes"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Multi-environment"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Manual config"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Built-in (staging, production)"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Pricing"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Free, forever"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["1,000 requests/month free, then usage-based"]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Start with the CLI during development."," ","Graduate to Monitoring when you need continuous assurance in production."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"faq","__idx":7},"children":["FAQ"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Do I need to write test code?"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["No."," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["redocly generate-arazzo openapi.yaml"]}," creates test workflows from your spec automatically."," ","Customize them for specific scenarios if you want, but the defaults cover your endpoints out of the box."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["What if my OpenAPI spec is incomplete?"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Start with what you have."," ","An incomplete spec still catches real drift on the endpoints it covers."," ","You can expand coverage incrementally as Respect reveals gaps."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Does this work in CI/CD?"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Yes."," ","Respect returns standard exit codes--non-zero means contract violations."," ","GitHub Actions, GitLab CI, Jenkins, CircleCI, plain shell scripts--anything that can run a CLI."," ","Broken contracts don't merge."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"run-it-once-and-see-what-breaks","__idx":8},"children":["Run it once and see what breaks"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Your OpenAPI description already defines the contract."," ","You just haven't been enforcing it."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["You will find something."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"npx @redocly/cli respect your-api.arazzo.yaml --verbose\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/respect-cli"},"children":["Respect CLI"]}," -- open source, free forever. ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://github.com/Redocly/redocly-cli/tree/main/packages/respect-core"},"children":["View on GitHub"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/respect"},"children":["Documentation"]}," -- guides, use cases, command reference."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/respect"},"children":["Respect Monitoring"]}," -- continuous, hosted contract validation."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/learn/arazzo/what-is-arazzo"},"children":["Learn Arazzo"]}," -- the workflow standard."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Your API has been making promises."," ","Time to find out if it's keeping them."]}]},"headings":[{"value":"Why your current tests miss this","id":"why-your-current-tests-miss-this","depth":2},{"value":"What Respect does about it","id":"what-respect-does-about-it","depth":2},{"value":"What Arazzo adds: multi-step workflows without glue code","id":"what-arazzo-adds-multi-step-workflows-without-glue-code","depth":2},{"value":"What gets caught","id":"what-gets-caught","depth":2},{"value":"Running it in practice","id":"running-it-in-practice","depth":2},{"value":"The open-standards advantage","id":"the-open-standards-advantage","depth":2},{"value":"From CLI to continuous monitoring","id":"from-cli-to-continuous-monitoring","depth":2},{"value":"FAQ","id":"faq","depth":2},{"value":"Run it once and see what breaks","id":"run-it-once-and-see-what-breaks","depth":2}],"frontmatter":{"template":"../@theme/templates/BlogPost","title":"API contract testing from OpenAPI using Arazzo","description":"Your OpenAPI spec is a contract. Learn how to enforce it automatically with API contract testing using the Arazzo standard and Redocly's Respect CLI.","seo":{"title":"API contract testing from OpenAPI using Arazzo","description":"Your OpenAPI spec is a contract. Learn how to enforce it automatically with API contract testing using the Arazzo standard and Redocly's Respect CLI.","image":"/assets/api-contract-testing-arazzo.edf0e7fb5145914f9b6ff425b13b55e0257b4cdf311737b2f65e9c71b98d054e.978384e4.png"},"author":"roman-marshevskyi","publishedDate":"2026-04-22","categories":["api-governance:compliance-quality","redocly:product-updates"],"image":"api-contract-testing-arazzo.png"},"lastModified":"2026-04-21T20:14:30.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/blog/api-contract-testing-arazzo","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}