openapi: 3.2.0 info: title: DoiT Billing Transfer API description: Programmatic access to DoiT Platform version: v1 servers: - url: https://api.doit.com security: - api_key: [] - tenantId: [] api_key: [] tags: - name: Billing Transfer description: Manage AWS billing-transfer mappings between distributors and resellers and between resellers and end customers, and list program management accounts. paths: /billingtransfer/v1/resellerhandshakes: post: tags: - Billing Transfer summary: Create reseller handshakes (batch) description: 'Maps reseller to distributor; also sends the handshake if required, as part of AWS billing transfer onboarding. Distributor-only; callers without the ChannelOps distributor tier entitlement receive `403`. Each item in the batch is processed independently; per-item outcomes are returned in `results[]` with HTTP `200` even when some items fail. Malformed items (missing fields, duplicate `resellerPmaAccountId` within the batch) are rejected up front with `422` and reported in `invalidItems`, and none of the batch is processed in that case. ' operationId: createBillingTransferResellerHandshakes parameters: - $ref: '#/components/parameters/tenantId' - name: Idempotency-Key in: header required: true description: 'Client-generated idempotency key (UUID v4 or ULID recommended, max 255 characters). Re-submitting the same key with the same request returns the cached response without re-executing side effects. ' schema: type: string maxLength: 255 - name: dryRun in: query required: false description: 'If `true`, validates the batch and simulates the outcome without issuing any AWS Organizations handshakes. The response shape is identical to a real execution. ' schema: type: boolean default: false requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ResellerHandshakeBatchCreate' responses: '200': description: OK - Batch processed; see `results` and `summary` for per-item outcomes. content: application/json: schema: $ref: '#/components/schemas/ResellerHandshakeBatchResult' '400': $ref: '#/components/responses/400' '401': $ref: '#/components/responses/401' '403': description: Forbidden - The caller is not entitled to the ChannelOps distributor tier, or does not own the referenced `dpmaId`. content: application/json: schema: $ref: '#/components/schemas/BillingTransferProblemDetails' '422': $ref: '#/components/responses/422_billing_transfer_batch_validation' '500': $ref: '#/components/responses/500' '502': description: Bad Gateway - AWS Organizations was unreachable; retry later. content: application/json: schema: $ref: '#/components/schemas/BillingTransferProblemDetails' /billingtransfer/v1/programmanagementaccounts: get: tags: - Billing Transfer summary: List program management accounts description: 'Lists the caller''s program management accounts (PMAs) and the reseller tenants mapped to each one, including AWS Organizations handshake status per account. Distributor-only. ' operationId: listBillingTransferProgramManagementAccounts parameters: - $ref: '#/components/parameters/tenantId' - $ref: '#/components/parameters/maxResults' - $ref: '#/components/parameters/pageToken' responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/ProgramManagementAccountList' '400': $ref: '#/components/responses/400' '401': $ref: '#/components/responses/401' '403': $ref: '#/components/responses/403' '500': $ref: '#/components/responses/500' /billingtransfer/v1/end-customer-mappings: post: tags: - Billing Transfer summary: Create end-customer mappings (batch) description: 'Maps end-customer AWS accounts under a reseller''s PMA to their DoiT tenant, as part of AWS billing transfer onboarding. Reseller-only; distributors are explicitly denied (`403`). Unlike `POST /billingtransfer/v1/resellerhandshakes`, this endpoint has no `Idempotency-Key` requirement and no `dryRun` support. This path is a deliberate exception to the de-hyphenation convention used by the sibling `resellerhandshakes` and `programmanagementaccounts` paths — it stays hyphenated to match the existing Go route and its console-facing equivalent. Each item in the batch is processed independently; per-item outcomes are returned in `results[]` with HTTP `200`. Malformed items are rejected up front and reported in `invalidItems` without processing any of the batch. ' operationId: createBillingTransferEndCustomerMappings parameters: - $ref: '#/components/parameters/tenantId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/EndCustomerMappingBatchCreate' responses: '200': description: OK - Batch processed; see `results` for per-item outcomes and `invalidItems` for any items rejected before processing. content: application/json: schema: $ref: '#/components/schemas/EndCustomerMappingBatchResult' '400': $ref: '#/components/responses/400' '401': $ref: '#/components/responses/401' '403': description: Forbidden - The caller is a distributor (denied), does not own the referenced `resellerPmaAccountId`, or the reseller is not mapped to the referenced `dpmaId`. content: application/json: schema: $ref: '#/components/schemas/BillingTransferProblemDetails' '422': $ref: '#/components/responses/422_billing_transfer_batch_validation' '500': $ref: '#/components/responses/500' /billingtransfer/v1/programmanagementaccounts/status: get: tags: - Billing Transfer summary: Get program management account status description: 'Lightweight polling surface for the onboarding wizard: returns each of the caller''s PMAs with only its IAM status/diff and timestamps — no tenant fan-out, no handshake aggregation, no pagination. Distributor-only. ' operationId: getBillingTransferProgramManagementAccountsStatus parameters: - $ref: '#/components/parameters/tenantId' responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/ProgramManagementAccountsStatusResponse' '400': $ref: '#/components/responses/400' '401': $ref: '#/components/responses/401' '403': $ref: '#/components/responses/403' '500': $ref: '#/components/responses/500' /billingtransfer/v1/end-customers: get: tags: - Billing Transfer summary: List end-customers under a reseller PMA description: 'Lists the end-customer AWS account mappings under a reseller''s program management account, identified by `dpmaId` and `resellerPmaAccountId`. Callable by the reseller who owns the PMA or the distributor who owns the DPMA. ' operationId: listBillingTransferEndCustomers parameters: - $ref: '#/components/parameters/tenantId' - name: dpmaId in: query required: true description: DPMA ID that owns the reseller PMA referenced by `resellerPmaAccountId`. schema: type: string - name: resellerPmaAccountId in: query required: true description: 12-digit AWS account ID of the reseller's program management account. schema: type: string example: '123456789012' - $ref: '#/components/parameters/includeRevoked' responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/ListEndCustomersResponse' '400': $ref: '#/components/responses/400' '401': $ref: '#/components/responses/401' '403': description: Forbidden - The caller lacks the BillingTransferAdmin permission, or is not the reseller who owns this PMA nor the distributor who owns the DPMA. content: application/json: schema: $ref: '#/components/schemas/BillingTransferProblemDetails' '404': description: Not Found - No reseller PMA exists for the given `resellerPmaAccountId` under the given `dpmaId`. content: application/json: schema: $ref: '#/components/schemas/BillingTransferProblemDetails' '500': $ref: '#/components/responses/500' /billingtransfer/v1/resellers/{resellerPmaAccountId}/end-customers: get: tags: - Billing Transfer summary: List end-customers under a reseller PMA, by reseller PMA alone description: 'Same result as `GET /billingtransfer/v1/end-customers`, identified by `resellerPmaAccountId` alone (no `dpmaId` needed). Callable by the reseller who owns the PMA or the distributor who owns its DPMA. ' operationId: listBillingTransferEndCustomersByReseller parameters: - $ref: '#/components/parameters/tenantId' - name: resellerPmaAccountId in: path required: true description: 12-digit AWS account ID of the reseller's program management account. schema: type: string example: '123456789012' - $ref: '#/components/parameters/includeRevoked' responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/ListEndCustomersResponse' '400': $ref: '#/components/responses/400' '401': $ref: '#/components/responses/401' '403': description: Forbidden - The caller lacks the BillingTransferAdmin permission. content: application/json: schema: $ref: '#/components/schemas/BillingTransferProblemDetails' '404': description: Not Found - No reseller PMA exists for the given `resellerPmaAccountId`, or it exists but the caller is not the reseller who owns it or the distributor who owns its DPMA. content: application/json: schema: $ref: '#/components/schemas/BillingTransferProblemDetails' '500': $ref: '#/components/responses/500' /billingtransfer/v1/reseller-accounts: get: tags: - Billing Transfer summary: List the caller's reseller PMA nodes description: 'Lists every reseller program management account (RPMA) node belonging to the calling reseller, with handshake state and status but without end-customer tenants — the reseller-tier analog of `GET /billingtransfer/v1/programmanagementaccounts`. ' operationId: listBillingTransferResellerAccounts parameters: - $ref: '#/components/parameters/tenantId' responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/ListResellerAccountsResponse' '400': $ref: '#/components/responses/400' '401': $ref: '#/components/responses/401' '403': description: Forbidden - The caller lacks the BillingTransferAdmin permission. content: application/json: schema: $ref: '#/components/schemas/BillingTransferProblemDetails' '500': $ref: '#/components/responses/500' /billingtransfer/v1/reseller-accounts/end-customers: get: tags: - Billing Transfer summary: List the caller's reseller PMA nodes with their end-customer tenants description: 'Lists every reseller PMA node belonging to the calling reseller, each with the end-customer tenants connected under it — the reseller-tier analog of `GET /billingtransfer/v1/programmanagementaccounts`. `region`, `iamStatus` and `lastRefreshTime` are inherited from the parent DPMA root; the reseller node itself carries no IAM/region metadata of its own. ' operationId: listBillingTransferResellerAccountsWithTenants parameters: - $ref: '#/components/parameters/tenantId' - $ref: '#/components/parameters/includeRevoked' responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/ListResellerAccountsWithTenantsResponse' '400': $ref: '#/components/responses/400' '401': $ref: '#/components/responses/401' '403': description: Forbidden - The caller lacks the BillingTransferAdmin permission. content: application/json: schema: $ref: '#/components/schemas/BillingTransferProblemDetails' '500': $ref: '#/components/responses/500' components: responses: '400': description: Bad Request - The server cannot process the request, often due to a malformed request. content: application/json: schema: $ref: '#/components/schemas/Error' '500': description: Internal Server Error - Something went wrong with the DoiT API server. content: application/json: schema: $ref: '#/components/schemas/Error' '401': description: Unauthorized - Invalid API key. content: application/json: schema: $ref: '#/components/schemas/Error' 422_billing_transfer_batch_validation: description: Unprocessable Entity - One or more items in the batch failed validation before any processing began (missing required fields, or duplicate account/customer IDs within the batch), or a batch-level field such as `dpmaId` is missing or invalid. None of the batch was processed; resubmit with corrected `items`. Invalid items are listed in `invalidItems`. content: application/json: schema: $ref: '#/components/schemas/BillingTransferProblemDetails' '403': description: Forbidden - The client is not authorized to perform the request. content: application/json: schema: $ref: '#/components/schemas/Error' schemas: ResellerHandshakeBatchSummary: type: object description: Per-outcome counts across the batch. Field names are snake_case, matching the actual wire format returned by this endpoint — an intentional inconsistency with the rest of the payload, which is camelCase. additionalProperties: false required: - handshake_issued - no_op - mapped - remapped - failed properties: handshake_issued: type: integer no_op: type: integer mapped: type: integer remapped: type: integer failed: type: integer ListResellerAccountsResponse: type: object additionalProperties: false required: - items properties: items: type: array items: $ref: '#/components/schemas/ResellerAccount' EndCustomerMappingBatchCreate: type: object additionalProperties: false required: - dpmaId - resellerPmaAccountId - items properties: dpmaId: type: string resellerPmaAccountId: type: string description: 12-digit AWS account ID of the reseller's program management account. example: '123456789012' items: type: array minItems: 1 maxItems: 100 description: Batch items. Duplicate `ecAccountId` or `ecCustomerId` values within the batch are rejected. items: $ref: '#/components/schemas/EndCustomerMappingItem' EndCustomerMappingResultStatus: type: string enum: - mapped - already_mapped - remapped InvalidHandshakeItem: type: object description: A single batch item that failed validation before processing began. additionalProperties: false required: - index - code properties: index: type: integer description: Zero-based position of the invalid item within the submitted batch. resellerCustomerId: type: string description: Present when the invalid item is a reseller-handshake batch item. code: type: string description: Stable machine-readable error code (e.g. `duplicate_pma`, `duplicate_ec_account`). example: duplicate_pma reason: type: string description: Human-readable explanation of why the item is invalid. HandshakeStatus: type: object description: Per-AWS-Organizations-handshake-state counts across all tenants mapped to a PMA. additionalProperties: false required: - total - requested - open - accepted - declined - canceled - expired properties: total: type: integer requested: type: integer open: type: integer accepted: type: integer declined: type: integer canceled: type: integer expired: type: integer ListEndCustomersResponse: type: object additionalProperties: false required: - items properties: items: type: array items: $ref: '#/components/schemas/EndCustomerNode' Error: type: object description: Standard error response structure. properties: error: type: string description: Detailed error message. ResellerHandshakeBatchCreate: type: object additionalProperties: false required: - dpmaId - items properties: dpmaId: type: string description: The distributor's program management account (DPMA) ID issuing the handshakes. sendHandshakes: type: boolean description: If `true`, issues a new AWS Organizations handshake for each item without an existing one. If omitted or `false`, maps the batch in place without issuing any AWS Organizations handshakes. default: false items: type: array minItems: 1 maxItems: 100 description: Batch items. Duplicate `resellerPmaAccountId` values within the batch are rejected. items: $ref: '#/components/schemas/ResellerHandshakeItem' ProgramManagementAccountStatus: type: object additionalProperties: false required: - dpmaId - accountId - iamStatus - createdAt properties: dpmaId: type: string accountId: type: string description: 12-digit AWS account ID of the program management account. example: '123456789012' iamStatus: type: string iamDiff: allOf: - $ref: '#/components/schemas/IamDiff' description: Present when `iamStatus` indicates drift. `null` when the deployed IAM matches expectations. createdAt: type: string format: date-time updatedAt: type: - string - 'null' format: date-time description: When this account's root record was last updated. `null` if never updated. BillingTransferProblemDetails: type: object description: RFC 7807 problem-detail body returned by Billing Transfer endpoints for 4xx/5xx errors. additionalProperties: false required: - type - title - status - code properties: type: type: string format: uri description: A URI identifying the problem type. title: type: string description: Short, human-readable summary of the problem type. status: type: integer description: The HTTP status code repeated in the body. detail: type: string description: Human-readable explanation specific to this occurrence of the problem. code: type: string description: Stable machine-readable error code. example: billing-transfer.batch_validation_failed retryable: type: boolean description: Whether retrying the same request may succeed without changes. invalidItems: type: array description: Present on batch-validation failures; the batch items that failed validation. items: $ref: '#/components/schemas/InvalidHandshakeItem' EndCustomerMappingBatchResult: type: object additionalProperties: false required: - dpmaId - resellerPmaAccountId - results properties: dpmaId: type: string resellerPmaAccountId: type: string results: type: array items: $ref: '#/components/schemas/EndCustomerMappingResult' invalidItems: type: array description: Items rejected before processing. Omitted when every item passed validation. items: $ref: '#/components/schemas/InvalidHandshakeItem' ProgramManagementAccountsStatusResponse: type: object additionalProperties: false required: - items properties: items: type: array items: $ref: '#/components/schemas/ProgramManagementAccountStatus' IamDiff: type: object additionalProperties: false properties: missing: type: array items: type: string extra: type: array items: type: string ResellerHandshakeItem: type: object additionalProperties: false required: - resellerCustomerId - resellerPmaAccountId properties: resellerCustomerId: type: string description: DoiT customer ID of the reseller's end customer to issue a handshake for. resellerPmaAccountId: type: string description: 12-digit AWS account ID of the reseller's program management account (PMA). example: '123456789012' ResellerAccountWithTenants: type: object additionalProperties: false required: - resellerPmaAccountId - dpmaId - handshakeId - handshakeState - status - region - iamStatus - createdAt - handshakeStatus - tenants properties: resellerPmaAccountId: type: string description: 12-digit AWS account ID of the reseller's program management account. example: '123456789012' dpmaId: type: string handshakeId: type: string handshakeState: $ref: '#/components/schemas/HandshakeState' status: type: string enum: - handshake_pending - cur_export_waiting - active - declined - canceled - expired region: type: string description: Inherited from the parent DPMA root; the reseller node itself carries no region metadata. iamStatus: type: string description: Inherited from the parent DPMA root; the reseller node itself carries no IAM metadata. lastRefreshTime: type: - string - 'null' format: date-time description: Inherited from the parent DPMA root. `null` if never refreshed. effectiveTime: type: - string - 'null' format: date-time description: When the handshake reached a terminal state. `null` until then. createdAt: type: string format: date-time handshakeStatus: $ref: '#/components/schemas/HandshakeStatus' tenants: type: array items: $ref: '#/components/schemas/EndCustomerNode' ProgramManagementAccount: type: object additionalProperties: false required: - dpmaId - accountId - roleArn - stackName - region - iamStatus - createdAt - tenants - handshakeStatus properties: dpmaId: type: string accountId: type: string description: 12-digit AWS account ID of the program management account. example: '123456789012' roleArn: type: string stackName: type: string region: type: string iamStatus: type: string iamDiff: allOf: - $ref: '#/components/schemas/IamDiff' description: Present when `iamStatus` indicates drift. `null` when the deployed IAM matches expectations. createdAt: type: string format: date-time lastRefreshTime: type: - string - 'null' format: date-time description: When this account's handshake/tenant state was last refreshed. `null` if never refreshed. tenants: type: array items: $ref: '#/components/schemas/MappedTenant' handshakeStatus: $ref: '#/components/schemas/HandshakeStatus' MappedTenant: type: object additionalProperties: false required: - customerId - tenantType - parentTenant - displayName - resellerPmaAccountId - handshakeState - status properties: customerId: type: string tenantType: type: string parentTenant: type: string displayName: type: string resellerPmaAccountId: type: string example: '123456789012' handshakeState: $ref: '#/components/schemas/HandshakeState' status: type: string effectiveTime: type: - string - 'null' format: date-time description: When the handshake reached a terminal state. `null` until then. EndCustomerNode: type: object additionalProperties: false required: - ecAccountId - ecCustomerId - status - handshakeState - billSourceType - createdAt properties: ecAccountId: type: string description: 12-digit AWS account ID of the end-customer account. example: '123456789012' ecCustomerId: type: string description: DoiT customer ID the end-customer account is mapped to. status: type: string enum: - pending - cur_export_waiting - active - canceled - declined - expired - revoked handshakeState: $ref: '#/components/schemas/HandshakeState' billSourceType: type: string effectiveTime: type: - string - 'null' format: date-time description: When the handshake reached a terminal state. `null` until then. createdAt: type: string format: date-time lastRefreshTime: type: - string - 'null' format: date-time description: When this mapping's status was last refreshed. `null` if never refreshed. EndCustomerMappingItem: type: object additionalProperties: false required: - ecAccountId - ecCustomerId properties: ecAccountId: type: string description: 12-digit AWS account ID of the end-customer account being mapped. example: '123456789012' ecCustomerId: type: string description: DoiT customer ID to map the end-customer account to. curBasePath: type: string description: Optional CUR export base path for the end-customer account. ResellerHandshakeBatchResult: type: object additionalProperties: false required: - dpmaId - results - summary properties: dpmaId: type: string results: type: array items: $ref: '#/components/schemas/ResellerHandshakeResult' summary: $ref: '#/components/schemas/ResellerHandshakeBatchSummary' ResellerHandshakeResult: type: object additionalProperties: false required: - resellerCustomerId - resellerPmaAccountId - status properties: resellerCustomerId: type: string resellerPmaAccountId: type: string status: $ref: '#/components/schemas/ResellerHandshakeStatus' handshakeId: type: string description: AWS Organizations handshake ID. Absent when no handshake was issued (e.g. `no_op`, `failed`, or dry run). handshakeState: $ref: '#/components/schemas/HandshakeState' error: $ref: '#/components/schemas/ResellerHandshakeError' ListResellerAccountsWithTenantsResponse: type: object additionalProperties: false required: - items properties: items: type: array items: $ref: '#/components/schemas/ResellerAccountWithTenants' ResellerAccount: type: object additionalProperties: false required: - resellerPmaAccountId - dpmaId - handshakeId - handshakeState - status - createdAt properties: resellerPmaAccountId: type: string description: 12-digit AWS account ID of the reseller's program management account. example: '123456789012' dpmaId: type: string handshakeId: type: string handshakeState: $ref: '#/components/schemas/HandshakeState' status: type: string enum: - handshake_pending - cur_export_waiting - active - declined - canceled - expired effectiveTime: type: - string - 'null' format: date-time description: When the handshake reached a terminal state. `null` until then. createdAt: type: string format: date-time ResellerHandshakeError: type: object additionalProperties: false required: - code - message properties: code: type: string description: Stable machine-readable error code for this item. message: type: string description: Human-readable explanation of the failure. ProgramManagementAccountList: type: object additionalProperties: false required: - items - rowCount properties: items: type: array items: $ref: '#/components/schemas/ProgramManagementAccount' pageToken: type: string description: Opaque cursor for the next page. Absent when this is the last page. rowCount: type: integer EndCustomerMappingResult: type: object additionalProperties: false required: - ecAccountId - ecCustomerId - status properties: ecAccountId: type: string ecCustomerId: type: string curBasePath: type: string status: $ref: '#/components/schemas/EndCustomerMappingResultStatus' ResellerHandshakeStatus: type: string description: Outcome of processing this batch item. enum: - handshake_issued - no_op - mapped - remapped - failed HandshakeState: type: string description: AWS Organizations Handshake `State` value, lowercased. enum: - requested - open - canceled - accepted - declined - expired parameters: includeRevoked: name: includeRevoked in: query required: false description: If `true`, includes end-customer mappings that have been revoked. Defaults to `false` (revoked mappings are excluded). schema: type: boolean default: false tenantId: in: header name: X-Tenant-Id required: false description: "Tenant (customer) identifier that conveys the tenant scope for the request\n(DoiT API Design Standards §15).\n\nResolution when the header is absent:\n- A key scoped to exactly one tenant resolves to that tenant automatically; the header is\n optional.\n- A principal that can access more than one tenant (e.g. DoiT-employee keys) must supply the\n header. Without it the request is rejected with `400` and code `tenant_id_required` — the\n server does not guess across tenant scopes.\n\nWhen the header is present but conflicts with the tenant the key is scoped to, the request is\nrejected with `400` and code `tenant_id_mismatch`.\n" schema: type: string example: Kp2mN8qL4vR0sT1wX3yZ pageToken: name: pageToken in: query description: Page token, returned by a previous call, to request the next page of results schema: type: string maxResults: name: maxResults in: query description: The maximum number of results to return in a single page. Use the page tokens to iterate through the entire collection. schema: type: integer format: int64 default: 50 securitySchemes: api_key: type: apiKey name: Authorization description: Use the "Bearer " format or sign in for autofill in: header tenantId: type: apiKey name: X-Tenant-Id description: 'Tenant (customer) ID that sets the request''s customer context. Required when the credential can access more than one tenant; omit when the credential is scoped to exactly one tenant (the server resolves that tenant automatically). If omitted for a multi-tenant credential, the request fails with `400` and code `tenant_id_required`. If the value conflicts with the credential''s tenant scope, the request fails with `400` and code `tenant_id_mismatch`. Use this header over the legacy `customerContext` query parameter, which only applies to legacy API keys and is ignored by personal and service-account API tokens. ' in: header oauth2: type: oauth2 flows: authorizationCode: authorizationUrl: https://console.doit.com/sign-in/oauth tokenUrl: https://console.doit.com/api/auth/token scopes: dci: Access All Data x-samples-languages: - curl - go - node - python x-cli-config: security: oauth2 params: client_id: cli