REST APIs are everywhere: whether you’re pulling data from a public service or integrating with a protected service.
In Progress OpenEdge, you can call REST services cleanly using the built-in HTTP client, handle the response, and structure your integration for reuse and clarity.
In this post, you’ll see how to implement each of the core HTTP methods, leveraging some existing free public APIs.
Typical steps:
- Build the request (GET, POST, PUT, PATCH, DELETE)
- Attach headers (Content-Type, Accept, Authorization, etc.)
- Send the request
- Handle the response
[Build Request] → [Add Headers] → [Send] → [Validate Status] → [Validate Entity Type] → [Return Domain Object]
Before you start
A good practice is to first try the API calls in Postman, Insomnia, command-line curl (or a similar tool) so you can see exactly what the server expects and returns before writing any ABL code.
Think of Postman as your REST sandbox – once the request works there, translating it into OpenEdge code becomes straightforward.
If you’re unsure about headers, payload structure, or authentication, testing the call using one of these tools can save a lot of trial and error.
GET – Retrieving Resources
GET is the most common HTTP method. You use it to pull data without modifying anything on the server.
You can retrieve a single record or a collection.
Expected response:
- Status code: 200 OK
- Entity: JsonObject (for single resource) or JsonArray (for collections)
Tips & Tricks:
- Always check the StatusCode before casting the entity.
- Use TYPE-OF to validate the returned entity type.
/*------------------------------------------------------------------------------
Purpose: Retrieve a list of Posts
Notes: GET https://jsonplaceholder.typicode.com/posts
------------------------------------------------------------------------------*/
METHOD PUBLIC JsonArray Get():
DEFINE VARIABLE oRequest AS IHttpRequest NO-UNDO.
DEFINE VARIABLE oResponse AS IHttpResponse NO-UNDO.
DEFINE VARIABLE oLib AS IHttpClientLibrary NO-UNDO.
ASSIGN
oRequest = RequestBuilder:Get('https://jsonplaceholder.typicode.com/posts')
:AcceptJson()
:Request
oLib = ClientLibraryBuilder:Build()
:ServerNameIndicator('jsonplaceholder.typicode.com')
:Library
oResponse = ClientBuilder:Build():UsingLibrary(oLib)
:Client:Execute(oRequest).
//we expect a 200 - OK
CASE oResponse:StatusCode:
WHEN StatusCodeEnum:OK:GetValue() THEN DO:
IF TYPE-OF(oResponse:Entity, Progress.Json.ObjectModel.JsonArray) THEN
RETURN CAST(oResponse:Entity, Progress.Json.ObjectModel.JsonArray).
ELSE
UNDO, THROW NEW AppError('Unexpected response type').
END.
OTHERWISE
UNDO, THROW NEW HttpRequestError(STRING(oResponse:StatusCode),
MethodEnum:GET:ToString(),
'https://jsonplaceholder.typicode.com/posts').
END CASE.
FINALLY:
DELETE OBJECT oRequest NO-ERROR.
DELETE OBJECT oLib NO-ERROR.
END FINALLY.
END METHOD.
POST – Creating a Resource
POST is used when you want to create a new entity on the API’s side.
Expected response:
- Status code: 201 Created
- Entity: JsonObject representing the newly created resource
Tips & Tricks:
- Ensure the payload includes all required fields; missing fields can lead to 400 Bad Request.
- If the API returns the created object, you can immediately read its id to perform subsequent requests (PUT/PATCH/DELETE).
Payload sample:
METHOD PRIVATE JsonObject GetBodyForNewPost( ):
DEFINE VARIABLE oJsonObject AS JsonObject NO-UNDO.
oJsonObject = NEW JsonObject().
oJsonObject:Add("userId", 1).
oJsonObject:Add("title", "New post title").
oJsonObject:Add("body", "This is a new post").
RETURN oJsonObject.
END METHOD.
/*------------------------------------------------------------------------------
Purpose: Create a Post
Notes: POST to https://jsonplaceholder.typicode.com/posts
------------------------------------------------------------------------------*/
METHOD PUBLIC JsonObject Post( ):
DEFINE VARIABLE oRequest AS IHttpRequest NO-UNDO.
DEFINE VARIABLE oResponse AS IHttpResponse NO-UNDO.
DEFINE VARIABLE oLib AS IHttpClientLibrary NO-UNDO.
DEFINE VARIABLE oEntity AS JsonObject NO-UNDO.
ASSIGN
oEntity = THIS-OBJECT:GetBodyForNewPost()
oRequest = RequestBuilder:POST('https://jsonplaceholder.typicode.com/posts', oEntity)
:ContentType("application/json")
:AcceptJson()
:Request
oLib = ClientLibraryBuilder:Build()
:ServerNameIndicator('jsonplaceholder.typicode.com')
:Library
oResponse = ClientBuilder:Build():UsingLibrary(oLib)
:Client:Execute(oRequest).
//we expect a 201 - Created
IF oResponse:StatusCode <> StatusCodeEnum:Created:GetValue() THEN
UNDO, THROW NEW HttpRequestError(STRING(oResponse:StatusCode),
MethodEnum:POST:ToString(),
'https://jsonplaceholder.typicode.com/posts').
//we expect a json
IF TYPE-OF(oResponse:Entity, Progress.Json.ObjectModel.JsonObject) THEN
RETURN CAST(oResponse:Entity, Progress.Json.ObjectModel.JsonObject).
ELSE
UNDO, THROW NEW AppError("Unexpected response type").
FINALLY:
DELETE OBJECT oRequest NO-ERROR.
DELETE OBJECT oLib NO-ERROR.
DELETE OBJECT oEntity NO-ERROR.
END FINALLY.
END METHOD.
PUT – Full Updates
PUT is intended to replace a resource entirely. The body should contain the full representation of the resource:
Expected response:
- Status code: 200 OK (or sometimes 204 No Content)
- Entity: Usually JsonObject of the updated resource
Tips & Tricks:
- Include the resource ID in both the URL and the body to avoid mismatches.
- PUT is idempotent: repeating the same request should not change the result.
- Always send the full resource; missing fields may be overwritten with defaults or nulls.
Common mistakes:
- Sending partial payloads. Use PATCH for partial updates instead.
Payload example (full resource):
METHOD PRIVATE JsonObject GetBodyForUpdatingAPost( ):
DEFINE VARIABLE oJsonObject AS JsonObject NO-UNDO.
oJsonObject = NEW JsonObject().
oJsonObject:Add("userId", 1).
oJsonObject:Add("id", 1).
oJsonObject:Add("title", "This is the updated title").
oJsonObject:Add("body", "This is the updated body").
RETURN oJsonObject.
END METHOD.
/*------------------------------------------------------------------------------
Purpose: Update a Post
Notes: PUT to https://jsonplaceholder.typicode.com/posts/1
------------------------------------------------------------------------------*/
METHOD PUBLIC JsonObject Put( ):
DEFINE VARIABLE oRequest AS IHttpRequest NO-UNDO.
DEFINE VARIABLE oResponse AS IHttpResponse NO-UNDO.
DEFINE VARIABLE oLib AS IHttpClientLibrary NO-UNDO.
DEFINE VARIABLE oEntity AS JsonObject NO-UNDO.
ASSIGN
oEntity = THIS-OBJECT:GetBodyForUpdatingAPost()
oRequest = RequestBuilder:PUT('https://jsonplaceholder.typicode.com/posts/1', oEntity)
:ContentType("application/json")
:AcceptJson()
:Request
oLib = ClientLibraryBuilder:Build()
:ServerNameIndicator('jsonplaceholder.typicode.com')
:Library
oResponse = ClientBuilder:Build():UsingLibrary(oLib)
:Client:Execute(oRequest).
//we expect a 200 - OK
IF oResponse:StatusCode <> StatusCodeEnum:Ok:GetValue() THEN
UNDO, THROW NEW HttpRequestError(STRING(oResponse:StatusCode),
MethodEnum:PUT:ToString(),
'https://jsonplaceholder.typicode.com/posts/1').
//we expect a json
IF TYPE-OF(oResponse:Entity, Progress.Json.ObjectModel.JsonObject) THEN
RETURN CAST(oResponse:Entity, Progress.Json.ObjectModel.JsonObject).
ELSE
UNDO, THROW NEW AppError("Unexpected response type").
FINALLY:
DELETE OBJECT oRequest NO-ERROR.
DELETE OBJECT oLib NO-ERROR.
DELETE OBJECT oEntity NO-ERROR.
END FINALLY.
END METHOD.
PATCH – Partial Updates
PATCH is used when only some fields of a resource need updating.
Expected response:
- Status code: 200 OK (or sometimes 204 No Content)
- Entity: JsonObject representing the updated resource
Tips & Tricks:
- Only include fields that need to be updated.
- PATCH is not necessarily idempotent, depending on the API.
- Useful for large resources to reduce payload size.
Common mistakes:
- Sending a full resource in PATCH: this is inefficient and may cause unexpected updates.
- Assuming PATCH works without the resource ID in the URL.
Payload example: only the fields that need to be updated are sent.
METHOD PRIVATE JsonObject GetBodyForPatchingAPost( ):
DEFINE VARIABLE oJsonObject AS JsonObject NO-UNDO.
oJsonObject = NEW JsonObject().
oJsonObject:Add("body", "This is the patched body").
RETURN oJsonObject.
END METHOD.
/*------------------------------------------------------------------------------
Purpose: Patch a Post
Notes: PATCH to https://jsonplaceholder.typicode.com/posts/1
------------------------------------------------------------------------------*/
METHOD PUBLIC JsonObject Patch( ):
DEFINE VARIABLE oRequest AS IHttpRequest NO-UNDO.
DEFINE VARIABLE oResponse AS IHttpResponse NO-UNDO.
DEFINE VARIABLE oLib AS IHttpClientLibrary NO-UNDO.
DEFINE VARIABLE oEntity AS JsonObject NO-UNDO.
ASSIGN
oEntity = THIS-OBJECT:GetBodyForPatchingAPost()
oRequest = RequestBuilder:PATCH('https://jsonplaceholder.typicode.com/posts/1', oEntity)
:ContentType("application/json")
:AcceptJson()
:Request
oLib = ClientLibraryBuilder:Build()
:ServerNameIndicator('jsonplaceholder.typicode.com')
:Library
oResponse = ClientBuilder:Build():UsingLibrary(oLib)
:Client:Execute(oRequest).
//we expect a 200 - OK
IF oResponse:StatusCode <> StatusCodeEnum:Ok:GetValue() THEN
UNDO, THROW NEW HttpRequestError(STRING(oResponse:StatusCode),
MethodEnum:PATCH:ToString(),
'https://jsonplaceholder.typicode.com/posts/1').
//we expect a json
IF TYPE-OF(oResponse:Entity, Progress.Json.ObjectModel.JsonObject) THEN
RETURN CAST(oResponse:Entity, Progress.Json.ObjectModel.JsonObject).
ELSE
UNDO, THROW NEW AppError("Unexpected response type").
FINALLY:
DELETE OBJECT oRequest NO-ERROR.
DELETE OBJECT oLib NO-ERROR.
DELETE OBJECT oEntity NO-ERROR.
END FINALLY.
END METHOD.
DELETE – Removing a Resource
DELETE tells the server to remove the specified resource.
Expected response:
- Status code: 200 OK (or sometimes 204 No Content)
- Entity: Usually none, sometimes a message confirming deletion
Tips & Tricks:
- Always validate that the resource exists before deleting to avoid 404.
- DELETE is idempotent: calling it multiple times should not cause errors (except 404).
Common mistakes:
- Forgetting to handle 404 (resource not found).
- Assuming the server will return a JSON object: sometimes DELETE responses are empty.
/*------------------------------------------------------------------------------
Purpose: Delete a Post
Notes: DELETE to https://jsonplaceholder.typicode.com/posts/1
------------------------------------------------------------------------------*/
METHOD PUBLIC VOID Delete( ):
DEFINE VARIABLE oRequest AS IHttpRequest NO-UNDO.
DEFINE VARIABLE oResponse AS IHttpResponse NO-UNDO.
DEFINE VARIABLE oLib AS IHttpClientLibrary NO-UNDO.
ASSIGN
oRequest = RequestBuilder:DELETE('https://jsonplaceholder.typicode.com/posts/1')
:Request
oLib = ClientLibraryBuilder:Build()
:ServerNameIndicator('jsonplaceholder.typicode.com')
:Library
oResponse = ClientBuilder:Build():UsingLibrary(oLib)
:Client:Execute(oRequest).
//we expect a 200 - OK
IF oResponse:StatusCode <> StatusCodeEnum:Ok:GetValue() AND
oResponse:StatusCode <> StatusCodeEnum:NoContent:GetValue() THEN
UNDO, THROW NEW HttpRequestError(STRING(oResponse:StatusCode),
MethodEnum:DELETE:ToString(),
'https://jsonplaceholder.typicode.com/posts/1').
FINALLY:
DELETE OBJECT oRequest NO-ERROR.
DELETE OBJECT oLib NO-ERROR.
END FINALLY.
END METHOD.
Authentication using a bearer token
For protected APIs, you typically need a Bearer token in the Authorization header.
Tips & Tricks:
- Cache the token if it has a long expiry to avoid repeated authentication calls.
- Always validate the response from the token endpoint (200 OK, correct JSON structure).
- Implement a controlled retry mechanism for token retrieval in case of temporary failures (network issues, timeouts, etc).
Common mistakes:
- Forgetting the Bearer prefix: some APIs reject the token without it.
- Not refreshing expired tokens, causing 401 Unauthorized.
Expected flow:
1. Request a token from the authentication endpoint.
/*------------------------------------------------------------------------------
Purpose: Get the Bearer Token
Notes: https://www.quickpickdeal.com/api/Auth/GetBearerToken
------------------------------------------------------------------------------*/
METHOD PRIVATE CHARACTER GetBearerToken( ):
DEFINE VARIABLE oRequest AS IHttpRequest NO-UNDO.
DEFINE VARIABLE oResponse AS IHttpResponse NO-UNDO.
DEFINE VARIABLE oLib AS IHttpClientLibrary NO-UNDO.
DEFINE VARIABLE oEntity AS JsonObject NO-UNDO.
DEFINE VARIABLE oDataResponseJson AS JsonObject NO-UNDO.
ASSIGN
oRequest = RequestBuilder:Get('https://www.quickpickdeal.com/api/Auth/GetBearerToken')
:ContentType("application/json")
:AcceptJson()
:Request
oLib = ClientLibraryBuilder:Build()
:ServerNameIndicator('www.quickpickdeal.com')
:Library
oResponse = ClientBuilder:Build():UsingLibrary(oLib)
:Client:Execute(oRequest).
//we expect a 200 - Ok
IF oResponse:StatusCode <> StatusCodeEnum:Ok:GetValue() THEN
UNDO, THROW NEW HttpRequestError(STRING(oResponse:StatusCode),
MethodEnum:GET:ToString(),
'https://www.quickpickdeal.com/api/Auth/GetBearerToken').
//we expect a json
IF TYPE-OF(oResponse:Entity, Progress.Json.ObjectModel.JsonObject) THEN
oEntity = CAST(oResponse:Entity, Progress.Json.ObjectModel.JsonObject).
ELSE
UNDO, THROW NEW AppError("Unexpected response type").
IF oEntity:Has("Data") THEN DO:
oDataResponseJson = oEntity:GetJsonObject("Data").
IF oDataResponseJson:Has("Token") THEN
RETURN oDataResponseJson:GetCharacter("Token").
END.
RETURN ?.
FINALLY:
DELETE OBJECT oRequest NO-ERROR.
DELETE OBJECT oLib NO-ERROR.
DELETE OBJECT oEntity NO-ERROR.
DELETE OBJECT oResponse NO-ERROR.
DELETE OBJECT oDataResponseJson NO-ERROR.
END FINALLY.
END METHOD.
2. Include it in subsequent API requests:
/*------------------------------------------------------------------------------
Purpose: Get Using Bearer Auth
Notes:
------------------------------------------------------------------------------*/
METHOD PUBLIC JsonObject GetRequestWithBearerAuth( ):
DEFINE VARIABLE cBearerToken AS CHARACTER NO-UNDO.
DEFINE VARIABLE oRequest AS IHttpRequest NO-UNDO.
DEFINE VARIABLE oResponse AS IHttpResponse NO-UNDO.
DEFINE VARIABLE oLib AS IHttpClientLibrary NO-UNDO.
cBearerToken = THIS-OBJECT:GetBearerToken().
IF cBearerToken = ? THEN
UNDO, THROW NEW AppError("Failed to retrieve bearer token").
ASSIGN
oRequest = RequestBuilder:Get('https://www.quickpickdeal.com/api/Customer/GetCustomerById?id=2')
:AddHeader("Authorization", SUBSTITUTE("Bearer &1", cBearerToken))
:Request
oLib = ClientLibraryBuilder:Build()
:ServerNameIndicator('www.quickpickdeal.com')
:Library
oResponse = ClientBuilder:Build():UsingLibrary(oLib)
:Client:Execute(oRequest).
//we expect a 200 - OK
CASE oResponse:StatusCode:
WHEN StatusCodeEnum:OK:GetValue() THEN DO:
IF TYPE-OF(oResponse:Entity, Progress.Json.ObjectModel.JsonObject) THEN
RETURN CAST(oResponse:Entity, Progress.Json.ObjectModel.JsonObject).
ELSE
UNDO, THROW NEW AppError("Unexpected response type").
END.
OTHERWISE
UNDO, THROW NEW HttpRequestError(STRING(oResponse:StatusCode),
MethodEnum:GET:ToString(),
'https://www.quickpickdeal.com/api/Customer/GetCustomerById?id=2').
END CASE.
FINALLY:
DELETE OBJECT oRequest NO-ERROR.
DELETE OBJECT oLib NO-ERROR.
END FINALLY.
END METHOD.
Author: Alexandru Trasca, Senior Developer
Alex is a Senior OE Developer with a strong passion for technology and problem-solving. Always ready to lend a hand, Alex is not only a talented developer but also a supportive mentor to his teammates. When he’s not coding or solving complex technical challenges, you’ll likely find him rewatching his favorite movie—Lord of the Rings.



