Robot Framework API Testing with RequestsLibrary

Robot Framework API Testing with RequestsLibrary

Robot Framework isn't just for UI testing. With RequestsLibrary, it becomes a capable API testing tool — one that produces the same structured reports and follows the same keyword model as your UI tests. This matters for teams that want unified test output and want non-developers to be able to write API tests.

This guide covers everything from basic HTTP requests to production-grade API test patterns.

Installation

pip install robotframework
pip install robotframework-requests

Note the package name: robotframework-requests, but you import it as RequestsLibrary.

*** Settings ***
Library    RequestsLibrary
Library    Collections
Library    String
Library    JSONLibrary    # Optional, for complex JSON operations

Also install JSONLibrary if you need advanced JSON querying:

pip install robotframework-jsonlibrary

Basic HTTP Requests

RequestsLibrary uses a session model — you create a session first, then make requests against it. This handles base URLs, headers, and authentication at the session level.

*** Variables ***
${BASE_URL}    https://api.example.com
${API_KEY}     %{API_KEY}    # Read from environment variable

*** Test Cases ***
GET Request Returns Expected Response
    Create Session    api    ${BASE_URL}
    ${response}=    GET On Session    api    /users/1
    Should Be Equal As Integers    ${response.status_code}    200
    ${body}=    Set Variable    ${response.json()}
    Should Be Equal    ${body}[name]    John Doe
    Should Be Equal    ${body}[email]    john@example.com

POST Request Creates Resource
    Create Session    api    ${BASE_URL}
    ${headers}=    Create Dictionary    Content-Type=application/json
    ${body}=    Create Dictionary
    ...    name=Jane Smith
    ...    email=jane@example.com
    ...    role=viewer
    ${response}=    POST On Session    api    /users    json=${body}    headers=${headers}
    Should Be Equal As Integers    ${response.status_code}    201
    Should Be Equal    ${response.json()}[email]    jane@example.com
    [Teardown]    DELETE On Session    api    /users/${response.json()}[id]

PUT Request Updates Resource
    Create Session    api    ${BASE_URL}
    ${body}=    Create Dictionary    name=Updated Name
    ${response}=    PUT On Session    api    /users/1    json=${body}
    Should Be Equal As Integers    ${response.status_code}    200

DELETE Request Removes Resource
    Create Session    api    ${BASE_URL}
    ${response}=    DELETE On Session    api    /users/99
    Should Be Equal As Integers    ${response.status_code}    204

Session configuration at creation:

Create Session    
...    alias=api
...    url=${BASE_URL}
...    headers={"Authorization": "Bearer ${TOKEN}", "Accept": "application/json"}
...    verify=True      # SSL verification (set False for self-signed certs)
...    timeout=30

Authentication Patterns

Bearer token auth:

*** Keywords ***
Create Authenticated Session
    [Arguments]    ${token}
    ${headers}=    Create Dictionary    Authorization=Bearer ${token}
    Create Session    api    ${BASE_URL}    headers=${headers}

Get Auth Token
    Create Session    auth    ${BASE_URL}
    ${body}=    Create Dictionary    
    ...    username=${API_USERNAME}    
    ...    password=${API_PASSWORD}
    ${response}=    POST On Session    auth    /auth/login    json=${body}
    Should Be Equal As Integers    ${response.status_code}    200
    ${token}=    Set Variable    ${response.json()}[access_token]
    RETURN    ${token}

*** Test Cases ***
Authenticated Endpoint Returns Data
    ${token}=    Get Auth Token
    Create Authenticated Session    ${token}
    ${response}=    GET On Session    api    /me
    Should Be Equal As Integers    ${response.status_code}    200
    Should Be Equal    ${response.json()}[email]    ${API_USERNAME}

Basic auth:

Create Session    api    ${BASE_URL}    auth=${('username', 'password')}

API key in header:

${headers}=    Create Dictionary    X-API-Key=${API_KEY}
Create Session    api    ${BASE_URL}    headers=${headers}

OAuth2 client credentials:

*** Keywords ***
Get OAuth2 Token
    Create Session    oauth    ${AUTH_SERVER_URL}
    ${body}=    Create Dictionary
    ...    grant_type=client_credentials
    ...    client_id=${CLIENT_ID}
    ...    client_secret=${CLIENT_SECRET}
    ...    scope=read write
    ${response}=    POST On Session    oauth    /oauth/token    data=${body}
    Should Be Equal As Integers    ${response.status_code}    200
    RETURN    ${response.json()}[access_token]

