Marketing Cloud REST API: OAuth 2.0 authentication and writing to a Data Extension
Create the Installed Package, get a server-to-server OAuth 2.0 token, reuse the token and write rows to a Data Extension through the REST API.
Integrating an external system with Marketing Cloud Engagement always starts with the same step: getting an access token. This tutorial covers server-to-server authentication (OAuth 2.0, client credentials) and a first real use case: writing rows to a Data Extension.
1. Installed Package
In Setup → Apps → Installed Packages:
- Click New and give it a name (e.g. "ERP integration").
- Add Component → API Integration → Server-to-Server.
- Select only the permissions the integration needs. To write to Data Extensions: Data → Data Extensions: Read and Write.
- Save and note down the Client Id, Client Secret, Authentication Base URI and REST Base URI.
2. Requesting the token
curl -X POST "https://YOUR_SUBDOMAIN.auth.marketingcloudapis.com/v2/token" \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"account_id": "BUSINESS_UNIT_MID"
}'account_idis the MID of the Business Unit you want to act in. Without it, the token applies to the package's Business Unit.- The subdomain is the same one that appears in the package's Authentication Base URI.
The response:
{
"access_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 1079,
"scope": "data_extensions_read data_extensions_write",
"rest_instance_url": "https://YOUR_SUBDOMAIN.rest.marketingcloudapis.com/",
"soap_instance_url": "https://YOUR_SUBDOMAIN.soap.marketingcloudapis.com/"
}3. Reuse the token
The token is valid for about 18 minutes (expires_in, in seconds). Requesting a new token on every call is the
most common mistake: it slows the integration down and can run into request limits.
The right pattern:
- store the
access_tokenand the time it expires; - reuse it while more than a minute or two is left before it expires;
- request a new one only when it is close to expiring, or when a call returns 401.
Always use the rest_instance_url from the response for the following calls.
4. Writing to a Data Extension
The row upsert endpoint uses the Data Extension's external key:
curl -X POST "https://YOUR_SUBDOMAIN.rest.marketingcloudapis.com/hub/v1/dataevents/key:ORDERS_API/rowset" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '[
{
"keys": { "Number": "10021" },
"values": { "Email": "ana@example.com", "Amount": "189.90", "Status": "Shipped" }
}
]'keyscarries the primary key columns;values, the rest.- If a row with that key already exists, it is updated; if not, it is created.
- Send several rows in the same array instead of one call per row.
Common errors
| Response | Likely cause |
|---|---|
| 401 on the token | Wrong Client Id or Secret, or a subdomain from another account |
| 401 on a call | Expired token: request a new one |
| 403 | The package lacks the permission for that operation, or account_id points to a Business Unit without access |
| 404 on the rowset | Wrong Data Extension external key (it is not the name) |
Summary
A server-to-server Installed Package, a token reused until it is close to expiring, and the rest_instance_url from
the response: with that, any Marketing Cloud integration is stable. The /hub/v1/dataevents upsert is the most direct
way to keep a Data Extension in sync with another system.
Related
| Title | Type | Platform | Belt | Reading |
|---|---|---|---|---|
| Transactional Messaging API: real-time transactional email in Marketing Cloud | Tutorial | Marketing Cloud Engagement | Brown | 3 min |
| AttributeValue and empty fields: AMPscript personalization that never breaks a send | Tutorial | Marketing Cloud Engagement | White | 3 min |
| How to use LookupRows in AMPscript (and when to switch to Lookup or LookupOrderedRows) | Tutorial | Marketing Cloud Engagement | Yellow | 3 min |
| Data Views in SQL: who opened or clicked in the last 30 days | Tutorial | Marketing Cloud Engagement | Green | 3 min |