API reference

Push records

Push a batch of records into the open sync, or check one with ?validate=1.

Generated from /openapi.json.

Request

POSThttps://www.postaged.com/api/integrations/push/v1/records

Pushes a batch into the open sync. One bad record never stops the others: the response counts what was accepted and lists one rejection per refused record. Values dropped from accepted records are grouped by field and code, each group with a count and up to 5 example record ids; every dropped value is listed one per record by List sync rejections with type=warning. With ?validate=1 the batch is checked against the same rules and nothing is written; a dry run needs no open sync and no Idempotency-Key. A body over the platform's own cap can get the platform's 413 without the documented error body.

curl https://www.postaged.com/api/integrations/push/v1/records \
  -H "Authorization: Bearer $POSTAGED_API_KEY" \
  -H "Idempotency-Key: 2026-10-03-records-1" \
  -H "Content-Type: application/json" \
  -d '{
    "records": [
      {
        "external_id": "crm-1042",
        "fields": {
          "email": "dana@example.com",
          "deal_value": "1500"
        }
      },
      {
        "external_id": "crm-0977",
        "op": "delete"
      }
    ]
  }'

Authentication

An integration key (it starts with pk_int_), created with New key on the integration's page. Send it in the Authorization: Bearer header. Only that integration's key works here; any other key gets 401 invalid_key.

Headers
Authorization: Bearer $POSTAGED_API_KEY
Content-Type: application/json

Parameters

  • validatestring (query)Optional
    Send 1 to check the batch and write nothing. One of 1.
  • Idempotency-Keystring (header)Optional
    Required on a push; not read on a ?validate=1 dry run. A new value per request, the same value on a retry of that request. Up to 200 characters.

Request body

  • recordsarray of objectsRequired
    Up to 1,000 items.
    • external_idstringRequired
      Your system's stable id for the contact. Up to 128 characters.
    • opstringOptional
      One of upsert or delete. Default upsert.
    • fieldsobjectOptional
      Values by declared field key. A null clears nothing; it is sent as blank.
Request body
{
  "records": [
    {
      "external_id": "crm-1042",
      "fields": { "email": "dana@example.com", "deal_value": "1500" }
    },
    { "external_id": "crm-0977", "op": "delete" }
  ]
}

Check a batch with ?validate=1

Add ?validate=1 to check a batch against the same rules and write nothing. A dry run needs no open sync and no Idempotency-Key, and it answers with the same rejections and warnings a push would.

curl "https://www.postaged.com/api/integrations/push/v1/records?validate=1" \
  -H "Authorization: Bearer $POSTAGED_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "records": [
      {
        "external_id": "crm-1042",
        "fields": {
          "email": "dana@example.com",
          "deal_value": "1500"
        }
      },
      {
        "external_id": "crm-0977",
        "op": "delete"
      }
    ]
  }'

Response 200

The batch was checked (and, without ?validate=1, written).

A push returns:

  • acceptedinteger
  • deletedinteger
  • rejectedinteger
  • rejectionsarray of objects
    • indexinteger
      The record's position in the batch.
    • externalIdstring or null
    • codestring
      One of missing_external_id, invalid_external_id, unknown_field, invalid_type, invalid_email, duplicate_in_batch, or record_too_large.
    • fieldstring
    • messagestring
  • warningsarray of objects
    • indexinteger
      The batch position of the group's first record.
    • externalIdstring
      The group's first record's external_id.
    • codestring
      One of invalid_type.
    • fieldstring
    • messagestring
    • countinteger
      How many accepted records lost a value for this field.
    • externalIdsarray of strings
      Up to 5 of those records' external_id values, in batch order. Up to 5 items.

A ?validate=1 dry run returns:

  • validatedboolean
    Always true.
  • acceptedinteger
  • rejectedinteger
  • rejectionsarray of objects
    • indexinteger
      The record's position in the batch.
    • externalIdstring or null
    • codestring
      One of missing_external_id, invalid_external_id, unknown_field, invalid_type, invalid_email, duplicate_in_batch, or record_too_large.
    • fieldstring
    • messagestring
  • warningsarray of objects
    • indexinteger
      The batch position of the group's first record.
    • externalIdstring
      The group's first record's external_id.
    • codestring
      One of invalid_type.
    • fieldstring
    • messagestring
    • countinteger
      How many accepted records lost a value for this field.
    • externalIdsarray of strings
      Up to 5 of those records' external_id values, in batch order. Up to 5 items.