Response Validation

Status code validation:

# Basic
Should Be Equal As Integers    ${response.status_code}    200

# With custom message
Run Keyword If    ${response.status_code} != 200
...    Fail    Expected 200, got ${response.status_code}: ${response.text}

JSON body validation:

*** Keywords ***
Validate User Response Body
    [Arguments]    ${response}
    ${body}=    Set Variable    ${response.json()}
    
    # Required fields present
    Dictionary Should Contain Key    ${body}    id
    Dictionary Should Contain Key    ${body}    email
    Dictionary Should Contain Key    ${body}    created_at
    
    # Field types and values
    Should Be True    isinstance(${body}[id], int)
    Should Match Regexp    ${body}[email]    ^[^@]+@[^@]+\.[^@]+$
    Should Be True    ${body}[id] > 0

Validate Paginated Response
    [Arguments]    ${response}    ${expected_per_page}=20
    ${body}=    Set Variable    ${response.json()}
    Dictionary Should Contain Key    ${body}    data
    Dictionary Should Contain Key    ${body}    total
    Dictionary Should Contain Key    ${body}    page
    ${count}=    Get Length    ${body}[data]
    Should Be True    ${count} <= ${expected_per_page}

Header validation:

${content_type}=    Get From Dictionary    ${response.headers}    Content-Type
Should Contain    ${content_type}    application/json

Response time:

${elapsed}=    Set Variable    ${response.elapsed.total_seconds()}
Should Be True    ${elapsed} < 2.0    msg=API took ${elapsed}s, expected under 2s

JSON Handling

Working with nested JSON:

*** Test Cases ***
Nested JSON Access
    Create Session    api    ${BASE_URL}
    ${response}=    GET On Session    api    /users/1
    ${body}=    Set Variable    ${response.json()}
    
    # Direct key access
    ${name}=    Set Variable    ${body}[name]
    
    # Nested access
    ${city}=    Set Variable    ${body}[address][city]
    
    # Array access
    ${first_tag}=    Set Variable    ${body}[tags][0]
    
    # Length check
    ${tag_count}=    Get Length    ${body}[tags]
    Should Be True    ${tag_count} >= 1

Building request bodies:

*** Keywords ***
Build Create Project Request
    [Arguments]    ${name}    ${description}    @{member_emails}
    ${members}=    Create List
    FOR    ${email}    IN    @{member_emails}
        ${member}=    Create Dictionary    email=${email}    role=viewer
        Append To List    ${members}    ${member}
    END
    ${body}=    Create Dictionary
    ...    name=${name}
    ...    description=${description}
    ...    members=${members}
    ...    settings={"notifications": true, "public": false}
    RETURN    ${body}

*** Test Cases ***
Create Project With Members
    ${body}=    Build Create Project Request
    ...    My Test Project
    ...    A project for testing
    ...    alice@example.com
    ...    bob@example.com
    Create Session    api    ${BASE_URL}    headers=${AUTH_HEADERS}
    ${response}=    POST On Session    api    /projects    json=${body}
    Should Be Equal As Integers    ${response.status_code}    201
    ${member_count}=    Get Length    ${response.json()}[members]
    Should Be Equal As Integers    ${member_count}    2

Parsing complex responses with JSONLibrary:

Library    JSONLibrary

*** Keywords ***
Extract All User Emails From Response
    [Arguments]    ${response}
    ${emails}=    Get Value From Json    ${response.json()}    $..email
    RETURN    ${emails}

Find Items With High Priority
    [Arguments]    ${response}
    ${items}=    Get Value From Json    ${response.json()}    $.items[?(@.priority=='high')]
    RETURN    ${items}

JSONLibrary uses JSONPath syntax — the $..email expression finds all email fields anywhere in the document.

Chaining Requests

Most real API tests need to chain requests — create a resource, then operate on it:

