# PostEverywhere Testing Guide > How to test your PostEverywhere API integration safely: dedicated test accounts, schedule-then-delete, and verifying media before attaching. **Source:** https://posteverywhere.ai/docs/testing **Section:** Core concepts **API reference:** https://posteverywhere.ai/docs/api/reference --- PostEverywhere does not currently have a separate sandbox environment or a bulk dry-run endpoint. The strategies below let you test your integration safely without publishing live posts to your real audience. ## Four ways to test without publishing ### 1. Create Dedicated Test Accounts Connect social accounts that you use exclusively for testing on each platform. For example, create a private X account, a test Instagram business account, and a test LinkedIn page. Test posts publish to accounts your audience never sees, so mistakes are invisible. ### 2. Schedule-Then-Delete Create posts scheduled far in the future (e.g. one year from now), inspect them via the API, then delete them before they publish. This exercises the full create / read / delete flow without any real publishing risk: ```bash # Schedule a post for next year curl -X POST https://app.posteverywhere.ai/api/v1/posts \ -H "Authorization: Bearer pe_live_abc123..." \ -H "Content-Type: application/json" \ -d '{ "content": "Test post - will be deleted", "account_ids": [123], "scheduled_for": "2027-04-15T10:00:00Z" }' # Inspect the post (use the id returned above) curl https://app.posteverywhere.ai/api/v1/posts/{post_id} \ -H "Authorization: Bearer pe_live_abc123..." # Delete before it publishes curl -X DELETE https://app.posteverywhere.ai/api/v1/posts/{post_id} \ -H "Authorization: Bearer pe_live_abc123..." ``` ### 3. Verify Media Before Attaching Upload media and confirm it processes successfully before using it in posts: ```bash # Option A: One-call URL import (images ready instantly; MP4 video imports async — poll until ready) curl -X POST https://app.posteverywhere.ai/api/v1/media/upload-from-url \ -H "Authorization: Bearer pe_live_abc123..." \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/hero.webp"}' # Option B: 3-step flow (for local files) # Step 1: Request a presigned URL (JSON body; required: filename, content_type, size). # Images can alternatively be sent as multipart/form-data in a single step. curl -X POST https://app.posteverywhere.ai/api/v1/media/upload \ -H "Authorization: Bearer pe_live_abc123..." \ -H "Content-Type: application/json" \ -d '{"filename": "test-image.jpg", "content_type": "image/jpeg", "size": 524288}' # Step 2: PUT/POST the bytes to the returned upload_url # Step 3: POST /api/v1/media/{media_id}/complete to mark it ready # Check status (poll until "ready") — for videos which may still be processing thumbnails curl https://app.posteverywhere.ai/api/v1/media/{media_id} \ -H "Authorization: Bearer pe_live_abc123..." # Response when ready: # { "media_id": "{media_id}", "media_status": "ready", "ready": true, "mime_type": "image/jpeg", ... } ``` Only attach media to posts once the status is `ready`. See [Media Requirements](/docs/media-requirements) for file size and dimension limits. ### 4. Validate Your Payload Client-Side First Before sending to the API, check your payload for the common mistakes that cause `400` errors: - `content` is present and non-empty - `account_ids` is a non-empty array of integers (not strings) - `scheduled_for` (if set) is a valid ISO 8601 datetime in UTC and in the future (any future instant is accepted; `POST /v1/posts/{id}/schedule` requires at least 1 minute ahead). Never use `scheduled_at`: it is a deprecated alias and new integrations should use `scheduled_for`. - `media_ids` (if set) are UUIDs from your media uploads. The only accepted media field name is `media_ids`. Sending `media`, `media_id`, `mediaId`, or `attachments` returns `400 invalid_field_name`. - `timezone` (if set) is a valid IANA name like `America/New_York`. It is display metadata only and does not affect when the post actually fires. All field names use `snake_case`. `accountIds`, `mediaIds`, and `platformContent` are also accepted as camelCase aliases for back-compat, but `snake_case` is canonical. ## Rate limits apply to test calls too Test requests count against your rate limits. Every call counts against the per-key API limit (60 per minute, 1,000 per hour). Posts also have their own publishing limits: scheduled posts (a future `scheduled_for`) are capped at 100 per minute and 1,000 per hour with no daily cap; immediate posts at 60 per minute, 200 per hour and 1,000 per day. Plan your test loops accordingly to avoid hitting `rate_limit_exceeded` errors. If you are running automated test suites, add a short delay between requests: ```javascript // Simple delay between test API calls async function testWithDelay(calls, delayMs = 1100) { for (const call of calls) { await call(); await new Promise((resolve) => setTimeout(resolve, delayMs)); } } ``` See [Rate Limits](/docs/rate-limits) for the full details on limits and headers. ## Testing against the Pinterest sandbox PostEverywhere does not offer a per-account Pinterest sandbox. Test Pin creation with the schedule-then-delete approach above, or pin to a dedicated private test board. ## Checklist before going live Before going live, verify each of these: - [ ] **Authentication works.** Your API key returns a `200` on `GET /accounts`. - [ ] **Error handling is implemented.** Your code handles 400, 401, 429, and 500 errors distinctly (see [Error Handling](/docs/errors)). - [ ] **Retry logic uses exponential backoff.** Retries only fire for 429 and 5xx responses. - [ ] **Rate limits are respected.** You are not exceeding your per-minute limit. - [ ] **Media uploads are validated.** You check that media status is `ready` before attaching to posts. - [ ] **Scheduling times are in the future.** All `scheduled_for` values are in the future and expressed in UTC. - [ ] **Field names are `snake_case` and canonical.** `content`, `account_ids`, `scheduled_for`, `media_ids`, `platform_content`, `timezone`. Never `scheduled_at`, `media`, `media_id`, `mediaId`, or `attachments`. - [ ] **Round-trip works.** A post you fetch via `GET /v1/posts/{id}` can be POSTed back as-is (top-level fields) to create a clone. - [ ] **Post results are polled.** You track publishing results via [Get Post Results](/docs/api/get-post-results) and handle failures. - [ ] **Scopes are correct.** Your API key has the minimum scopes needed (see [Scopes](/docs/scopes)). ## Related testing and error pages - [Quick Start](/docs/quick-start): first API call walkthrough - [Error Handling](/docs/errors): error codes and retry strategies - [Rate Limits](/docs/rate-limits): request limits and headers **Related:** [testing webhook endpoints](/docs/webhooks)