A push with one rejection and one dropped value200 OK
{
  "accepted": 1,
  "deleted": 1,
  "rejected": 1,
  "rejections": [
    {
      "index": 2,
      "externalId": null,
      "code": "missing_external_id",
      "message": "Every record needs an external_id: it is how this record is recognized on the next sync."
    }
  ],
  "warnings": [
    {
      "index": 0,
      "externalId": "crm-1042",
      "code": "invalid_type",
      "field": "deal_value",
      "message": "\"deal_value\" is declared as number and this value is not one. The record was accepted without it.",
      "count": 1,
      "externalIds": ["crm-1042"]
    }
  ]
}
A ?validate=1 dry run200 OK
{
  "validated": true,
  "accepted": 2,
  "rejected": 0,
  "rejections": [],
  "warnings": []
}

Errors

Errors use the body { "error": { "code", "message", "details" } }; details appears only on some codes. Full tables: Error reference.

A body over 4.25 MB can get the hosting platform's own 413, without the push API's error body.

StatuscodemessageWhat to do
400invalid_jsonThe request body must be JSON.Send the body as JSON with a Content-Type of application/json.
400invalid_requestSend { records: [...] }.Send the body in the shape the message names, then send the request again.
400missing_idempotency_keySend an Idempotency-Key header so a retried batch is not applied twice.Send an Idempotency-Key header with a new value for each request, and the same value when you retry that request.
401invalid_keyThat API key is not valid for this integration.Send this integration's key (it starts with pk_int_) in the Authorization: Bearer header. A revoked key, another kind of key, or a key for a removed integration gets this error.
403integration_unavailableThis integration is not accepting data. Contact support if you think that is wrong.The integration is paused or disconnected in Postaged. Ask the owner to resume it, then send the request again.
409no_open_syncBegin a sync before pushing records, so a full sync knows what it saw.Begin a sync with POST /api/integrations/push/v1/sync first. A sync left open expires after 24 hours.
409idempotency_key_reusedThat Idempotency-Key was used for a different batch. Use a new key.Use a new Idempotency-Key for this request. Reuse a key only to retry the same request with the same body.
409in_progressThat batch is still being processed.The first attempt is still running. Wait a few seconds, then retry with the same Idempotency-Key to get its response.
413too_many_recordsYou sent 1001 records. Send at most 1000 per batch.Split the batch so each one holds at most the records in details.limit.
413payload_too_largeA batch may be at most 4456448 bytes.Split the batch so each body stays at or under the bytes in details.limit.
429rate_limitedToo many requests. Slow down and retry.Wait the number of seconds in the Retry-After header, then send the request again.
invalid_json400
{
  "error": {
    "code": "invalid_json",
    "message": "The request body must be JSON."
  }
}
invalid_request400
{
  "error": { "code": "invalid_request", "message": "Send { records: [...] }." }
}
missing_idempotency_key400
{
  "error": {
    "code": "missing_idempotency_key",
    "message": "Send an Idempotency-Key header so a retried batch is not applied twice."
  }
}
invalid_key401
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="Postaged"

{
  "error": {
    "code": "invalid_key",
    "message": "That API key is not valid for this integration."
  }
}
integration_unavailable403
{
  "error": {
    "code": "integration_unavailable",
    "message": "This integration is not accepting data. Contact support if you think that is wrong."
  }
}
no_open_sync409
{
  "error": {
    "code": "no_open_sync",
    "message": "Begin a sync before pushing records, so a full sync knows what it saw."
  }
}
idempotency_key_reused409
{
  "error": {
    "code": "idempotency_key_reused",
    "message": "That Idempotency-Key was used for a different batch. Use a new key."
  }
}
in_progress409
{
  "error": {
    "code": "in_progress",
    "message": "That batch is still being processed."
  }
}
too_many_records413
{
  "error": {
    "code": "too_many_records",
    "message": "You sent 1001 records. Send at most 1000 per batch.",
    "details": { "limit": 1000, "received": 1001 }
  }
}
payload_too_large413
{
  "error": {
    "code": "payload_too_large",
    "message": "A batch may be at most 4456448 bytes.",
    "details": { "limit": 4456448 }
  }
}
rate_limited429
HTTP/1.1 429 Too Many Requests
Retry-After: 60

{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Slow down and retry."
  }
}