*** Test Cases ***
Full User Lifecycle
    [Setup]    Create Authenticated Session    ${AUTH_TOKEN}
    
    # Create user
    ${create_body}=    Create Dictionary    
    ...    email=test-${RANDOM_STRING}@example.com
    ...    name=Test User
    ${create_resp}=    POST On Session    api    /users    json=${create_body}
    Should Be Equal As Integers    ${create_resp.status_code}    201
    ${user_id}=    Set Variable    ${create_resp.json()}[id]
    
    # Verify user exists
    ${get_resp}=    GET On Session    api    /users/${user_id}
    Should Be Equal As Integers    ${get_resp.status_code}    200
    Should Be Equal    ${get_resp.json()}[email]    ${create_body}[email]
    
    # Update user
    ${update_body}=    Create Dictionary    name=Updated Name
    ${update_resp}=    PUT On Session    api    /users/${user_id}    json=${update_body}
    Should Be Equal As Integers    ${update_resp.status_code}    200
    Should Be Equal    ${update_resp.json()}[name]    Updated Name
    
    # Delete user
    ${delete_resp}=    DELETE On Session    api    /users/${user_id}
    Should Be Equal As Integers    ${delete_resp.status_code}    204
    
    # Verify deleted
    ${gone_resp}=    GET On Session    api    /users/${user_id}    expected_status=404
    Should Be Equal As Integers    ${gone_resp.status_code}    404
    
    [Teardown]    Delete Sessions

*** Keywords ***
Create Authenticated Session
    [Arguments]    ${token}
    ${headers}=    Create Dictionary    
    ...    Authorization=Bearer ${token}
    ...    Content-Type=application/json
    ...    Accept=application/json
    Create Session    api    ${BASE_URL}    headers=${headers}

The expected_status=404 argument tells RequestsLibrary not to raise an exception for 4xx responses — useful when you're deliberately testing error cases.

Handling Async APIs

For APIs that return 202 Accepted and process asynchronously:

*** Keywords ***
Submit Job And Wait For Completion
    [Arguments]    ${job_payload}    ${max_wait}=60s
    
    # Submit the job
    ${submit_resp}=    POST On Session    api    /jobs    json=${job_payload}
    Should Be Equal As Integers    ${submit_resp.status_code}    202
    ${job_id}=    Set Variable    ${submit_resp.json()}[job_id]
    
    # Poll until complete
    Wait Until Keyword Succeeds    ${max_wait}    3s    
    ...    Job Should Be Complete    ${job_id}
    
    # Get final result
    ${result_resp}=    GET On Session    api    /jobs/${job_id}/result
    Should Be Equal As Integers    ${result_resp.status_code}    200
    RETURN    ${result_resp}

Job Should Be Complete
    [Arguments]    ${job_id}
    ${resp}=    GET On Session    api    /jobs/${job_id}
    Should Be Equal As Integers    ${resp.status_code}    200
    ${status}=    Set Variable    ${resp.json()}[status]
    Should Be Equal    ${status}    completed
    ...    msg=Job ${job_id} status is ${status}, expected completed

Query Parameters and URL Building

*** Keywords ***
Search Users By Email Domain
    [Arguments]    ${domain}    ${page}=1    ${per_page}=20
    ${params}=    Create Dictionary    
    ...    email_domain=${domain}
    ...    page=${page}
    ...    per_page=${per_page}
    ${response}=    GET On Session    api    /users    params=${params}
    RETURN    ${response}

*** Test Cases ***
Search Returns Filtered Results
    Create Session    api    ${BASE_URL}    headers=${AUTH_HEADERS}
    ${resp}=    Search Users By Email Domain    example.com
    Should Be Equal As Integers    ${resp.status_code}    200
    ${users}=    Set Variable    ${resp.json()}[data]
    FOR    ${user}    IN    @{users}
        Should Contain    ${user}[email]    @example.com
    END

Error Response Testing

Testing error cases is as important as the happy path:

*** Test Cases ***
Missing Required Field Returns 422
    Create Session    api    ${BASE_URL}    headers=${AUTH_HEADERS}
    ${body}=    Create Dictionary    email=test@example.com    # Missing required 'name'
    ${response}=    POST On Session    api    /users    json=${body}
    ...    expected_status=422
    Should Be Equal As Integers    ${response.status_code}    422
    ${errors}=    Set Variable    ${response.json()}[errors]
    Should Be True    'name' in ${errors}

Unauthorized Access Returns 401
    Create Session    unauth    ${BASE_URL}    # No auth headers
    ${response}=    GET On Session    unauth    /protected-endpoint
    ...    expected_status=401
    Should Be Equal As Integers    ${response.status_code}    401

