Developer docs
How a store that does not use PostalMate loads its mailbox directory into WeShip
A mailbox directory lists every box in a store and who holds it. Stores that run PostalMate sync it automatically. Every other store sends it as one CSV file, which an HQ administrator uploads in the console, or as the same data in JSON over a REST endpoint. Both ways use the same rules, so a file that passes a check will apply the same way.
Every import is first checked without changing anything (a dry run). The response lists what would be added, changed and inactivated, and any errors by row and column. Apply a file only when it has no errors. Error messages never repeat the values you sent.
The file is UTF-8 with a header row. Use exactly these column names; the order does not matter. Columns that do not apply to a row stay empty. A file may hold up to 20,000 rows.
| Column | Required | Rule |
|---|---|---|
| spec_version | Always | The number 1, on every row. |
| mailbox_number | Always | The number on the box, up to 40 characters. Several rows may share one number: one row per holder. |
| mailbox_status | Always | active, past_due, closed or available. Every row of one mailbox must have the same status. |
| holder_type | Unless available | primary, additional or business. Empty for an available mailbox. A mailbox has at most one primary. |
| first_name | Primary and additional | Up to 100 characters. |
| last_name | Primary and additional | Up to 100 characters. |
| company_name | Business | Up to 200 characters. |
| No | Contact email of the holder. Only the primary holder's email is stored (the first holder when no row is primary); it is ignored on other rows. | |
| phone_e164 | No | Contact phone with country code, for example +15555550100. Only the primary holder's phone is stored (the first holder when no row is primary); it is ignored on other rows. |
| renewal_date | No | ISO 8601 date, for example 2027-01-31. |
| size | No | Free-text box size, for example Small. |
| external_id | No | Your own stable id for the mailbox, up to 100 characters. It lets a box keep its identity if its number changes. |
A mailbox with several holders has one row per holder. An available mailbox has a single row and no holder. A closed mailbox is stored as terminated.
The names, emails and phone numbers below are invented. Box 101 has two holders, 102 is a business that is past due, and 103 is available.
spec_version,mailbox_number,mailbox_status,holder_type,first_name,last_name,company_name,email,phone_e164,renewal_date,size,external_id
1,101,active,primary,Ada,Example,,ada@example.test,+15555550100,2027-01-31,Small,box-101
1,101,active,additional,Bo,Example,,,,2027-01-31,,box-101
1,102,past_due,business,,,Sample Supply Co,office@example.test,,2026-11-15,Medium,
1,103,available,,,,,,,,Small,
Merge adds new mailboxes and updates the ones in the file; other mailboxes are left alone. Replace treats the file as the complete directory: managed mailboxes missing from it become inactive. A replace that would inactivate more than half of the store's managed mailboxes is refused, and nothing is written. Mailboxes that were not created by a directory import are never changed by an import.
The same rows as JSON, described by a draft 2020-12 JSON Schema: mailbox-directory-v1.schema.json. The schema describes the shape of the file. The server checks more than a schema can express, such as one primary holder per mailbox, the same status on every row of a mailbox, and unique mailbox numbers and external ids, so a file that passes the schema can still be rejected.
{
"spec_version": 1,
"rows": [
{
"mailbox_number": "101",
"mailbox_status": "active",
"holder_type": "primary",
"first_name": "Ada",
"last_name": "Example",
"email": "ada@example.test",
"phone_e164": "+15555550100",
"renewal_date": "2027-01-31",
"size": "Small",
"external_id": "box-101"
},
{
"mailbox_number": "101",
"mailbox_status": "active",
"holder_type": "additional",
"first_name": "Bo",
"last_name": "Example",
"renewal_date": "2027-01-31",
"external_id": "box-101"
},
{
"mailbox_number": "102",
"mailbox_status": "past_due",
"holder_type": "business",
"company_name": "Sample Supply Co",
"email": "office@example.test",
"renewal_date": "2026-11-15",
"size": "Medium"
},
{
"mailbox_number": "103",
"mailbox_status": "available",
"size": "Small"
}
]
}A store owner or manager sends the JSON with PUT /v1/stores/{store-id}/mailbox-directory. Use mode=merge or mode=replace. Without dry_run=false the call is only a check. A request with errors is refused with 422 when it is not a dry run, a replace over the safety limit gets 409, and a store id that is not yours gets 404. Download the full error list from GET /v1/stores/{store-id}/mailbox-directory/sync-runs/{run-id}/errors.csv.
curl -X PUT \
"https://atlas-api.weshipstore.com/v1/stores/<store-id>/mailbox-directory?mode=merge&dry_run=true" \
-H "authorization: Bearer <token>" \
-H "content-type: application/json" \
--data @directory.jsonThis is version 1, and every row says so in spec_version. Within version 1 we only add optional columns, and we announce them here first. Anything that would break an existing file, such as renaming or removing a column or changing a rule, gets a new version number and a new schema file, and version 1 keeps working. A file with an unsupported spec_version is refused with the code unsupported_spec_version.
Need help? Contact us.
Your privacy choices
Essential cookies keep the site working. With your permission, analytics helps us improve it.