{"openapi":"3.1.0","info":{"title":"On Me External API","version":"1.0.0","description":"# Introduction\n\nBuy and send On Me gifts programmatically. New here? Start with the [quickstart](/docs/quickstart).\n\nAuthenticate with a secret key as a bearer token: `Authorization: Bearer omk-...`. See [Authentication](/docs/authentication).\n\nThere is one base URL for both environments, and your key decides which one answers: `omk-` reaches live, `omk-sandbox-` reaches the sandbox. Everything below works the same in each, so moving an integration to production is a key swap and nothing else.\n\nThe sandbox additionally answers the **Sandbox** endpoints below, and lets you choose what becomes of a gift by the recipient address you send to — delivered, bounced, opened, claimed, spent, or a failed order. Operations badged _Mockable_ are the ones that address steers. See [Testing in the sandbox](/docs/sandbox).\n\nSuccessful responses return the resource itself, or `{ \"object\": \"list\", \"data\": [...], \"has_more\": false }` for lists. Errors return a single `error` object; see [Errors](#errors).\n\n`POST /v1/orders` requires an `Idempotency-Key` header. Retry a timed-out request with the same key and you get the original order, never a second charge. See [Idempotency and retries](/docs/idempotency).\n\n# Errors\n\n## Handling errors\n\nEvery error response has the same shape:\n\n```json\n{\n  \"error\": {\n    \"type\": \"invalid_request_error\",\n    \"code\": \"parameter_invalid\",\n    \"message\": \"Invalid email address\",\n    \"param\": \"gifts[0].delivery.email\",\n    \"doc_url\": \"https://business.onme.com/docs/reference#errors/parameter_invalid\",\n    \"request_id\": \"req-3kTq9x...\"\n  }\n}\n```\n\n- Branch on `type` and `code`. Never parse `message`; it is for humans and may change.\n- `param` names the offending field when there is one, as a path such as `gifts[0].amount`.\n- Log `request_id` (also sent as the `Request-Id` header on every response) and quote it when contacting support.\n\nAn error in this shape means **nothing happened**: no order, no charge, no gifts. A timeout, a dropped connection or a gateway error without an `error` object means the outcome is unknown: retry with the same `Idempotency-Key`.\n\n## Retrying\n\nEvery code below says whether to retry it:\n\n| Retry | Meaning |\n| --- | --- |\n| `yes` | Send the same request again, unchanged, after a short backoff. We send `Retry-After` with these. |\n| `after_fix` | Fix what the error names (the request, your key, your balance or your account setup), then retry. For `POST /v1/orders`, keep the same `Idempotency-Key`. |\n| `no` | Sending it again cannot succeed. |\n\nBack off exponentially with jitter (for example 0.5s, 1s, 2s, capped at 10s) and honour `Retry-After` when present.\n\n## Error codes\n\n### api_key_missing\n\n**401** · `authentication_error` · Retry: after fix\n\nNo API key was provided. Send it as a bearer token in the Authorization header.\n\nSend `Authorization: Bearer omk-...` on every request.\n\n### api_key_invalid\n\n**401** · `authentication_error` · Retry: after fix\n\nThe API key is not recognised.\n\nCheck for a truncated or mistyped key. An org admin can create a new one under Account → Developers in the business portal.\n\n### api_key_revoked\n\n**401** · `authentication_error` · Retry: after fix\n\nThe API key was valid but has since been revoked.\n\nSwitch to a current key. An org admin can create one under Account → Developers in the business portal.\n\n### api_key_environment_mismatch\n\n**401** · `authentication_error` · Retry: after fix\n\nThe request reached the wrong environment for its key. You should not see this: there is one base URL, and your key decides which deployment serves the request. It means the request bypassed that routing, or reached a deployment host directly.\n\nCall the documented base URL rather than a deployment host directly. If you already are, this is a fault on our side — quote the request_id.\n\n### organization_suspended\n\n**403** · `authentication_error` · Retry: no\n\nThe organization owning this API key is suspended. Contact support.\n\n### resource_missing\n\n**404** · `invalid_request_error` · Retry: no\n\nNo such resource. Also returned for a resource owned by another organization, so that IDs are not enumerable.\n\n### parameter_invalid\n\n**422** · `invalid_request_error` · Retry: after fix\n\nA parameter failed validation, or a required one such as the Idempotency-Key header is missing. `param` identifies it.\n\nFix the parameter named in `param` (for example `gifts[0].delivery.phone`) and send the request again. For `POST /v1/orders`, keep the same Idempotency-Key.\n\n### cursor_invalid\n\n**422** · `invalid_request_error` · Retry: after fix\n\nThe cursor in starting_after or ending_before does not refer to a resource you can access.\n\n### request_body_invalid\n\n**400** · `invalid_request_error` · Retry: after fix\n\nThe request body could not be parsed as JSON.\n\n### request_body_too_large\n\n**413** · `invalid_request_error` · Retry: after fix\n\nThe request body exceeds the 1MB limit.\n\n### gift_already_redeemed\n\n**422** · `invalid_request_error` · Retry: no\n\nThe gift has already been redeemed by its recipient.\n\n### gift_already_delivered\n\n**422** · `invalid_request_error` · Retry: no\n\nThe gift has already been delivered.\n\n### gift_resend_limit_reached\n\n**422** · `invalid_request_error` · Retry: no\n\nThe gift has reached its delivery-retry limit.\n\n### gift_delivery_not_updatable\n\n**422** · `invalid_request_error` · Retry: no\n\nDelivery can only be redirected for a gift whose delivery has failed and that is not yet redeemed.\n\n### idempotency_key_reused\n\n**409** · `invalid_request_error` · Retry: no\n\nThis Idempotency-Key was already used for a request with a different body. Use a new key for a new order.\n\nGenerate a new Idempotency-Key for a new order. Reuse a key only to retry the exact same request.\n\n### organization_not_configured\n\n**422** · `invalid_request_error` · Retry: after fix\n\nThe organization has no active member for orders to be attributed to. Add an active admin, or set a billing contact who is one, in the On Me business portal.\n\nIn the business portal, make sure your organization has an active admin, or set a billing contact who is one.\n\n### insufficient_balance\n\n**402** · `insufficient_balance_error` · Retry: after fix\n\nThe organization balance cannot cover this order. Top up and retry; no order was created and nothing was charged.\n\nAdd funds in the On Me business portal under Billing, then retry with the same Idempotency-Key.\n\n### rate_limit_exceeded\n\n**429** · `rate_limit_error` · Retry: yes\n\nToo many requests. Retry after the interval given in the Retry-After header.\n\nBack off for the number of seconds in `Retry-After`, then retry.\n\n### sandbox_missing\n\n**404** · `invalid_request_error` · Retry: after fix\n\nNo sandbox environment exists for this organization, or the requested sandbox belongs to another organization.\n\n### gift_not_claimed\n\n**422** · `invalid_request_error` · Retry: after fix\n\nThe gift has not been claimed yet, and the requested operation requires a claimed gift.\n\n### sandbox_endpoint_only\n\n**403** · `invalid_request_error` · Retry: no\n\nA `/v1/sandbox` endpoint was called with a live key. These endpoints fabricate delivery and redemption state, so they answer only in the sandbox. They are documented here because they share a base URL with everything else, not because they exist in production.\n\nSend the request with a sandbox key (`omk-sandbox-`). There is no live equivalent — in production these transitions are caused by the recipient.\n\n### gift_delivery_not_simulatable\n\n**422** · `invalid_request_error` · Retry: after fix\n\nSandbox only. Delivery can only be simulated for a gift On Me sends itself (email or phone); a link gift is delivered by you.\n\n### resource_locked\n\n**409** · `api_error` · Retry: yes\n\nAnother operation holds this resource. Nothing happened; retry after a short wait.\n\n### idempotency_in_progress\n\n**409** · `api_error` · Retry: yes\n\nA request with this Idempotency-Key is still in progress. Retry after a short wait to get its result.\n\nWait a second and retry with the same key; you will get the original result.\n\n### internal_error\n\n**500** · `api_error` · Retry: yes\n\nAn unexpected error occurred on our side. It is safe to retry after a short wait; quote the request_id if it persists.\n\nRetry after a short wait, with the same Idempotency-Key for `POST /v1/orders`. If it keeps happening, contact support with the `request_id`."},"servers":[{"url":"https://api.onme.com"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Categories","description":"The gift catalog."},{"name":"Orders","description":"Purchases of one or more gifts."},{"name":"Gifts","description":"Individual gifts and their delivery."},{"name":"Sandbox","description":"Simulation endpoints, answered only for a sandbox key. They drive state that in production only a recipient can cause — delivery settling, opening, claiming, spending — plus a reset. See [Testing in the sandbox](/docs/sandbox)."},{"name":"Meta","description":"Unauthenticated service endpoints."}],"paths":{"/v1/categories":{"get":{"operationId":"listCategories","summary":"List categories","description":"Returns the full catalog in one response. Uses the same list envelope as the other collections, but is not paginated — the catalog is small and bounded, so `has_more` is always false.","tags":["Categories"],"responses":{"200":{"description":"The full category catalog.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryList"}}}},"401":{"description":"The API key is missing, invalid, or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The organization is suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry after the interval in Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"default":{"description":"An unexpected error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/orders":{"get":{"operationId":"listOrders","summary":"List orders","description":"Orders belonging to the calling organization, most recent first. Includes orders still `processing` as well as `processed` ones; orders that were never paid for are not returned.","tags":["Orders"],"parameters":[{"name":"limit","in":"query","required":false,"description":"Page size for the order list.","schema":{"description":"Page size, 1–100. Defaults to 10.","default":10,"type":"integer","minimum":1,"maximum":100}},{"name":"starting_after","in":"query","required":false,"description":"Return results after this resource ID. Mutually exclusive with ending_before.","schema":{"description":"Return results after this resource ID. Mutually exclusive with ending_before.","type":"string","minLength":1,"pattern":"^[A-Za-z0-9_-]+$"}},{"name":"ending_before","in":"query","required":false,"description":"Return results before this resource ID. Mutually exclusive with starting_after.","schema":{"description":"Return results before this resource ID. Mutually exclusive with starting_after.","type":"string","minLength":1,"pattern":"^[A-Za-z0-9_-]+$"}}],"responses":{"200":{"description":"A page of orders.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderList"}}}},"401":{"description":"The API key is missing, invalid, or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The organization is suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"A query parameter or cursor is invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry after the interval in Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"default":{"description":"An unexpected error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createOrder","summary":"Create an order","x-badges":[{"name":"Mockable","position":"before"}],"description":"Creates and funds an order of 1–10 gifts, charged to your organization balance in a single debit.\n\nResponds once every gift exists: 201 for a new order, and 200 when an `Idempotency-Key` replay returns the order that key already created. If we hit a problem after charging you, the order comes back `processing` instead; it is paid for and we will finish it.\n\nEvery request must carry an `Idempotency-Key` header. See [Idempotency and retries](/docs/idempotency) and [Errors](#errors).\n\n**Mockable in the sandbox.** The recipient address you send to decides what happens: a gift that is delivered, bounces, is opened, claimed or spent, an order that stays `processing` for twenty seconds, or a whole-order 402 or 500. See [Testing in the sandbox](/docs/sandbox).","tags":["Orders"],"parameters":[{"name":"Idempotency-Key","in":"header","required":true,"description":"Required. A unique key for this order, up to 255 printable ASCII characters. A retry with the same key and body returns the original order instead of charging again.","schema":{"type":"string","maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"gifts":{"description":"The gifts to create, 1–10. The whole order is funded from your organization balance in one debit.","minItems":1,"maxItems":10,"type":"array","items":{"type":"object","properties":{"amount":{"description":"Gift value in cents, between 1000 and 50000.","examples":[2500],"type":"integer","minimum":1000,"maximum":50000},"category":{"description":"ID of the category, as returned by GET /v1/categories.","examples":["coffee"],"type":"string","minLength":1},"delivery":{"anyOf":[{"type":"object","properties":{"method":{"type":"string","const":"email"},"email":{"description":"Recipient address. Required when method is \"email\".","type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"required":["method","email"]},{"type":"object","properties":{"method":{"type":"string","const":"phone"},"phone":{"description":"Recipient number in international format, such as +14155551234. Required when method is \"phone\". Spaces, dashes and parentheses are accepted; the number is stored in E.164.","type":"string"}},"required":["method","phone"]},{"type":"object","properties":{"method":{"type":"string","const":"link"}},"required":["method"]}]},"personalization":{"description":"Optional names to show the recipient. `sender_name` defaults to your organization name; `recipient_name` is shown only when you supply it.","type":"object","properties":{"recipient_name":{"type":"string","minLength":1,"maxLength":200},"sender_name":{"type":"string","minLength":1,"maxLength":200}}}},"required":["amount","category","delivery"]}}},"required":["gifts"]}}}},"responses":{"200":{"description":"An Idempotency-Key replay: the order this key already created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}},"headers":{"Idempotent-Replayed":{"description":"Present on a replay.","schema":{"type":"string","const":"true"}}}},"201":{"description":"The new order: `processed`, with its gifts, or `processing` if we hit a problem after charging you.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"400":{"description":"The request body could not be parsed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The API key is missing, invalid, or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The organization balance cannot cover this order. Nothing was created or charged.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The organization is suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"A request with this key is still in progress (`idempotency_in_progress`), this key was used with a different body (`idempotency_key_reused`), or another order for your organization was being charged at the same moment (`resource_locked`). Nothing was created or charged.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"The request body exceeds the 1MB limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The Idempotency-Key header is missing or invalid, the request body failed validation, or the organization has no active member for orders to be attributed to.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry after the interval in Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"An unexpected error. Nothing was created or charged, so it is safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"default":{"description":"An unexpected error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/orders/{order_id}":{"get":{"operationId":"retrieveOrder","summary":"Retrieve an order","tags":["Orders"],"parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The order.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"401":{"description":"The API key is missing, invalid, or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The organization is suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it belongs to another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry after the interval in Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"default":{"description":"An unexpected error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/gifts":{"get":{"operationId":"listGifts","summary":"List gifts","description":"Gifts belonging to the calling organization, most recent purchase first. Redemption links in a list response are not refreshed — retrieve a gift individually to be sure of a working link.","tags":["Gifts"],"parameters":[{"name":"limit","in":"query","required":false,"description":"Page size for the gift list.","schema":{"description":"Page size, 1–100. Defaults to 10.","default":10,"type":"integer","minimum":1,"maximum":100}},{"name":"starting_after","in":"query","required":false,"description":"Return results after this resource ID. Mutually exclusive with ending_before.","schema":{"description":"Return results after this resource ID. Mutually exclusive with ending_before.","type":"string","minLength":1,"pattern":"^[A-Za-z0-9_-]+$"}},{"name":"ending_before","in":"query","required":false,"description":"Return results before this resource ID. Mutually exclusive with starting_after.","schema":{"description":"Return results before this resource ID. Mutually exclusive with starting_after.","type":"string","minLength":1,"pattern":"^[A-Za-z0-9_-]+$"}}],"responses":{"200":{"description":"A page of gifts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GiftList"}}}},"401":{"description":"The API key is missing, invalid, or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The organization is suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"A query parameter or cursor is invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry after the interval in Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"default":{"description":"An unexpected error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/gifts/{gift_id}":{"get":{"operationId":"retrieveGift","summary":"Retrieve a gift","description":"Retrieving a link-delivered gift whose redemption token has expired mints a fresh one, so the returned link always works.","tags":["Gifts"],"parameters":[{"name":"gift_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The gift.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Gift"}}}},"401":{"description":"The API key is missing, invalid, or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The organization is suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it belongs to another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry after the interval in Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"default":{"description":"An unexpected error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/gifts/{gift_id}/delivery":{"put":{"operationId":"updateGiftDelivery","summary":"Redirect a failed delivery","x-badges":[{"name":"Mockable","position":"before"}],"description":"Re-sends a gift to a different address. Restricted to gifts whose delivery has FAILED and that are not yet redeemed; any other state returns 422. Redirecting a successfully delivered gift would let a caller reroute it, so this is a security constraint rather than a convenience.\n\n**Mockable in the sandbox.** The failed delivery this needs is otherwise unreachable on demand — order to `delivery-failed@sandbox.onme.com`, or call `POST /v1/sandbox/gifts/{gift_id}/deliver` with `{\"outcome\":\"failed\"}`, to get a gift in the one state it accepts. See [Testing in the sandbox](/docs/sandbox).","tags":["Gifts"],"parameters":[{"name":"gift_id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"method":{"type":"string","const":"email"},"email":{"description":"Address to re-send the gift to.","type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"required":["method","email"]},{"type":"object","properties":{"method":{"type":"string","const":"phone"},"phone":{"description":"Number to re-send the gift to, in international format. Stored in E.164.","type":"string"}},"required":["method","phone"]}]}}}},"responses":{"200":{"description":"The updated gift.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Gift"}}}},"400":{"description":"The request body could not be parsed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The API key is missing, invalid, or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The organization is suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it belongs to another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"The request body exceeds the 1MB limit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The gift is not in a state whose delivery can be redirected, or the body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry after the interval in Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"default":{"description":"An unexpected error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/sandbox/gifts/{gift_id}/deliver":{"post":{"operationId":"simulateGiftDelivery","summary":"Simulate delivery","description":"Settles a pending email or phone delivery as `delivered` or `failed`. Use this on a gift that already exists. A link-delivered gift has no delivery for On Me to settle and returns 422 `gift_delivery_not_simulatable`. A `failed` outcome is what unlocks `PUT /v1/gifts/{gift_id}/delivery`.\n\nSandbox only: a live key gets 403 `sandbox_endpoint_only`. In production this transition is caused by the recipient, and there is no way to ask for it.","tags":["Sandbox"],"x-badges":[{"name":"Sandbox only","position":"before"}],"parameters":[{"name":"gift_id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"outcome":{"description":"What to settle the pending delivery as. Defaults to `delivered`, so an empty body is the happy path.","default":"delivered","type":"string","enum":["delivered","failed"]}}}}}},"responses":{"200":{"description":"The gift, after the transition.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Gift"}}}},"400":{"description":"The request body could not be parsed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The API key is missing, invalid, or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Called with a live key (`sandbox_endpoint_only`) — these endpoints exist only in the sandbox — or the organization is suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it belongs to another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Delivery cannot be simulated for this gift (`gift_delivery_not_simulatable` — the gift is delivered by link, so there is nothing for us to send), or the body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry after the interval in Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"default":{"description":"An unexpected error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/sandbox/gifts/{gift_id}/open":{"post":{"operationId":"simulateGiftOpen","summary":"Simulate the recipient opening a gift","description":"Stamps the gift opened, as a recipient following their link would.\n\nSandbox only: a live key gets 403 `sandbox_endpoint_only`. In production this transition is caused by the recipient, and there is no way to ask for it.","tags":["Sandbox"],"x-badges":[{"name":"Sandbox only","position":"before"}],"parameters":[{"name":"gift_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The gift, after the transition.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Gift"}}}},"401":{"description":"The API key is missing, invalid, or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Called with a live key (`sandbox_endpoint_only`) — these endpoints exist only in the sandbox — or the organization is suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it belongs to another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry after the interval in Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"default":{"description":"An unexpected error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/sandbox/gifts/{gift_id}/claim":{"post":{"operationId":"simulateGiftClaim","summary":"Simulate the recipient claiming a gift","description":"Redeems the gift onto a card. Back-fills the opened timestamp if the gift was never opened, because a claimed-but-never-opened gift is a state production cannot produce and your integration should not have to handle.\n\nSandbox only: a live key gets 403 `sandbox_endpoint_only`. In production this transition is caused by the recipient, and there is no way to ask for it.","tags":["Sandbox"],"x-badges":[{"name":"Sandbox only","position":"before"}],"parameters":[{"name":"gift_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The gift, after the transition.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Gift"}}}},"401":{"description":"The API key is missing, invalid, or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Called with a live key (`sandbox_endpoint_only`) — these endpoints exist only in the sandbox — or the organization is suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it belongs to another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The gift is already redeemed (`gift_already_redeemed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry after the interval in Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"default":{"description":"An unexpected error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/sandbox/gifts/{gift_id}/spend":{"post":{"operationId":"simulateGiftSpend","summary":"Simulate a first spend","description":"Records spend against the claimed gift’s card. Ordering is enforced as it is in production: spending before claiming returns 422 `gift_not_claimed`.\n\nSandbox only: a live key gets 403 `sandbox_endpoint_only`. In production this transition is caused by the recipient, and there is no way to ask for it.","tags":["Sandbox"],"x-badges":[{"name":"Sandbox only","position":"before"}],"parameters":[{"name":"gift_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The gift, after the transition.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Gift"}}}},"401":{"description":"The API key is missing, invalid, or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Called with a live key (`sandbox_endpoint_only`) — these endpoints exist only in the sandbox — or the organization is suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such resource, or it belongs to another organization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The gift has not been claimed yet (`gift_not_claimed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry after the interval in Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"default":{"description":"An unexpected error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/sandbox/reset":{"post":{"operationId":"resetSandbox","summary":"Delete all sandbox data","description":"Deletes every order, gift, balance and balance ledger entry belonging to your organization in the sandbox. Your API keys survive; you will need to add funds again before you can order.\n\nNot reversible and not idempotency-protected. It is scoped to your own sandbox data, which is the only safety property it needs. The counts come back separately rather than summed so an automated setup step can assert on what actually went.\n\nSandbox only: a live key gets 403 `sandbox_endpoint_only`.","tags":["Sandbox"],"x-badges":[{"name":"Sandbox only","position":"before"}],"responses":{"200":{"description":"What was deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxResetResult"}}}},"401":{"description":"The API key is missing, invalid, or revoked.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Called with a live key (`sandbox_endpoint_only`) — this endpoint exists only in the sandbox — or the organization is suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded. Retry after the interval in Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"default":{"description":"An unexpected error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/health":{"get":{"operationId":"health","summary":"Health check","description":"Liveness probe. Requires no authentication.","tags":["Meta"],"security":[],"responses":{"200":{"description":"The API is reachable.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","const":"ok"}},"required":["status"]}}}},"429":{"description":"Rate limit exceeded. Retry after the interval in Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"default":{"description":"An unexpected error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/openapi.json":{"get":{"operationId":"getOpenAPISpec","summary":"Retrieve this specification","description":"Returns this document. Requires no authentication, so a client can read the contract before it holds a key.","tags":["Meta"],"security":[],"responses":{"200":{"description":"The OpenAPI document.","content":{"application/json":{"schema":{"type":"object"}}}},"429":{"description":"Rate limit exceeded. Retry after the interval in Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"default":{"description":"An unexpected error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A secret key: `omk-` for live, `omk-sandbox-` for the sandbox. One base URL serves both, and the key decides which environment answers — including on the `/v1/sandbox` endpoints, which a live key cannot use."}},"schemas":{"GiftDelivery":{"type":"object","properties":{"method":{"type":"string","enum":["email","phone","link"]},"status":{"type":"string","enum":["sent","failed","opened","claimed"]},"email":{"description":"Present only when method is \"email\".","type":"string"},"phone":{"description":"Present only when method is \"phone\". E.164 format.","type":"string"},"link":{"description":"Redemption URL. Present only when method is \"link\". Retrieving a single gift refreshes this if it has expired.","type":"string"}},"required":["method","status"]},"GiftPersonalization":{"type":"object","properties":{"recipient_name":{"type":"string"},"sender_name":{"type":"string"}}},"Gift":{"type":"object","properties":{"gift_id":{"examples":["g-gpsh60pP2JhgzroxWk4AyXfL237_DiKIJQK8ilq2u5g"],"type":"string"},"amount":{"description":"Gift value in cents.","examples":[2500],"type":"integer"},"category":{"description":"ID of the category this gift is for.","examples":["coffee"],"type":"string"},"delivery":{"$ref":"#/components/schemas/GiftDelivery"},"personalization":{"description":"What the recipient sees. `sender_name` is always present, falling back to your organization name when you do not supply one; `recipient_name` appears only when you do.","$ref":"#/components/schemas/GiftPersonalization"}},"required":["gift_id","amount","category","delivery"]},"GiftList":{"type":"object","properties":{"object":{"type":"string","const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Gift"}},"has_more":{"description":"Whether more results exist after this page in the direction of travel.","type":"boolean"}},"required":["object","data","has_more"]},"Order":{"type":"object","properties":{"order_id":{"examples":["o-jfAQKlOHalPHMJkwMYZffJF_6j2OMWSu9svqSIvkLSw"],"type":"string"},"status":{"description":"Whether every gift has been created. `processed` means they all exist. `processing` means the order is paid for and some gifts are still being created; it happens only if we hit a problem after charging you, and the order finishes on its own. Retrying the original request with the same Idempotency-Key returns it.","type":"string","enum":["processing","processed"]},"gifts":{"description":"The order's gifts, in the order you submitted them, so the Nth gift here is the Nth gift you sent.","type":"array","items":{"$ref":"#/components/schemas/Gift"}}},"required":["order_id","status","gifts"]},"OrderList":{"type":"object","properties":{"object":{"type":"string","const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Order"}},"has_more":{"description":"Whether more results exist after this page in the direction of travel.","type":"boolean"}},"required":["object","data","has_more"]},"Category":{"type":"object","properties":{"id":{"description":"Stable identifier for the category. Pass this as `category` when creating a gift.","examples":["coffee"],"type":"string"},"name":{"examples":["Coffee"],"type":"string"},"description":{"description":"Short description of who the category suits.","type":"string"},"artwork":{"description":"URL of the card artwork, or null if none is configured.","anyOf":[{"type":"string"},{"type":"null"}]},"product_details_url":{"description":"Public category details page on onme.com.","type":"string"}},"required":["id","name","description","artwork","product_details_url"]},"CategoryList":{"type":"object","properties":{"object":{"type":"string","const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Category"}},"has_more":{"description":"Whether more results exist after this page in the direction of travel.","type":"boolean"}},"required":["object","data","has_more"]},"SandboxResetResult":{"type":"object","properties":{"object":{"type":"string","const":"sandbox_reset"},"orders_deleted":{"type":"integer"},"gifts_deleted":{"type":"integer"},"balances_deleted":{"type":"integer"},"ledger_entries_deleted":{"description":"Balance ledger entries removed alongside the balances. Counted separately because the balance and its ledger are separate records, and a reset that cleared one but not the other would leave a sandbox that reconciles wrong.","type":"integer"}},"required":["object","orders_deleted","gifts_deleted","balances_deleted","ledger_entries_deleted"]},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"description":"Broad class of error. Clients should branch on this rather than on the HTTP status.","type":"string","enum":["invalid_request_error","authentication_error","insufficient_balance_error","rate_limit_error","api_error"]},"code":{"description":"Stable, machine-readable identifier for the specific error. Safe to branch on.","type":"string","enum":["api_key_missing","api_key_invalid","api_key_revoked","api_key_environment_mismatch","organization_suspended","resource_missing","parameter_invalid","cursor_invalid","request_body_invalid","request_body_too_large","gift_already_redeemed","gift_already_delivered","gift_resend_limit_reached","gift_delivery_not_updatable","idempotency_key_reused","organization_not_configured","insufficient_balance","rate_limit_exceeded","sandbox_missing","gift_not_claimed","sandbox_endpoint_only","gift_delivery_not_simulatable","resource_locked","idempotency_in_progress","internal_error"]},"message":{"description":"Human-readable explanation. Not stable — never branch on this.","type":"string"},"param":{"description":"Path of the offending field, when the error concerns one.","examples":["gifts[0].delivery.phone"],"type":"string"},"doc_url":{"description":"Documentation for this error code.","type":"string"},"request_id":{"description":"Identifier for this request. Present on every error. Quote it when contacting support.","type":"string"}},"required":["type","code","message","request_id"]}},"required":["error"]}}},"x-error-codes":{"api_key_missing":{"type":"authentication_error","http_status":401,"retry":"after_fix","description":"No API key was provided. Send it as a bearer token in the Authorization header."},"api_key_invalid":{"type":"authentication_error","http_status":401,"retry":"after_fix","description":"The API key is not recognised."},"api_key_revoked":{"type":"authentication_error","http_status":401,"retry":"after_fix","description":"The API key was valid but has since been revoked."},"api_key_environment_mismatch":{"type":"authentication_error","http_status":401,"retry":"after_fix","description":"The request reached the wrong environment for its key. You should not see this: there is one base URL, and your key decides which deployment serves the request. It means the request bypassed that routing, or reached a deployment host directly."},"organization_suspended":{"type":"authentication_error","http_status":403,"retry":"no","description":"The organization owning this API key is suspended. Contact support."},"resource_missing":{"type":"invalid_request_error","http_status":404,"retry":"no","description":"No such resource. Also returned for a resource owned by another organization, so that IDs are not enumerable."},"parameter_invalid":{"type":"invalid_request_error","http_status":422,"retry":"after_fix","description":"A parameter failed validation, or a required one such as the Idempotency-Key header is missing. `param` identifies it."},"cursor_invalid":{"type":"invalid_request_error","http_status":422,"retry":"after_fix","description":"The cursor in starting_after or ending_before does not refer to a resource you can access."},"request_body_invalid":{"type":"invalid_request_error","http_status":400,"retry":"after_fix","description":"The request body could not be parsed as JSON."},"request_body_too_large":{"type":"invalid_request_error","http_status":413,"retry":"after_fix","description":"The request body exceeds the 1MB limit."},"gift_already_redeemed":{"type":"invalid_request_error","http_status":422,"retry":"no","description":"The gift has already been redeemed by its recipient."},"gift_already_delivered":{"type":"invalid_request_error","http_status":422,"retry":"no","description":"The gift has already been delivered."},"gift_resend_limit_reached":{"type":"invalid_request_error","http_status":422,"retry":"no","description":"The gift has reached its delivery-retry limit."},"gift_delivery_not_updatable":{"type":"invalid_request_error","http_status":422,"retry":"no","description":"Delivery can only be redirected for a gift whose delivery has failed and that is not yet redeemed."},"idempotency_key_reused":{"type":"invalid_request_error","http_status":409,"retry":"no","description":"This Idempotency-Key was already used for a request with a different body. Use a new key for a new order."},"organization_not_configured":{"type":"invalid_request_error","http_status":422,"retry":"after_fix","description":"The organization has no active member for orders to be attributed to. Add an active admin, or set a billing contact who is one, in the On Me business portal."},"insufficient_balance":{"type":"insufficient_balance_error","http_status":402,"retry":"after_fix","description":"The organization balance cannot cover this order. Top up and retry; no order was created and nothing was charged."},"rate_limit_exceeded":{"type":"rate_limit_error","http_status":429,"retry":"yes","description":"Too many requests. Retry after the interval given in the Retry-After header."},"sandbox_missing":{"type":"invalid_request_error","http_status":404,"retry":"after_fix","description":"No sandbox environment exists for this organization, or the requested sandbox belongs to another organization."},"gift_not_claimed":{"type":"invalid_request_error","http_status":422,"retry":"after_fix","description":"The gift has not been claimed yet, and the requested operation requires a claimed gift."},"sandbox_endpoint_only":{"type":"invalid_request_error","http_status":403,"retry":"no","description":"A `/v1/sandbox` endpoint was called with a live key. These endpoints fabricate delivery and redemption state, so they answer only in the sandbox. They are documented here because they share a base URL with everything else, not because they exist in production."},"gift_delivery_not_simulatable":{"type":"invalid_request_error","http_status":422,"retry":"after_fix","description":"Sandbox only. Delivery can only be simulated for a gift On Me sends itself (email or phone); a link gift is delivered by you."},"resource_locked":{"type":"api_error","http_status":409,"retry":"yes","description":"Another operation holds this resource. Nothing happened; retry after a short wait."},"idempotency_in_progress":{"type":"api_error","http_status":409,"retry":"yes","description":"A request with this Idempotency-Key is still in progress. Retry after a short wait to get its result."},"internal_error":{"type":"api_error","http_status":500,"retry":"yes","description":"An unexpected error occurred on our side. It is safe to retry after a short wait; quote the request_id if it persists."}}}