Rate Limiting Returns 429
    Create Session    api    ${BASE_URL}    headers=${AUTH_HEADERS}
    # Make requests until rate limited
    FOR    ${i}    IN RANGE    100
        ${response}=    GET On Session    api    /rate-limited-endpoint
        ...    expected_status=any
        Exit For Loop If    ${response.status_code} == 429
    END
    Should Be Equal As Integers    ${response.status_code}    429
    Dictionary Should Contain Key    ${response.headers}    Retry-After

Test Data Setup with API Calls

Use the API itself for test data setup instead of database manipulation when possible:

*** Keywords ***
Create Test User Via API
    [Documentation]    Creates a test user and returns the user data dict
    [Arguments]    ${role}=viewer
    Create Session    admin_api    ${BASE_URL}    headers=${ADMIN_HEADERS}
    ${email}=    Generate Random String    8    [LETTERS]
    ${email}=    Set Variable    ${email.lower()}@test-${SUITE NAME}.example.com
    ${body}=    Create Dictionary    
    ...    email=${email}
    ...    name=Test User
    ...    role=${role}
    ...    password=TestPass123!
    ${response}=    POST On Session    admin_api    /admin/users    json=${body}
    Should Be Equal As Integers    ${response.status_code}    201
    RETURN    ${response.json()}

Delete Test User Via API
    [Arguments]    ${user_id}
    Create Session    admin_api    ${BASE_URL}    headers=${ADMIN_HEADERS}
    DELETE On Session    admin_api    /admin/users/${user_id}

*** Test Cases ***
Viewer Cannot Access Admin Endpoint
    ${user}=    Create Test User Via API    viewer
    ${token}=    Get Token For User    ${user}[email]    TestPass123!
    ${headers}=    Create Dictionary    Authorization=Bearer ${token}
    Create Session    viewer_session    ${BASE_URL}    headers=${headers}
    ${response}=    GET On Session    viewer_session    /admin/users
    ...    expected_status=403
    Should Be Equal As Integers    ${response.status_code}    403
    [Teardown]    Delete Test User Via API    ${user}[id]

Suite Structure for API Tests

tests/api/
  suite_setup.robot      # Auth setup, shared variables
  users/
    users_crud.robot
    users_search.robot
    users_permissions.robot
  projects/
    projects_crud.robot
    projects_members.robot
  errors/
    validation_errors.robot
    auth_errors.robot
resources/
  api/
    auth.resource
    users.resource
    projects.resource

Shared suite-level auth setup:

# tests/api/suite_setup.robot — included by all API test suites
*** Settings ***
Resource    ../../resources/api/auth.resource

*** Variables ***
${BASE_URL}       %{API_BASE_URL}
${ADMIN_TOKEN}    ${EMPTY}
${USER_TOKEN}     ${EMPTY}

*** Keywords ***
Initialize API Test Session
    ${admin_token}=    Get Admin Auth Token
    ${user_token}=     Get User Auth Token    viewer@example.com    ViewerPass123!
    Set Suite Variable    ${ADMIN_TOKEN}    ${admin_token}
    Set Suite Variable    ${USER_TOKEN}     ${user_token}
    ${admin_headers}=    Create Dictionary    Authorization=Bearer ${ADMIN_TOKEN}
    ${user_headers}=     Create Dictionary    Authorization=Bearer ${USER_TOKEN}
    Set Suite Variable    ${ADMIN_HEADERS}    ${admin_headers}
    Set Suite Variable    ${USER_HEADERS}     ${user_headers}
    Create Session    admin_api    ${BASE_URL}    headers=${ADMIN_HEADERS}
    Create Session    user_api     ${BASE_URL}    headers=${USER_HEADERS}

When Robot Framework API Testing Makes Sense

The main advantage over raw Python API test frameworks (pytest + requests, or Postman) is report unification. If your team already uses RF for UI tests, adding API tests in RF means all results flow into the same HTML reports, the same CI pipeline, and use the same keyword vocabulary.

The disadvantage: complex data manipulation (sorting, filtering, mathematical assertions) is more verbose in RF than in Python. A complex JSON schema validation that's 5 lines in pytest becomes 20 lines in RF.

For teams with mixed technical backgrounds or teams already invested in RF, RequestsLibrary is a solid choice. For developer-only teams doing pure API testing, pytest + requests is simpler. The tradeoff is the same as for RF in general: readability and accessibility vs raw expressiveness.

Read more

Start now free